Components¶
An ankka service is built from a fixed set of component kinds. Each kind has one job, and the runtime supplies everything that job needs: persistence, distribution, scheduling or delivery. You choose the kind by the question the component answers, write its handlers, and register it. Every kind is available in Scala and in Python.
The kinds¶
| Component | Answers | State | Addressed by |
|---|---|---|---|
| Event sourced entity | "what happened to this thing, and what is it now?" | derived from its events | entity id |
| Key value entity | "what is this thing now?" | its latest value | entity id |
| View | "which things match this?" | rows in a table, one per source id | a query |
| Consumer | "when this changes, what else should happen?" | none | nothing; fed by a source |
| Workflow | "how does this multi-step process proceed?" | its own state and current step | workflow id |
| Timed action | "what should happen later?" | none; timers are stored by the runtime | a timer name |
| Agent | "what does a model make of this, with these tools?" | a conversation per session | session id |
| HTTP endpoint | "how does the outside world reach the service?" | none | a route |
Event sourced entity¶
An event sourced entity holds the state of one thing, identified by an id: a cart, an order, an account. Its command handlers decide what happened and persist events; its state is the fold of those events, rebuilt by replaying them. The journal of events is the durable record, so the entity has a full history of how it reached its state, and views and consumers can react to each change.
The runtime hosts each entity id as one actor in cluster sharding, so exactly one instance of the service handles a given id at a time, and commands to it are processed one after another. That makes an entity a consistency boundary: a rule over one entity's state holds without locks. Event sourced entities shows how to build one.
Key value entity¶
A key value entity holds the latest value of one thing and nothing else. A command handler replaces the value, and no history is kept. It has the same hosting and the same one-at-a-time guarantee as an event sourced entity. Choose it for state whose history nobody needs: preferences, a configuration, a cached summary. Key value entities shows how.
View¶
A view is a queryable projection of another component's changes. Its handler receives each change from its source — an entity's events, a key value entity's new values, or messages from a broker topic — and says what that change does to the row for that source id. The runtime stores the rows in a Postgres table as JSON, and you query them with SQL.
A view exists to answer questions an entity cannot: an entity can only be looked up by its id, and "every cart containing product p1" is a question about all of them. A view is updated after the change it reflects, so it is eventually consistent. Views shows how.
Consumer¶
A consumer reacts to a source's changes, as a view does, but keeps no rows. It either acts, by calling other components through the component client, or publishes a message to a broker topic. Use it to turn an internal event into a published one, to trigger work in another component, or to integrate with something outside the service. Delivery is at least once, so what it does must tolerate a repeat. Consumers shows how.
Workflow¶
A workflow is a durable multi-step process: a transfer between two accounts, a checkout that reserves stock and takes payment. Commands start it and change its state; steps run one at a time, call other components, and say what happens next. The runtime journals each transition before the next step starts, so a workflow survives a restart mid-flight and resumes where it was. Timeouts, retries and a failover step for compensation are declared as settings. Workflows shows how.
Timed action¶
A timed action is a call the runtime makes later. A component schedules a timer by name, with a delay and the call to make; the runtime stores it in the database and calls the timed action's handler when it is due, retrying with backoff until the handler reports success. Timers survive restarts. Use them for deadlines, reminders and expiry. Timers shows how.
Agent¶
An agent carries out a task by talking to a language model. Its handler returns an effect naming the instructions, the user's message, the tools the model may call and the guardrails to apply; the runtime runs the loop — calling the model, running tools, feeding their results back — until the model answers. Conversation memory is kept per session, as an event sourced entity, so it survives restarts and can be shared by several agents. Requests to one session are handled one at a time. Agents and sessions explains the model and Agents shows how to build one.
HTTP endpoint¶
An HTTP endpoint is the service's edge. It declares routes, turns requests into component calls, and states an access control list saying who may call it. Rejections from components carry their own error codes, which the endpoint turns into HTTP statuses without mapping them itself. An endpoint can also stream server-sent events, which is how agent responses reach a browser token by token. HTTP endpoints shows how.
Choosing a component¶
| You need to | Use |
|---|---|
| Enforce a rule over one thing's state, and keep its history | an event sourced entity |
| Enforce a rule over one thing's state, with no need for history | a key value entity |
| Find things by anything other than their id, or list them | a view over the entity |
| Do something else whenever a thing changes | a consumer |
| Tell another service that something happened | a consumer that produces to a topic |
| Coordinate several components through steps that must all complete or be compensated | a workflow |
| Make something happen after a delay, even across restarts | a timer and a timed action |
| Ask a model, possibly with tools and memory | an agent |
| Accept requests from outside the service | an HTTP endpoint |
Two rules settle most choices:
- A rule that must always hold lives in one entity. An entity is the only place where a check and the change it guards happen together, one command at a time. A rule that spans entities cannot be enforced by a single handler; it is a process, and belongs in a workflow.
- Reads that are not by id go to a view. Do not keep a list inside an entity to make listing easy; it grows without bound and serializes every change through one entity. Project it into a view instead.
Designing a service applies these to a whole system, with a worked example.
Registration¶
Every component is registered on the service builder explicitly. There is no classpath scanning, so the registration is the complete inventory of what a service hosts, and a component that is not registered fails at startup rather than at its first request.
Scala
Ankka.service
.register(ShoppingCartEntity.descriptor)
.register(CartRows.descriptor)
.withExtension(ProjectionRuntime())
.withExtension(HttpServer.of(clients => ShoppingCartEndpoint(clients.componentClient)))
.start()
Python
await Ankka.service().register(ShoppingCartEntity).register(CartRows).register(ShoppingCartEndpoint).listen()
In Scala, some kinds need a runtime extension as well as registration: views and consumers need
ProjectionRuntime, timed actions need TimerRuntime, agents need AgentRuntime, and endpoints need
HttpServer. In Python the sidecar supplies all of them.