0

So, I have following code:

public class CQRSQuestion {

    public static void main(String[] args) {

        //received command for algorithm run
        AlgorithmAR algorithmAR = new AlgorithmAR(111);
        algorithmAR.runAlgorithm(1,"2");

        //after a while we receive another command to actually save algorithm results
        AlgorithmCachedResultAR cachedResultAR = loadCachedAlgResFromRepoById(123);
        AlgorithmAR algorithmAR1 = loadAlgorithARFromRepoById(111);
        algorithmAR1.applyCache(cachedResultAR);
    }

    class AlgorithmAR{
        int algorithmId;
        AlgorithmPhase1Entity1 phase1;
        AlgorithmPhase2Entity2 phase2;

        void runAlgorithm(int param1, String param2){
            phase1.doPhase1(param1, param2);
        }

        void applyCache(AlgorithmCachedResultAR cachedResultAR){
            //since CQRS aggreagates are for writing only, can we use
            //something like cachedResultAR.getCache() here?
        }
    }

    class AlgorithmPhase1Entity1{
        void doPhase1(int param1, String param2){
            //do phase1 and construct Phase1Completed event
            Bus.publish(new Phase1Completed());
        }

        @EventSourcingHandler
        void when(Phase1Completed event){
            //set state of this object on intial publish and aggregate creation
        }
    }

    class AlgorithmPhase2Entity2{
        @EventHandler   //event handler is not run at aggregate creation from repo
        void when(Phase1Completed event){
            //do phase2 and construct Phase2Completed event
            Bus.publish(new Phase2Completed());
        }

        @EventSourcingHandler
        void when(Phase2Completed event){
            //set state of this object on intial publish and aggregate creation
        }
    }

    class StatelessProcessManagerForAlg{

        @ProcessManagerEventHandler
        void when(Phase1Completed event){
            Bus.publish(new CachePhase1Command());
        }

        @ProcessManagerEventHandler
        void when(Phase2Completed event){
            Bus.publish(new CachePhase2Command());
        }
    }

    class AlgorithmCachedResultAR{
        int cachedResId;
        int algorithmId;

        void create(int param1, String param2, String param3){
            //init aggreagate after phase 1
            Bus.publish(new Phase1Cached());
        }

        void create(String param3){
            //update after phase 2
            Bus.publish(new Phase2Cached());
        }

        @EventSourcingHandler
        void when(Phase1Cached event){
            //update internal state of this object
        }

        @EventSourcingHandler
        void when(Phase2Cached event){
            //update internal state of this object
        }
    }

}

Here I have AlgorithmAR which is Aggregate root that executes some algorithm in 2 phases. After calling runAlgorithm on it, it calls AlgorithmPhase1Entity1 and method doPhase1. This entity then as a result of operation emits Phase1Completed event. This is published at same time to GUI (async) and to the event bus for other interested parties. Here, other interested party is AlgorithmPhase2Entity2 that will receive it in event handler when(Phase1Completed event) and after processing emit event Phase2Completed. Also, interested in this event is StatelessProcessManagerForAlg which is saga that emits command CachePhase1Command for creating other aggregate that stores cached results of algorithm. This command is routed to command handler, that as result creates AlgorithmCachedResultAR and calls method create on it. Params in create method are extracted from CachePhase1Command message.

All @EventSourcingHandler annotated methods execute on rehydration of aggregate from event store and on initial emit of events. @EventHandler annotations execute only on initial emit of events.

Now the question - since I need later to apply cached results of algorithm from AlgorithmCachedResultAR inside AlgorithmAR.applyCache(AlgorithmCachedResultAR cachedResultAR) method, should I do this - since by CQRS, aggregate root is only used for writing data. Here I would actually use AR for querying data from it. If I use read projection, I would have to create 1-1 copy of AlgorithmCachedResultAR which seems redundant here. Thanks!

  • Phase2Cached - this event name is probably a smell that your domain deals with technical stuff it shouldn't know about. – guillaume31 May 2 '17 at 12:57
  • DDD and CQRS are best used in highly collaborative domains where strong business rules govern transitions between states of the system. Not much in this code looks like that. – guillaume31 May 2 '17 at 12:58
1

From what I understand you try to implement a Read model by emulating it using other Aggregates or to implement an Aggregate snapshot using another Aggregate so you have two options:

  1. Develop AlgorithmCachedResult as a Read model, that is, a class that listen for Phase1Completed and Phase2Completed events and store them in a database; keep in mind that this read model can be eventually inconsisten, or

  2. Implement a snapshot feature inside the AlgorithmAR repository possibly by using the Decorator pattern, in order to not break the Single Responsability Pattern. In this way, instead of loading all the event history you search for a cached version of the AlgorithmAR, load it if exists then apply only the events that were generated after that; you could generate a new cache version every n events. I think that is what you want.

  • The problem with 1st option is that I need to preserve this algorithm cached results (to have history of it) so read model does not seem right. Also, read model should have additional data that would be hidden from the user that is "viewing" it. The only option for this second problem is to make view that is used only by the system. – Bojan Vukasovic Feb 27 '17 at 8:25
  • 2nd option is also problematic since I would not want to modify aggregate1 state, since some other user may do something else at the same time and it should not have concurrency exceptions. – Bojan Vukasovic Feb 27 '17 at 8:25
  • "...so read model does not seem right" - why? – Constantin Galbenu Feb 27 '17 at 8:31
  • example is: I have aggregate AlgorithmCachedResult containing array [1,2] -> read model for user should only be [1]. When updating AlgorithmAR I must pass [1,2] - meaning I need read model that contains [1,2] but then this read model would be only used for inner-system works (not display to end user). Also, if read model is removed, I loose history of events. – Bojan Vukasovic Feb 27 '17 at 14:30
  • 1
    I don't seem to understand your problem. Anyway, you should create aggregates only to protect business invariants. – Constantin Galbenu Feb 27 '17 at 16:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.