While being aware of ADR-0008: Agent Architecture I'm quite concerned about this architecture.
As far as I can tell from makeAppEnv, most of the system runs through five agents:
let formularyAgent = createFormularyAgent provider
let orderCtxAgent = createOrderCtxAgent logAgent logger provider
// [...]
let orderPlanAgent = createOrderPlanAgent logAgent provider orderCtxPort
let nutritionAgent = createNutritionAgent logger provider orderCtxPort
let interactionAgent = createInteractionAgent ()
This seems to include read operations.
Since the entire purpose of an agent (MailboxProcessor) is to serialise operations, this entails that most operations, including read operations, are serialised.
Each agent serialises all operations on a single thread. With five agents, this seems to effectively limit the system to five threads. For particular subdomains (e.g. nutrition), a single agent means effectively single-threaded access to every operation offered by that agent.
This could become a bottleneck if the system needs to scale.
While not a trivial change, I recommend reverting ADR-0008: Agent Architecture.
If a lock-free architecture is desired, a better alternative is CQRS with a persistent queue for Commands. This may, however, be overkill for this system. For instance, ADR-0008: Agent Architecture states that a benefit of using agents is that
"The agent serialises all access to the provider, removing the need for the explicit lock inside CachedResourceProvider."
While true, other solutions exist, including using lock, introducing a custom compare-and-swap atomic cell type, leveraging thread-safe BCL types, etc.
In fact, we should seriously consider not using F# agents at all. F# agents are useful for serialising and streaming information when persistence is not a concern, which could include logging. Still in light of #416, even this may not be something that warrants agent use.
To expand, a problem with F# agents is that while they serialise operations, they offer no persistence guarantees. You could send a command to an agent, only for the server to shut down before the agent completes the operation. This is particularly a risk in cloud environments.
While being aware of ADR-0008: Agent Architecture I'm quite concerned about this architecture.
As far as I can tell from
makeAppEnv, most of the system runs through five agents:This seems to include read operations.
Since the entire purpose of an agent (
MailboxProcessor) is to serialise operations, this entails that most operations, including read operations, are serialised.Each agent serialises all operations on a single thread. With five agents, this seems to effectively limit the system to five threads. For particular subdomains (e.g. nutrition), a single agent means effectively single-threaded access to every operation offered by that agent.
This could become a bottleneck if the system needs to scale.
While not a trivial change, I recommend reverting ADR-0008: Agent Architecture.
If a lock-free architecture is desired, a better alternative is CQRS with a persistent queue for Commands. This may, however, be overkill for this system. For instance, ADR-0008: Agent Architecture states that a benefit of using agents is that
While true, other solutions exist, including using
lock, introducing a custom compare-and-swap atomic cell type, leveraging thread-safe BCL types, etc.In fact, we should seriously consider not using F# agents at all. F# agents are useful for serialising and streaming information when persistence is not a concern, which could include logging. Still in light of #416, even this may not be something that warrants agent use.
To expand, a problem with F# agents is that while they serialise operations, they offer no persistence guarantees. You could send a command to an agent, only for the server to shut down before the agent completes the operation. This is particularly a risk in cloud environments.