Cronus is a lightweight framework for building event driven systems with DDD/CQRS in mind
C#Apache-2.0
Cronus is a lightweight framework for dispatching and receiving messages between microservices with DDD/CQRS in mind
Motivation
Building software is not an easy task. It involves specific domain knowledge and a lot of software infrastructure. The goal of Cronus is to keep the software engineers focused on the domain problems because this is important at the end of the day. Cronus aims to keep you away from the software infrastructure.
Usually you do not need a CQRS framework to develop greate apps. However, we noticed a common infrastructure code written with every applicaiton. We started to abstract and move that code to github. The key aspect was that even with a framework you still have full control and flexibility over the application code.
Domain Modeling
To get out the maximum of Cronus you need to mark certain parts of your code to give hints to Cronus.
Serialization
ISerializer interface is really simple. You can plugin your own implementation but do not do it once you are in production.
The samples on this page work with Json and Proteus-protobuf serializers. Every ICommand, IEvent, ValueObject or anything which is persisted is marked with a DataContractAttribute and the properties are marked with a DataMemberAttribute. Here is a quick sample how this works (just ignore the WCF or replace it with Cronus while reading). We use Guid for the name of the DataContract because it is unique.
You can/should/must...
you must add private parameterless constructor
you must initialize all collections in the constructor(s)
you can rename any class whenever you like even when you are already in production
you can rename any property whenever you like even when you are already in production
you can add new properties
You must not...
you must not delete a class when already deployed to production
you must not remove/change the Name of the DataContractAttribute when already deployed to production
you must not remove/change the Order of the DataMemberAttribute when deployed to production. You can change the visibility modifier from public to private
ICommand
A command is used to dispatch domain model changes. It can be accepted or rejected depending on the domain model invariants.
Triggered by
Description
UI
It is NOT common practice to send commands directly from the UI. Usually the UI communicates with web APIs.
API
APIs sit in the middle between UI and Server translating web requests into commands
External System
It is NOT common practice to send commands directly from the External System. Usually the External System communicates with web APIs.
IPort
Ports are a simple way for an aggregate root to communicate with another aggregate root.
ISaga
Sagas are a simple way for an aggregate root to do complex communication with other aggregate roots.
Handled by
Description
IAggregateRootApplicationService
This is a handler where commands are received and delivered to the addressed AggregateRoot. We call these handlers ApplicationService. This is the write side in CQRS.
You can/should/must...
a command must be immutable
a command must clearly state a business intent with a name in imperative form
a command can be rejected due to domain validation, error or other reason
This is a handler where commands are received and delivered to the addressed AggregateRoot. We call these handlers ApplicationService. This is the write side in CQRS.
Triggered by
Description
ICommand
A command is used to dispatch domain model changes. It can either be accepted or rejected depending on the domain model invariants
You can/should/must...
an appservice can load an aggregate root from the event store
an appservice can save new aggregate root events to the event store
an appservice can establish calls to the ReadModel (not common practice but sometimes needed)
an appservice can establish calls to external services
you can do dependency orchestration
an appservice must be stateless
an appservice must update only one aggreate root. Yes, this means that you can create one aggregate and update another one but think twice
You should not...
an appservice should not update more than one aggregate root in single command/handler
you should not place domain logic inside an application service
you should not use application service to send emails, push notifications etc. Use Port or Gateway instead
This is a handler where commands are received and delivered to the addressed AggregateRoot. We call these handlers ApplicationService. This is the write side in CQRS.
Domain events represent business changes which already happened.
Triggered by
Description
IAggregateRoot
TODO
You can/should/must...
an event must be immutable
an event must represent a domain event which already happened with a name in past tense
an event can be dispatched only by one aggregate
[DataContract(Name ="fff400a3-1af0-4332-9cf5-b86c1c962a01")]publicclassAccountSuspended:IEvent{AccountSuspended(){}publicAccountSuspended(AccountIdid){Id=id;}[DataMember(Order =1)]publicAccountIdId{get;privateset;}publicoverridestringToString(){return"Account was suspended";}}
IProjection
Projection tracks events and project their data for specific purposes.
Triggered by
Description
IEvent
Domain events represent business changes which have already happened
You can/should/must...
a projection must be idempotent
a projection must not issue new commands or events
You should not...
a projection should not query other projections. All the data of a projection must be collected from the Events' data
a projection should not establish calls to external systems
IPort
Port is the mechanism to establish communication between aggregates. Usually this involves one aggregate who triggered an event and one aggregate which needs to react.
If you feel the need to do more complex interactions it is advised to use ISaga. The reason for this is that ports do not provide a transparent view of the business flow because they do not have persistent state.
Triggered by
Description
IEvent
Domain events represent business changes which have already happened
You can/should/must...
a port can send a command
ISaga/ProcessManager
When we have a workflow which involves several aggregates it is recommended to have the whole process described in a single place such as а Saga/ProcessManager.
Triggered by
Description
IEvent
Domain events represent business changes which have already happened
You can/should/must...
a saga can send new commands
IGateway
Compared to IPort, which can dispatch a command, an IGateway can do the same but it also has a persistent state. A scenario could be sending commands to external BC, such as push notifications, emails etc. There is no need to event source this state and its perfectly fine if this state is wiped. Example: iOS push notifications badge. This state should be used only for infrastructure needs and never for business cases. Compared to IProjection, which tracks events, projects their data and are not allowed to send any commands at all, an IGateway can store and track metadata required by external systems. Furthermore, IGateways are restricted and not touched when events are replayed.
Triggered by
Description
IEvent
Domain events represent business changes which have already happened
You can/should/must...
a gateway can send new commands
Ecosystem
Legend
Name
Description
It is stable and it will continue to get support, maintenance and future development
The future is not clear. There are two possible paths from here - olympus or tartarus
This has been the prefered serialization with Cronus v2. However, there is a huge warm up performance hit with big projects which needs to be resolved. Despite this, it works really fast. The implementation has small protocol changes
Builds projections dynamically. Very usefull for projects which just started and changes occur frequently. However, it must be switched to another persister such as Cassandra after the initial stages of the project