Hi everyone,
To give some context first, I have an ASP.NET Core 3.1 application that works like this:
- The app receives requests using a regular MVC Controller (e.g. POST to /Client to create one)
- The controller then builds a command and sends it like so:
await _endpointInstance.Send(command);
- Then I have a separate console application that has all the Saga handler that listen to the commands that trigger mutations to the Client entity:
public class ClientSaga : Saga<ClientSagaData>, IAmStartedByMessages<CreateClient>, IHandleSagaNotFound, IHandleMessages<UpdateClient>, IHandleTimeouts<UpdateClient>, IHandleMessages<DeleteClient>, IHandleTimeouts<DeleteClient>, IHandleTimeouts<RetryDeleteClient>, IHandleMessages<ClientReadModelCreated>, IHandleMessages<ClientReadModelUpdated>, IHandleMessages<ClientReadModelDeleted>, IHandleMessages<ClientReadModelCreationCancelled>, IHandleMessages<ClientReadModelUpdateCancelled>, IHandleMessages<ClientReadModelDeleteCancelled> {
-
Then these handlers do some boilerplate code. First, we map the message properties to the SagaData. Then, we build and send a CreateTransaction command (handled by another class). And finally, it builds and sends a CreateClientReadModel command (handled by another class).
-
As you can see, my ClientSaga acts as an orchestrator but it also has boilerplate code since I do the same for every entity and I’d have to repeat the same code from point #4.
-
So what I did to avoid the boilerplate code was to create a generic abstract base class:
public abstract class BaseSagaWithGenerics<TSagaData, TCreateCommand, TUpdateCommand, TDeleteCommand> : Saga<TSagaData>, IAmStartedByMessages<TCreateCommand>, IHandleSagaNotFound, IHandleMessages<TUpdateCommand>, IHandleTimeouts<TUpdateCommand> ...... where TSagaData : class, IContainSagaData, IBaseSagaData, new() where TCreateCommand {
The idea here is to have default generic implementations for my handlers depending on the types sent when we inherit from this class and then overriding parts (not boilerplate) of those methods:
public class DegreeSaga : BaseSagaWithGenerics<DegreeSagaData, CreateDegree, UpdateDegree, DeleteDegree> { public override DegreeSagaData MapSagaData(CreateDegree createCommand) { return new DegreeSagaData { ObjectId = createCommand.ObjectId, Name = createCommand.Name, LawDegree = createCommand.LawDegree, Regions = createCommand.Regions, AlternateNames = createCommand.AlternateNames, LegacyId = createCommand.LegacyId }; }
But here the problem is that the implementation for the Handle method have the same signature so the c# compiler won’t let me implement this:
|Error|CS0695|'BaseSagaWithGenerics<TSagaData, TCreateCommand, TUpdateCommand, TDeleteCommand>' cannot implement both 'IHandleMessages<TCreateCommand>' and 'IHandleMessages<TUpdateCommand>' because they may unify for some type parameter substitutions|BaseSaga.cs|101|Active|
Is there a way to implement this? I also tried with base classes constraints but also no luck there.
Thanks in advance for your help!
Saul.