Functional service
The functional command service is an alternative way to handle commands. There, you don't use aggregates for the domain model. Instead, you define a set of stateless functions that receive the restored state instance and the collection of previously stored events, and produces new events. The service performs the following operations when handling one command:
Extract the stream name from the command, if necessary.
Instantiate all the necessary value objects. This could effectively reject the command if value objects cannot be constructed. The command service could also load some other streams, or any other information, which is needed to execute the command but won't change state.
If the command expects to operate on an existing stream, the stream events get loaded from the Event Store.
Restore state from the loaded events.
Execute an operation on the loaded (or new) state and events, using values from the command, and the constructed value objects.
The function either performs the operation and produces new events, or rejects the operation. It can also do nothing.
If the operation was successful, the service persists new events to the store. Otherwise, it returns a failure to the edge.
Implementation
Eventuous provides a base class for you to build functional command services. It is a generic abstract class, which is typed to the state type. You should create your own implementation of a service for each state type. As command execution is transactional, it can only operate on a single stream, and, logically, only one state type. However, there is no strong link between the state type and the stream name. You can use the same state type for multiple streams, or use different state types for the same stream.
Handling commands
The base class has three methods, which you call in your class constructor to register the command handlers:
OnNewRegisters the handler, which expects that the stream doesn't exist. It will get a new state object instance. The operation will fail when it will try storing events due to version mismatch.
OnExistingRegisters the handler, which expect an existing stream where it will load events from. You need to provide a function to extract the stream name from the command. The handler will get the events loaded from the store, and will throw if there's no stream to load.
OnAnyUsed for handlers, which can operate both on new and existing streams. The command service will try to load events from the given stream, but won't throw if the load fails, and will pass a new state instance instead.
Here is an example of a functional command service form our test project:
The service uses the same BookingState record as described on the State page.
Usage
Because the functional service base class implements the same ICommandService interface, it can be used the same way as any other command service, by calling the Handle<TCommand> method from the API controller. You can, therefore, use it in API controllers similar to the command service:
You can also use the CommandHttpApiBaseFunc base class as described on the command API page.
Registration
You can add a functional service to the DI container using the AddFunctionalService extensions methods: