dimanche 31 juillet 2022

CQRS Read Model syncronization: right way to "query" the write models between microservices

I'm trying to follow the database per read/write per microservice pattner with HTTP CQRS API.

following this example

Asset Microservice

Write Model:

  • AssetWriteDb (mssql)
  • Asset class/table
public class Asset
{
 public Guid AssetId {get;set;}
 public Guid ContractId {get;set;} //reference to contract of other microservice
 ...
}

Read Model

  • AssetReadDb (mongodb)
  • AssetAggregate class/collection
public class AssetAggregate
{
 public Guid AssetId {get;set;}
 public Guid ContractId {get;get;}
 public string ContractNumber {get;set;} //this comes from Contract Microservice
 ...
}

Contract Microservice

Write Model

  • ContractWriteDb (mssql)
  • Contract class/table
public class Contract
{
 public Guid ContractId {get;set;}
 public string ContractNumber {get;set;}
 ...
}

Read Model

  • ContractReadDb (mongodb)
  • ContractAggregate class/collection
public class ContractAggregate
{
 public Guid ContractId {get;set;}
 public string ContractNumber {get;set;}
 public int AssetCount {get;set;} //this comes from Asset microservice
 ...
}

Contract aggregate syncronization eventHandler example:

public class ContractAggregateHandler :
 IHandleMessage<ContractChangedEvent> // published from ContractWriteDb mssql repository
 IHandleMessage<AssetChangedEvent> // published from AssetWriteDb mssql repository 
{
 
 public async Task Handle(ContractChangedEvent message)
 {
   await _bus.Send(new RefreshContractAggregateCommand(message.ContractId));
 }

 public async Task Handle(AssetChangedEvent message)
 {
   //here i have two options to obtain the contractId from asset microservice:

   //call the AssetApi microservice reading the AssetAggregate collection (mongodb)
   //var contractId = await _mediator.Send(new GetAssetContractIdQuery(message.AssetId);
   
   //call the AssetApi microservice reading the Asset table (sqlserver)
   //var contractId = await _mediator.Send(new GetAssetContractIdFromWriteDbQuery(message.AssetId);


   await _bus.Send(new RefreshContractAggregateCommand(contractId));
 }
}

Following the rule that Queries should always query the Read Model and Commands should always read and write the Write Model, what are the best practices to achive this?

in the first case (reading the mongodb asset read model), I think it's wrong: the AssetChanged event comes from the AssetWriteDb (sql server) and querying the read model is not safe. also, if I base the aggregates generation by other aggregates, I should listen the AssetAggregateRefreshedEvent, but this will create infinites loops between aggregates generation because the AssetAggregates will need to liaste the ContractAggregateRefreshedEvent that these operations will never ends.

in the second case, (reading the sql asset write model) I think it is the safest but I need to manage a lot of queries that are "wrong" because they are not following the rule "queries must get data from read model". That's why to avoid mistakes, I need to differentiate them using a different ending word like "FromWriteDbQuery"

there is a third option that I obviously didn't want to evaluate: directly querying the AssetWriteDb from the Contract Microservice

NOTE: there is a "public" api gateway that "protects" all internal microservices from the external. the api gataway is exposing always right queries needed for clients that are querying mongodb in the right way. this question is just about internal processing of the aggregates and how to "query" Write Models between microservices

NOTE 2: I didn't write the "synchronization" business logic (the RefreshContractAggreagteHandler) because it's simply a "sql query" to the ContractWriteDb projecting a "ContractAggregate", then to map the assetCount I have the same issue, I want to query the AssetWriteDb from the Contract Microservice, so there is exactly the same of the main question)

Aucun commentaire:

Enregistrer un commentaire