Skip to content

Commit

Permalink
GitBook: [master] 34 pages modified
Browse files Browse the repository at this point in the history
  • Loading branch information
mynkow authored and gitbook-bot committed Oct 20, 2020
1 parent 0cbbe97 commit 93f3b33
Show file tree
Hide file tree
Showing 17 changed files with 383 additions and 15 deletions.
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Welcome to the Cronus Manual!

Cronus provides the Cronus Framework to help build applications centered on three core concepts - CQRS \(Command Query Responsibility Segregation\) / Event Sourcing and DDD \(Domain Driven Design\).
Cronus provides the Cronus Framework to help build applications centred on three core concepts - CQRS \(Command Query Responsibility Segregation\) / Event Sourcing and DDD \(Domain Driven Design\).

While many types of applications can be built using Cronus, it has proven to be very popular for microservices architectures. Cronus provides an innovative and powerful way of sensibly evolving to event-driven microservices within a microservices architecture.
While many types of applications can be built using Cronus, it has proven to be very popular for microservices architectures. Cronus provides an innovative and powerful way of sensibly evolving to event-driven microservices within a microservice architecture.



24 changes: 12 additions & 12 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@
* [Aggregate](cronus-framework/domain-modeling/aggregate.md)
* [Entity](cronus-framework/domain-modeling/entity.md)
* [Value Object](cronus-framework/domain-modeling/value-object.md)
* [Commands](cronus-framework/domain-modeling/commands.md)
* [Events](cronus-framework/domain-modeling/events.md)
* [Signals](cronus-framework/domain-modeling/signals.md)
* [Published Language](cronus-framework/domain-modeling/published-language.md)
* [Messages](cronus-framework/domain-modeling/messages/README.md)
* [Commands](cronus-framework/domain-modeling/messages/commands.md)
* [Events](cronus-framework/domain-modeling/messages/events.md)
* [Public Events](cronus-framework/domain-modeling/messages/public-events.md)
* [Signals](cronus-framework/domain-modeling/messages/signals.md)
* [Handlers](cronus-framework/domain-modeling/handlers/README.md)
* [Application Services](cronus-framework/domain-modeling/handlers/application-services.md)
* [Sagas](cronus-framework/domain-modeling/handlers/sagas.md)
* [Projections](cronus-framework/domain-modeling/handlers/projections.md)
* [Ports](cronus-framework/domain-modeling/handlers/ports.md)
* [Triggers](cronus-framework/domain-modeling/handlers/triggers.md)
* [Gateways](cronus-framework/domain-modeling/handlers/gateways.md)
* [Migrations](cronus-framework/migrations/README.md)
* [Copy EventStore](cronus-framework/migrations/copy-eventstore.md)
* [Event Store](cronus-framework/event-store.md)
Expand All @@ -28,12 +37,3 @@
* [Messaging](cronus-framework/messaging.md)
* [Configuration](cronus-framework/configuration.md)

## Message Handlers

* [Application Services](message-handlers/application-services.md)
* [Projections](message-handlers/projections.md)
* [Sagas](message-handlers/sagas.md)
* [Ports](message-handlers/ports.md)
* [Gateways](message-handlers/gateways.md)
* [Triggers](message-handlers/triggers.md)

11 changes: 10 additions & 1 deletion docs/cronus-framework/domain-modeling/aggregate.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Aggregates represent the business models explicitly. They are designed to fully

### Aggregate root

Creating an aggregate root with Cronus is as simple as writing a class that inherits`AggregateRoot<TState>` and a class for the state of the aggregate root.
Creating an aggregate root with Cronus is as simple as writing a class that inherits`AggregateRoot<TState>` and a class for the state of the aggregate root. To publish an event from an aggregate root use the `Apply(IEvent @event)` method provided by the base class.

```csharp
public class Concert : AggregateRoot<ConcertState>
Expand All @@ -14,11 +14,13 @@ public class Concert : AggregateRoot<ConcertState>
public Concert(string name, Venue venue, DateTimeOffset startTime, TimeSpan duration)
{
// business logic for creating a concert
Apply(new ConcertAnnounced(...));
}

public void RegisterPerformer(Performer performer)
{
// business logic for registering a performer
Apply(new PerformerRegistered(...));
}

// ...
Expand Down Expand Up @@ -57,6 +59,11 @@ public class ConcertState : AggregateRootState<Concert, ConcertId>
{
// change the state here ...
}

public void When(PerformerRegistered @event)
{
// change the state here ...
}
}
```

Expand All @@ -69,6 +76,7 @@ You could read more about the state pattern [here](https://refactoring.guru/desi
All aggregate root ids must implement the `IAggregateRootId` interface. Since Cronus uses [URNs](https://en.wikipedia.org/wiki/Uniform_Resource_Name) for ids that will require implementing the [URN specification](https://tools.ietf.org/html/rfc8141) as well. If you don't want to do that, you can use the provided helper base class `AggregateRootId`.

```csharp
[DataContract(Name = "e96d90d0-4943-43f4-8a84-cd90b1217d06")]
public class ConcertId : AggregateRootId
{
const string RootName = "concert";
Expand All @@ -82,6 +90,7 @@ public class ConcertId : AggregateRootId
Another option is to use the `AggregateRootId<T>` class. This will give you more flexibility in constructing instances of the id. Also, parsing URNs will return the specified type `T` instead of `AggregateUrn`.

```csharp
[DataContract(Name = "e96d90d0-4943-43f4-8a84-cd90b1217d06")]
public class ConcertId : AggregateRootId<ConcertId>
{
const string RootName = "concert";
Expand Down
78 changes: 78 additions & 0 deletions docs/cronus-framework/domain-modeling/entity.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,80 @@
# Entity

An entity is an object that has an identity and is mutable. Each entity is uniquely identified by an ID rather than by its properties; therefore, two entities can be considered equal if both of them have the same ID even though they have different properties.

You can define an entity with Cronus using the `Entity<TAggregateRoot, TEntityState>` base class. To publish an event from an entity, use the `Apply(IEvent @event)` method provided by the base class.

```csharp
// TODO: give a relevant example
public class ExampleEntity : Entity<Example, ExampleEntityState>
{
public ExampleEntity(Example root, IEntityId entityId, ExampleEntityTitle title)
: base(root, entityId)
{
state.Title = title;
}

public void DoSomething()
{
Apply(new SomethingHappend(state.EntityId));
}

public void DoSomethingElse()
{
Apply(new SomethingElseHappend(state.EntityId));
}
}
```

{% hint style="info" %}
Set the initial state of the entity using the constructor. The event responsible for creating the entity is being published by the root/parent to modify its state. That means that you can not \(and should not\) subscribe to that event in the entity state using `When(Event e)`.
{% endhint %}

## Entity state

The entity state keeps current data of the entity and is responsible for changing it based on events raised only by the same entity.

Use the abstract helper class `EntityState<TEntityId>` to create an entity state. It can be accessed in the entity using the `state`field provided by the base class. Also, you can implement the `IEntityState` interface by yourself in case inheritance is not a viable option.

To change the state of an entity, create event-handler methods for each event with a method signature `public void When(Event e) { ... }`.

```csharp
// TODO: give a relevant example
public class ExampleEntityState : EntityState<ExampleEntityId>
{
public override ExampleEntityId EntityId { get; set; }

public ExampleEntityTitle Title { get; set; }

public void When(SomethingHappend e)
{

}

public void When(SomethingElseHappend e)
{

}
}
```

## Entity id

All entity ids must implement the `IEntityId` interface. Since Cronus uses [URNs](https://en.wikipedia.org/wiki/Uniform_Resource_Name) for ids that will require implementing the [URN specification](https://tools.ietf.org/html/rfc8141) as well. If you don't want to do that, you can use the provided helper base class `EntityId<TAggregateRootId>`.

```csharp
// TODO: give a relevant example
[DataContract(Name = "5154f78a-cd72-43f0-a445-a5d3fa44a461")]
public class ExampleEntityId : EntityId<ConcertId>
{
ExampleEntityId() { }

public ExampleEntityId(string idBase, ConcertId rootId) : base(idBase, rootId, "exampleentity")
{
}
}
```

2 changes: 2 additions & 0 deletions docs/cronus-framework/domain-modeling/handlers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Handlers

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Application Services

This is a handler where commands are received and delivered to the addressed Aggregate. We call these handlers _ApplicationService_. This is the _write side_ in CQRS.

An application service is a command handler for a specific aggregate. One aggregate has one application service whose purpose is to handle commands and invoke the aggregate root's correct methods passing the command's payload. It mediates between Domain and infrastructure and it shields any domain model from the "outside". Only the Application Service interacts with the domain model.

You can create an application service with Cronus by using the `AggregateRootApplicationService` base class. Specifying which commands the application service can handle is done using the `ICommandHandler<T>` interface.

`AggregateRootApplicationService` provides a property of type `IAggregateRepository` that you can use to load and save the aggregate state. There is also a helper method `Update(IAggregateRootId id, Action update)` that loads and aggregate based on the provided id invokes the action and saves the new state if there are any changes.

```csharp
public class ConcertAppService : AggregateRootApplicationService<Concert>,
ICommandHandler<AnnounceConcert>,
ICommandHandler<RegisterPerformer>
{
...

public void Handle(AnnounceConcert command)
{
if (Repository.TryLoad<Concert>(command.Id, out _))
return;

var concert = new Concert(...);
Repository.Save(concert);
}

public void Handle(RegisterPerformer command)
{
Update(command.Id, x => x.RegisterPerformer(...));
}

...
}
```

## Best Practices

{% hint style="success" %}
**You can/should/must...**

* an application service **can** load an aggregate root from the event store
* an application service **can** save new aggregate root events to the event store
* an application service **can** establish calls to the read model \(not a common practice but sometimes needed\)
* an application service **can** establish calls to external services
* you **can** do dependency orchestration
* an application service **must** be stateless
* an application service **must** update only one aggregate root. Yes, you can create one aggregate and update another one but think twice before doing so.
{% endhint %}

{% hint style="warning" %}
**You should not...**

* an application service **should not** update more than one aggregate root in a single command/handler
* you **should not** place domain logic inside an application service
* you **should not** use an application service to send emails, push notifications etc. Use a port or a gateway instead
* an application service **should not** update the read model
{% endhint %}

18 changes: 18 additions & 0 deletions docs/cronus-framework/domain-modeling/handlers/gateways.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Gateways

Compared to Port, which can dispatch a command, a Gateway 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 Projection, which tracks events, projects their data, and are not allowed to send any commands at all, a Gateway can store and track metadata required by external systems. Furthermore, Gateways are restricted and not touched when events are replayed.

## Communication Guide Table

| Triggered by | Description |
| :--- | :--- |
| Event | Domain events represent business changes which have already happened |

## Best Practices

{% hint style="success" %}
**You can/should/must...**

* a gateway **can** send new commands
{% endhint %}

22 changes: 22 additions & 0 deletions docs/cronus-framework/domain-modeling/handlers/ports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Ports

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 Saga. The reason for this is that ports do not provide a transparent view of the business flow because they do not have persistent state.

## Communication Guide Table

| Triggered by | Description |
| :--- | :--- |
| Event | Domain events represent business changes which have already happened |

## Best Practices

{% hint style="success" %}
**You can/should/must...**

* a port can send a command
{% endhint %}



30 changes: 30 additions & 0 deletions docs/cronus-framework/domain-modeling/handlers/projections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Projections

Projection tracks events and project their data for specific purposes.

## Communication Guide Table

| Triggered by | Description |
| :--- | :--- |
| Event | Domain events represent business changes which have already happened |

## Best Practices

{% hint style="success" %}
**You can/should/must...**

* a projection **must** be idempotent
* a projection **must not** issue new commands or events
{% endhint %}

{% hint style="warning" %}
**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
{% endhint %}





22 changes: 22 additions & 0 deletions docs/cronus-framework/domain-modeling/handlers/sagas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
description: Sometimes called a Process Manager
---

# Sagas

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.

## Communication Guide Table

| Triggered by | Description |
| :--- | :--- |
| Event | Domain events represent business changes which have already happened |

## Best Practices

{% hint style="success" %}
**You can/should/must...**

* a saga **can** send new commands
{% endhint %}

2 changes: 2 additions & 0 deletions docs/cronus-framework/domain-modeling/handlers/triggers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Triggers

2 changes: 2 additions & 0 deletions docs/cronus-framework/domain-modeling/messages/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Messages

55 changes: 55 additions & 0 deletions docs/cronus-framework/domain-modeling/messages/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Commands

A command is used to dispatch domain model changes. It can be accepted or rejected depending on the domain model invariants.

## Communication Guide Table

| 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. |
| Port | Ports are a simple way for an aggregate to communicate with other aggregates. |
| Saga | Sagas are a simple way for an aggregate to do complex communication with other aggregates. |
| | |

## Best Practices

{% hint style="success" %}
**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
* a command **must** update only one Aggregate
{% endhint %}

## Examples

```csharp
public class DeactivateAccount : ICommand
{
DeactivateAccount() {}
public DeactivateAccount(AccountId id, Reason reason)
{
Id = id;
Reason = reason;
}

public AccountId Id { get; private set; }
public Reason ReasonToDeactivate { get; private set; }
}

[DataContract(Name = "24c59143-b95e-4fd6-8bbf-8d5efffe3185")]
public class AccountId : StringTenantId
{
protected AccountId() { }
public AccountId(string id, string tenant) : base(id, "account", tenant) { }
public AccountId(IUrn urn) : base(urn, "account") { }
}

public class Reason : ValueObject<Reason>{...}
```



0 comments on commit 93f3b33

Please sign in to comment.