ContextMapper / context-mapper-dsl

ContextMapper DSL: A Domain-specific Language for Context Mapping & Service Decomposition
https://contextmapper.org/
Apache License 2.0
215 stars 28 forks source link

Add the end state(s) to PlantUML diagrams #316

Closed Adelrisk closed 1 year ago

Adelrisk commented 2 years ago

Is your feature request related to a problem? Please describe. Generated PlantUML diagrams do not contain explicitly contain end states as stated in the documentation https://contextmapper.org/docs/aggregate/

// target states can be marked as end states with a star: CHECK_IN_PROGRESS -> ACCEPTED X REJECTED

The stars do not lead to any visual change in the PlantUML diagrams. Because the end states are not explicitly documented, this leads to an additional cognitive burden to read this information out of the diagrams.

To demonstrate:

    Aggregate Test {
        Entity TestEntity {
            aggregateRoot
        }
        enum TestState {
            aggregateLifecycle
            START, MIDDLE, END
        }
        Service TestService {
            void createEntity(@TestEntity entity): write [ -> START ];
            void process(@TestEntity entity): write [ START -> MIDDLE ];
            void complete(@TestEntity entity): write [ MIDDLE -> END* ];
        }
    }

Context-Mapper will generate the following state diagram: https://www.plantuml.com/plantuml/uml/FOunoy8m48Rt-nKtVz3b6nsaXNOGgeFj9WuXlePYceJa7FBlJQXrIGwUvttlsYGgodkrb5wB2oggbRLVWtTmCacsu0B_YQQvkyvyO1ekVVtEfxuVXphT_kxSD4VU3HsX18EJHV8tLl4l4ABNI6blg6phh7ij14HaOU0UB3wmXXD4OfwHX3jZ8emIyBWyeREse69kFhNbAUT-0000

@startuml

skinparam componentStyle uml2

START : 
END : 
MIDDLE : 
[*] --> START : createEntity
START --> MIDDLE : process
MIDDLE --> END : complete

legend top center
  Test Aggregate Lifecycle
endlegend

@enduml

Describe the solution you'd like I can thing of two possible solutions.

Solution 1 My initial knee-jerk reaction was to suggest adding the transition END -> [*] to the state diagram, like so: https://www.plantuml.com/plantuml/uml/FO-_2i8m4CRtUugRXIwE3j92EmZLmNOJXn0_Gp5DGd8EVNiJQrsIuxu_FwvEegAyPqkKNfCBAggPjP-3Tt0oo6B1MTqJ3MDp7Vd0r5tQyfrFRTjtPRhjxbnLHzuSEa89EYUJBBzOaV-14Br6Ibj3qTREmhKGV15vcuk9oCB0FLXyO0qdYCGy8WatnaIOZE9-Ua8lseAoS_KJrVajXtq0

@startuml

skinparam componentStyle uml2

START : 
END : 
MIDDLE : 
[*] --> START : createEntity
START --> MIDDLE : process
MIDDLE --> END : complete
END -> [*]

legend top center
  Test Aggregate Lifecycle
endlegend

@enduml

Solution 2 It occurred to me that introducing an additional state is not desirable, but a visual change should be enough. For example: https://www.plantuml.com/plantuml/uml/HOynJyCm48Nt-nKdTKFgmjW1jIWPa0e3pLPrCDU75ULYM_OnH8Z_ZWs4Bbw-zps_snTpsJvc9IhJ69BBRcQ9Sue1mKwsABZIUwBJS7WTUCTK3GFtBnrlDkUtgDVBepgP-9E-wFcfwuvzIvtlBhpTFl2gIKRL-c2ZBNzT3VyBAKT1AMj3O-sBtSyOXM6LRi05WHAHmYDSsM9YGJ1aOXvGZ0_UP_YszZY-GnPHK4t-vedszQstVGC0

@startuml
skinparam componentStyle uml2

START : 
state END ##[bold]Black {
}
MIDDLE : 
[*] --> START : createEntity
START --> MIDDLE : process
MIDDLE --> END : complete
START --> END : cancel

legend top center
  Test Aggregate Lifecycle
endlegend
@enduml

Additional context The relevance of this issue increases with the complexity of the state diagram, raising the cognitive burden proportionly.

Adelrisk commented 2 years ago

AFAIK, the end state is not used in the whole code-base https://github.com/ContextMapper/context-mapper-dsl/blob/master/org.contextmapper.dsl/src/org/contextmapper/tactic/dsl/TacticDDDLanguage.xtext#L131

TargetState:
    value=[EnumValue]((endState?='*'))?
;
stefan-ka commented 2 years ago

Thank you very much @Adelrisk for reporting the issue.

I was wondering why you think that introducing the additional final state is not desirable? (END -> [*]) Wouldn't that be the "UML conform" way to solve the issue? I mean the start ([*]) and end ([*]) points are not real Aggregate states; they just mark the initial and the final state. (in more complex examples there could be multiple final states of course)

Adelrisk commented 2 years ago

I was wondering why you think that introducing the additional final state is not desirable? (END -> [*])

This is tricky to describe, and the point of discussion is very fine. I believe I identify the arise of two different mental models describing the states, and thus a slight difference in semantics. If true, it would lead to unexpected results for the uninitiated/inexperienced, and just lower the satisfaction of experienced users forced to adapt.

This arises because the of the mismatch in descriptions between the Context-Mapper DSL and PlantUML (as proxy of UML). In the DSL, the end state is labelled/named. In the solution 2 resulting PlantUML, not. In the solution 3 resulting PlantUML, the semantics are preserved. Additionally, Context-Mapper currently allows a clear description of the transition to the end state (adopting the name of the operation/command out of the context). By artificially introducing an end state, without a corresponding action/label, it becomes bloat that has to be ignored by the reader - as nothing else actually models or corresponds to this action anywhere.

Does this make sense?

Adelrisk commented 2 years ago

To make my point with an example.

UML challenge

The target

@startuml
[*] --> A : create
A --> B : joke
B --> A : halt
B --> [*] : scare
@enduml

Current DSL

Recap: if modelled in the DSL, the resulting end state is not obvious

@startuml
[*] --> A : create
A --> B : joke
B --> A : halt
B --> END : scare
@enduml

(Alternatively, there is the "everyday" solution. The transition scare is omitted and the reader is informed by further documentation/collogues/the customer that scare happens and ends things.)

Solution 1

Recap: helpfully automatically add an additional transition to the DSL from the current solution:

@startuml
[*] --> A : create
A --> B : joke
B --> A : halt
B --> END : scare
END --> [*]
@enduml

Solution 2

Recap: visually mark the end state described as such in the DSL.

@startuml
state END ##[bold]Black {
}
[*] --> A : create
A --> B : joke
B --> A : halt
B --> END : scare
@enduml

Summary

Solution 1 has addition UML features, but also includes (in the context of the documented solution) loose ends/undocumented features. Solution 2 is (graphically) equivalent and with a small help semantically equivalent.

Additional thoughts This should probably be stated in an additional issue, but the current PlantUML generator will always list all the states before the transitions. This greatly influences the layouting of PlantUML. In my experience, these diagrams are then compact (similarly high and wide), but also greatly confusing to read (many crossing transitions; transitions close to multiple labels). After remove this state list, the diagram is tall but (I feel) more readable.

stefan-ka commented 2 years ago

Honestly I still don't really get why the generated output should not just conform to the UML standard. In my opinion it does not make any sense to compare the Context Mapper DSL and PlantUML DSL. I personally don't care how something is written in PlantUML :) I just want the graphical representation to be correct in terms of UML. How the written PlantUML text compares to CML does not matter at all... That's my opinion :)

And graphically the answer is clear, right? The end states need the corresponding UML mark; which in PlantUml is added with END -> [*]. That this is another line in PlantUML, which does not exist in CML, does not matter at all. But it produces a graphical representation that is correct in terms of UML.

Do i still not get your point? :)

Adelrisk commented 2 years ago

Do i still not get your point? :)

Probably :)

I was using the PlantUML to represent the semantics described in the ContextMapper DSL. I'm trying to describe a disconnect that will arise when "correct" UML is generated - a disconnect between expectation and reality.

In my opinion it does not make any sense to compare the Context Mapper DSL and PlantUML DSL.

For me, it makes sense because they should describe the same semantics. That is my expectation. Maybe this is the critical point?

This reminds me how ORMs are a leaky abstraction over SQL. I feel the ContextMapper DSL is a leaky abstraction for the UML. That's not terrible, we users can all make it work - but I've seen to many non-SQL-experts (or non-ORM-experts?) struggle to make ORMs truly productive. This comparison makes me think: maybe there is no ideal solution for this UML discussion?

stefan-ka commented 2 years ago

This reminds me how ORMs are a leaky abstraction over SQL. I feel the ContextMapper DSL is a leaky abstraction for the UML.

Well, then we have a completely different understanding of what Context Mapper is ;) That sounds like you just want to do UML. Then I would suggest you don't use Context Mapper and just write PlantUML directly :D

It was never our idea to abstract UML. CML implements our interpretation of the meta-model of DDD on a strategic level. The tactic level has been taken from Sculptor and implements the meta-model of tactic DDD.

We only derive UML models from the information we have within our DDD models and try to derive / translate as best as we can. But the meta-models behind the two languages are different.

I'm trying to describe a disconnect that will arise when "correct" UML is generated - a disconnect between expectation and reality.

I still don't see that disconnect in semantics here.

All three are semantically exactly the same things (same meaning). Those are just visually and textually different approaches to express exactly the same thing; namely that END is and end-state. Only different syntax.

Your original solutions 1 and 2 are just two different suggestions to visualize the semantically same thing. Just that one conforms to UML and the other is just another suggestion to visualize the same thing in another way :) Whether you use "an arrow that directs to a circle with a dot inside" or "make the states borders bold" are just two different syntaxes to say the same thing.

For me, it makes sense because they should describe the same semantics.

When you compare DSL's you compare text/syntax; you do not compare semantics.

I can define two languages A and B.

A statement in A: xy -> wz A statement in B: @#?holymoly wzxy

These two statements can be semantically the same thing. Depends on the languages definition and what they want to express with those statements.

stefan-ka commented 1 year ago

I implemented a solution for this one.

The following example CML:

BoundedContext InsuranceQuotes {

   Aggregate QuoteRequest {
     Entity QuoteRequest {
       aggregateRoot
     }

     Service QuoteRequestService {       
       void testOp() : read-only;

       void submitRequest(@QuoteRequest request) : write [-> REQUEST_SUBMITTED];
       void rejectRequest(@QuoteRequest request): write [REQUEST_SUBMITTED -> REQUEST_REJECTED*];
       void receiveQuote(@QuoteRequest request) : write [REQUEST_SUBMITTED -> QUOTE_RECEIVED];
       void checkQuote(@QuoteRequest request) : write [QUOTE_RECEIVED -> QUOTE_REJECTED* X QUOTE_ACCEPTED X QUOTE_EXPIRED*];
       void createPolicy(@QuoteRequest request) : write [QUOTE_ACCEPTED -> POLICY_CREATED* X QUOTE_EXPIRED*];
     }

     enum RequestState {
       aggregateLifecycle
       REQUEST_SUBMITTED, QUOTE_RECEIVED, REQUEST_REJECTED, QUOTE_ACCEPTED, QUOTE_REJECTED, QUOTE_EXPIRED, POLICY_CREATED
     }
   }

   Aggregate AnotherAggregateThatMustBeIgnored {
     enum States {
       REQUEST_SUBMITTED, QUOTE_RECEIVED, REQUEST_REJECTED, QUOTE_ACCEPTED, QUOTE_REJECTED, QUOTE_EXPIRED, POLICY_CREATED
     }
   }
 }

Leads to this output: image