Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a Contract Fist approach to define Actors #302

Merged
merged 53 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b2a5edd
Update protos and some internals structures
sleipnir Feb 14, 2024
19c7da1
Add proto path and refactor proto
sleipnir Feb 14, 2024
78f74f9
First part of grpc compiler
sleipnir Feb 17, 2024
06a6c61
Guarantees that all Well-Known types will be available at runtime
sleipnir Feb 17, 2024
a69ced2
Generate and compile Elixir modules
sleipnir Feb 17, 2024
f7e49ca
Added docs and some validations to dispatcher module
sleipnir Feb 18, 2024
e798e67
Create service resolver module to find actor metadata in protobuf files
sleipnir Feb 18, 2024
2971559
Update Elixir to 1.15 and create gRPC Reflection Server
sleipnir Feb 18, 2024
837e482
Added documentation
sleipnir Feb 18, 2024
21e533c
Document load_modules function
sleipnir Feb 18, 2024
d78eee2
Start gRPC Server and minor adjusts
sleipnir Feb 18, 2024
77120e5
Merge with main
sleipnir Feb 19, 2024
b900d95
Downgrade elixir version to just 1.15
sleipnir Feb 19, 2024
bd521c9
Adjusts
sleipnir Feb 19, 2024
9c8c419
Added a protobuf extensio for mapping actor ids
sleipnir Feb 20, 2024
3bcdd7f
Some refactor
sleipnir Feb 21, 2024
c4ee513
Build payload
sleipnir Feb 23, 2024
f73f0a0
Some refactor
sleipnir Feb 23, 2024
c40c092
Encode payload
sleipnir Feb 23, 2024
9bf27fb
Fix imports
sleipnir Feb 23, 2024
80d81de
Fix actor not found
sleipnir Feb 23, 2024
52f9c12
Update types
sleipnir Apr 21, 2024
ddb0b26
Merge with main
sleipnir May 27, 2024
2c649bf
Correct lock
sleipnir May 27, 2024
cba7712
Include modeling with actors documentation
sleipnir May 27, 2024
cbe480b
fix bandit version conflict
eliasdarruda May 30, 2024
6e113a9
symlink protos priv path
eliasdarruda May 30, 2024
679e7ca
hello world proto with extensions
eliasdarruda May 30, 2024
341f81d
merge
sleipnir May 31, 2024
4b75066
Merge lock
sleipnir May 31, 2024
d9904bd
Merge doc
sleipnir May 31, 2024
b0861a0
Fix. Protoc compiler
sleipnir May 31, 2024
70ed1be
Fix start grpc server
sleipnir Jun 3, 2024
d07faea
Create new ping-pong test contract
sleipnir Jun 3, 2024
6669082
Added healthcheck proto
sleipnir Jun 4, 2024
5e29f6d
Compile dependencies and implemented default grpc server for healthcheck
sleipnir Jun 4, 2024
d04fc11
Refactor. Move package
sleipnir Jun 4, 2024
98dd021
Refactor. Remove state from health check actor. Use stateless actor i…
sleipnir Jun 5, 2024
fc40cb8
Add actor entity health checks
sleipnir Jun 5, 2024
0932241
Add module import
sleipnir Jun 5, 2024
d6c056d
Add correct import
sleipnir Jun 5, 2024
019c8b2
skip distributed tests
eliasdarruda Jun 5, 2024
91ac421
Implemented healthcheck actor callbacks
sleipnir Jun 6, 2024
d635e41
Fix. Dispatch evento to actors
sleipnir Jun 10, 2024
3537f92
fix unary type request throwing a stream error
eliasdarruda Jun 11, 2024
a99107b
warning changes and config.exs getting the right env configs
eliasdarruda Jun 11, 2024
96399c7
Compile all
sleipnir Jun 11, 2024
d78ebc7
Merge branch 'feat/embedded-grpc' of https://github.com/eigr/spawn in…
sleipnir Jun 11, 2024
17bf1a1
fix multiple proto files erroring
eliasdarruda Jun 11, 2024
c906e2b
nested folder proto example
eliasdarruda Jun 11, 2024
bb02784
some generator fixes
eliasdarruda Jun 11, 2024
0229339
fix code generator
eliasdarruda Jun 11, 2024
8895171
fix lock deps
eliasdarruda Jun 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
steps:
- uses: actions/checkout@v3

- name: Install Protoc
uses: arduino/setup-protoc@v3

- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ run-proxy-local2:
ERL_ZFLAGS='-proto_dist inet_tls -ssl_dist_optfile rel/overlays/local-mtls.ssl.conf' cd spawn_proxy/proxy && mix deps.get && PROXY_DATABASE_TYPE=$(database) PROXY_HTTP_PORT=9003 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= iex --name spawn_a2@test.default.svc -S mix

run-proxy-local-3:
cd spawn_proxy/proxy && mix deps.get && SPAWN_PROXY_LOGGER_LEVEL=info PROXY_CLUSTER_STRATEGY=epmd SPAWN_USE_INTERNAL_NATS=true SPAWN_PUBSUB_ADAPTER=nats PROXY_DATABASE_PORT=3307 PROXY_DATABASE_TYPE=mariadb PROXY_DATABASE_POOL_SIZE=30 PROXY_HTTP_PORT=9003 USER_FUNCTION_PORT=8091 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= iex --name spawn_a3@127.0.0.1 -S mix
cd spawn_proxy/proxy && mix deps.get && SPAWN_PROXY_LOGGER_LEVEL=info PROXY_CLUSTER_STRATEGY=epmd SPAWN_USE_INTERNAL_NATS=false SPAWN_PUBSUB_ADAPTER=nats PROXY_DATABASE_PORT=3307 PROXY_DATABASE_TYPE=mariadb PROXY_DATABASE_POOL_SIZE=30 PROXY_HTTP_PORT=9003 USER_FUNCTION_PORT=8091 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= iex --name spawn_a3@127.0.0.1 -S mix

run-proxy-local-nodejs-test:
ERL_ZFLAGS='-proto_dist inet_tls -ssl_dist_optfile rel/overlays/local-mtls.ssl.conf' cd spawn_proxy/proxy && mix deps.get && PROXY_DATABASE_TYPE=$(database) PROXY_HTTP_PORT=9001 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= PROXY_ACTOR_SYSTEM_NAME=SpawnSysTest SPAWN_SUPERVISORS_STATE_HANDOFF_CONTROLLER=crdt iex --name spawn_a1@test.default.svc -S mix
Expand Down
52 changes: 47 additions & 5 deletions compile-pb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,51 @@ set -o nounset
set -o errexit
set -o pipefail

protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/any.proto
protoc --elixir_out=gen_descriptors=true:./lib/spawn/actors --proto_path=priv/protos priv/protos/eigr/functions/protocol/actors/actor.proto
protoc --elixir_out=gen_descriptors=true:./lib/spawn/actors --proto_path=priv/protos priv/protos/eigr/functions/protocol/actors/protocol.proto
protoc --elixir_out=gen_descriptors=true:./lib/spawn/actors --proto_path=priv/protos priv/protos/eigr/functions/protocol/actors/state.proto
protoc --elixir_out=gen_descriptors=true,plugins=grpc:./lib/spawn/grpc --proto_path=priv/protos/grpc/ priv/protos/grpc/reflection/v1alpha/reflection.proto

#protoc --elixir_out=gen_descriptors=true:./lib/spawn/cloudevents --proto_path=priv/protos/io/cloudevents/v1 priv/protos/io/cloudevents/v1/spec.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/any.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/empty.proto
# protoc --elixir_out=./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/descriptor.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/duration.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/timestamp.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/struct.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/source_context.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/field_mask.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/wrappers.proto

# protoc --elixir_out=gen_descriptors=true:./lib/spawn/actors --proto_path=priv/protos priv/protos/eigr/functions/protocol/actors/extensions.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/actors --proto_path=priv/protos priv/protos/eigr/functions/protocol/actors/actor.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/actors --proto_path=priv/protos priv/protos/eigr/functions/protocol/actors/protocol.proto
# protoc --elixir_out=gen_descriptors=true:./lib/spawn/actors --proto_path=priv/protos priv/protos/eigr/functions/protocol/actors/state.proto


#protoc --elixir_out=gen_descriptors=true:./lib/spawn/actors --proto_path=priv/protos priv/protos/eigr/functions/protocol/actors/healthcheck.proto

#protoc --elixir_out=gen_descriptors=true:./lib/spawn/cloudevents --proto_path=priv/protos/io/cloudevents/v1 priv/protos/io/cloudevents/v1/spec.proto

PROTOS=("
priv/protos/eigr/functions/protocol/actors/extensions.proto
priv/protos/eigr/functions/protocol/actors/actor.proto
priv/protos/eigr/functions/protocol/actors/protocol.proto
priv/protos/eigr/functions/protocol/actors/state.proto
priv/protos/eigr/functions/protocol/actors/healthcheck.proto
")

BASE_PATH=`pwd`

echo "Base protobuf path is: $BASE_PATH/priv/protos"

for file in $PROTOS; do
echo "Compiling file $BASE_PATH/$file..."

mix protobuf.generate \
--output-path=./lib/spawn/actors \
--include-docs=true \
--generate-descriptors=true \
--include-path=$BASE_PATH/priv/protos/ \
--include-path=./priv/protos/google/protobuf \
--include-path=./priv/protos/google/api \
--plugins=ProtobufGenerate.Plugins.GRPCWithOptions \
--one-file-per-module \
$BASE_PATH/$file
done
5 changes: 2 additions & 3 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ config :do_it, DoIt.Commfig,

config :logger,
backends: [:console],
truncate: 65536

# level: :info
truncate: 65536,
level: :debug

# compile_time_purge_matching: [
# [level_lower_than: :info]
Expand Down
2 changes: 1 addition & 1 deletion config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import Config

if config_env() == :prod do
config :logger,
level: String.to_atom(System.get_env("SPAWN_PROXY_LOGGER_LEVEL", "debug"))
level: String.to_atom(System.get_env("SPAWN_PROXY_LOGGER_LEVEL", "info"))
end
113 changes: 112 additions & 1 deletion docs/modeling_with_actors.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,113 @@
# Modeling Systems with Actors
TODO

The Actor Model, introduced by Carl Hewitt in 1973, is a conceptual framework for dealing with concurrent computation. Unlike traditional models that rely on shared state and locks, the Actor Model provides a highly modular and scalable approach to designing systems, making it particularly well-suited for distributed and concurrent applications.

At its core, the Actor Model revolves around the concept of "actors"—independent entities that encapsulate state and behavior. Each actor can:
1. Receive messages from other actors.
2. Process these messages using its internal behavior.
3. Send messages to other actors.
4. Create new actors.

This model inherently avoids issues like race conditions and deadlocks by ensuring that each actor processes only one message at a time. Consequently, the state is modified in a thread-safe manner, as actors do not share state directly but communicate exclusively through message passing.

### Key Benefits

1. **Concurrency and Scalability**: Since actors operate independently, systems based on the Actor Model can easily scale across multiple processors or machines. This makes it ideal for cloud-native applications and services that demand high concurrency.

2. **Fault Tolerance**: Actors can be designed to monitor and manage other actors, allowing for robust error handling and recovery strategies. This self-healing capability is a cornerstone of resilient system design.

3. **Modularity**: Actors encapsulate state and behavior, promoting a modular structure that is easier to reason about, test, and maintain. This separation of concerns aligns well with microservices architecture, where each service can be seen as an actor.

### Using the Actor Model to Build Business Applications

In the realm of business applications, the Actor Model can be employed to create robust, scalable, and maintainable systems. Here’s how it can be leveraged:

1. **Order Processing Systems**: In e-commerce platforms, each order can be represented as an actor. The order actor can handle various stages of the order lifecycle, including validation, payment processing, inventory adjustment, and shipping. By encapsulating these processes within an actor, the system can handle a large number of orders concurrently without interference.

2. **Customer Service Applications**: Each customer session can be modeled as an actor, managing individual interactions and maintaining state throughout the session. This is particularly useful in chatbots and automated customer service systems where maintaining context and state consistency is crucial.

3. **Financial Systems**: In banking applications, transactions can be modeled as actors. This allows for secure, concurrent processing of transactions, ensuring that the system can handle multiple operations simultaneously without risking data corruption.

4. **Game Applications**: In multiplayer online games, actors can represent various entities such as players, non-player characters (NPCs), items, and game rooms. Each actor encapsulates its state and behavior, interacting through messages to create a dynamic and responsive game world.

### Architectural Decisions Influenced by Actor Model Constraints

Implementing the Actor Model in business applications necessitates careful architectural planning due to its unique constraints and capabilities:

1. **State Management**: Actors maintain their state internally and do not share state. This requires designing the system in such a way that all necessary information is passed through messages. This may involve breaking down complex processes into smaller, message-driven interactions.

2. **Message Passing**: Since actors communicate solely through asynchronous messages, it’s essential to design a robust messaging infrastructure. This includes choosing appropriate messaging protocols, ensuring message delivery guarantees (e.g., at-least-once, at-most-once, or exactly-once delivery), and handling message serialization/deserialization.

3. **Error Handling and Supervision**: Actors can supervise other actors, which means designing a supervision strategy is crucial. This involves defining how actors should respond to failures—whether they should restart, escalate the error, or stop entirely. Effective supervision trees help in building resilient applications.

4. **Distributed Coordination**: In distributed systems, coordinating actors across multiple nodes can be challenging. Architectural decisions should account for network partitions, latency, and data consistency. Utilizing Spawn framework can simplify these aspects by providing built-in support for distributed actor systems.

5. **Scalability Considerations**: Scaling an actor-based system requires monitoring actor workloads and ensuring that actors are efficiently distributed across available resources. Load balancing strategies and dynamic actor creation/destruction policies need to be defined to maintain optimal performance.

### Granularity of Actors and Its Impact

One critical architectural decision in the Actor Model is determining the granularity of actors. Granularity refers to the size and number of actors within the system, affecting both performance and maintainability.

#### Fine-Grained vs. Coarse-Grained Actors

1. **Fine-Grained Actors**: In this approach, actors are small, representing very specific tasks or entities. For example, each item in an inventory system might be an individual actor.
- **Pros**: High concurrency, fine control over individual components, easier to scale specific parts of the system.
- **Cons**: Higher overhead due to the increased number of actors, potential performance issues due to frequent message passing, complex actor management.

2. **Coarse-Grained Actors**: Here, actors represent larger aggregates, such as an entire inventory or order processing system.
- **Pros**: Reduced overhead, fewer messages, simpler management.
- **Cons**: Reduced concurrency, potential bottlenecks if a single actor becomes overwhelmed, more complex internal state management.

### Managing Granularity

To effectively manage granularity, consider the following strategies:

1. **Profiling and Performance Testing**: Continuously monitor the performance of your actor system. Identify bottlenecks and adjust the granularity accordingly. Profiling tools specific to actor systems, such as those available in Spawn, can help pinpoint performance issues.

2. **Dynamic Actor Creation**: Use patterns like actor hierarchies and dynamic actor creation to balance the load. For instance, a coarse-grained actor can create fine-grained child actors to handle specific tasks when needed, thus balancing load dynamically.

3. **State Partitioning**: Partition state across multiple actors where applicable. In a fine-grained system, ensure that each actor holds only the necessary state for its specific responsibility, reducing the overhead of managing large states.

4. **Batch Processing**: In some cases, batch processing within actors can reduce the message-passing overhead. Group related tasks and process them in batches within a single actor, which can be particularly useful in coarse-grained systems.

### Modeling Actors as Business Entities

In business applications, actors can be modeled as business entities to better align with real-world processes and operations. Here’s how to effectively model actors as business entities like users, transactions, and more:

1. **User Actors**: Each user can be represented as an actor, encapsulating the user's state, preferences, and actions. This actor can handle messages related to user authentication, profile updates, and interaction with other parts of the system.
- **Example**: In a social media platform, a user actor might manage friend requests, message notifications, and content creation.

2. **Transaction Actors**: Transactions in financial systems can be modeled as actors to ensure secure and isolated processing. Each transaction actor handles the entire lifecycle of a transaction, from initiation to completion, including rollback in case of failures.
- **Example**: In a banking application, a transaction actor can manage the transfer of funds between accounts, ensuring consistency and integrity.

3. **Order Actors**: For e-commerce platforms, orders can be represented as actors. Each order actor manages the stages of the order lifecycle, such as validation, payment processing, inventory update, and shipping.
- **Example**: An order actor can interact with inventory actors to check stock availability and with payment actors to process transactions.

4. **Game Entities**: In a game application, actors can represent various game entities such as players, NPCs, items, and game rooms. Each game entity actor manages its state and behavior, interacting through messages to create a dynamic and interactive game environment.
- **Example**: In a multiplayer online game, player actors manage individual player states, NPC actors handle non-player character behavior, and item actors represent in-game objects like weapons and power-ups.

### Actor Model and Architectural Patterns

Actors can be used to implement various architectural patterns, enhancing the robustness and scalability of business applications:

1. **Saga Pattern**: The Saga pattern is used to manage long-running transactions by breaking them into a series of smaller, isolated transactions that can be individually committed or compensated. Actors are well-suited for implementing the Saga pattern due to their message-passing capabilities and independent state management.
- **Implementation**: Each step of a saga can be an actor, with a coordinator actor managing the overall process. The coordinator sends messages to initiate each step and handles compensation actions in case of failures.

2. **Event Sourcing**: Actors can be used to implement event sourcing, where changes to the state are stored as a sequence of events. Each actor can manage its event log, ensuring that the state can be reconstructed by replaying events.
- **Implementation**: An order actor, for instance, can log events such as "OrderPlaced," "PaymentProcessed," and "OrderShipped." Replaying these events can reconstruct the current state of the order.

3. **CQRS (Command Query Responsibility Segregation)**: In a CQRS architecture, actors can handle commands (state-changing operations) and queries (read operations) separately, improving scalability and performance.
- **Implementation**: Command actors handle operations that modify the state, while query actors provide read-only views of the state. This separation can enhance performance by allowing the system to scale read and write operations independently.

4. **State Machines**: Actors can represent state machines, where each state of the actor corresponds to a different behavior. This is particularly useful in scenarios where an entity has a well-defined lifecycle with distinct states.
- **Implementation**: Consider a user registration process. The user actor can have states like "New," "Email

Sent," "Verified," and "Active." Each state defines how the actor responds to messages, enabling clear and maintainable transitions.

### Conclusion

The Actor Model offers a robust paradigm for building scalable, resilient, and maintainable software systems. By embracing message-passing and encapsulating state within actors, developers can address the complexities of concurrency and distribution effectively. In business applications, this model can streamline processes such as order handling, customer interactions, financial transactions, and game interactions. The granularity of actors significantly impacts system performance and maintainability, requiring careful management through profiling, dynamic actor creation, state partitioning, and batch processing. Additionally, actors can be modeled as business entities, aligning system design with real-world processes. By implementing architectural patterns like Sagas, Event Sourcing, CQRS, and State Machines, the Actor Model can enhance the robustness and scalability of a wide range of applications.

[Next: Index](index.md)

[Previous: History](history.md)
Loading
Loading