davidmoten / state-machine

Finite state machine class generator for java, exports graphml, supports immutability!
Apache License 2.0
132 stars 14 forks source link

Questions: Reflection and PlantUML #12

Open ambition-consulting opened 3 years ago

ambition-consulting commented 3 years ago

Hi David,

I just came across this amazing library of yours and am considering using it in our next payment processing microservice.

Four question came to my mind, that I would be thankful to get your thoughts on:

Cheers and thumbs up for your holding the flag of software quality high, John

davidmoten commented 3 years ago

Hi John, glad you like it.

Does this library use reflection during run-time? If not, could it in your opinion be compiled into a native executable using GraalVM or do you foresee any problems?

I don't think reflection is used. After all there's every opportunity to avoid reflection when generating code. I haven't compiled to native executable so don't know if there are any blockers. Let me know if you find any!

Did you achieve better results with GraphML -> yed, or why did you select this semi-manual process over an automatic with PlantUML?

Automatic would be nice of course, I think the customizability and auto-positioning was better with yed. I'd be happy to look to target PlantUML as well. Wanna do a PR?

How powerful is your state chart export, and do you intend on improving it? E.g. I cannot find sub states as defined by Harel statecharts. To me, the diagram export and ES persistence are the most powerful feature of your library.

No intention at the moment. We can chat further.

How would you attempt to implement a LOCK across multiple microservices to make your request exactly-once processed, i.e. that the first request blocks the second while still processing, or returns the same result without executing twice? A database implementation would be possible when the database is shared for all microservices of the same domain, but has it been implemented yet? Or would you use a IMDG e.g. Hazelcast instead for this use case?

Good ol' distributed locks, the scalability bottleneck. One solution is to use a persisted or in-memory datastore that offers conditional updates so you can do compare-and-swap loops. AWS has DynamoDB for example. Hazelcast would do the job too though not sure what your options are for a consensus strategy to handle node availability issues.

davidmoten commented 3 years ago

I just had a look at PlantUML again, looks really good!

ambition-consulting commented 3 years ago

I will see if I can find on sending a PR for PlantUML - it's something very nice to have, but not mandatory for my current project.

Also, I am wondering how you feel about introducing phantom typing to your code generation? Currently state transitions are just typed, but the compiler does not help during compile time with the identification of valid follow-up states, right? This only gets decided during run-time, which is too late to be of efficient use for a developer.

davidmoten commented 3 years ago

I've already added PlantUML in #13.

Can you give me code examples of what you want with phantom typing?

ambition-consulting commented 3 years ago

Nice job on PlantUml, great! :-)

I want to get compile time errors on invalid state changes. Taken from https://stackoverflow.com/questions/27875970/is-this-an-accurate-conversion-of-phantom-types-in-java:

interface Fueled {}
interface NoFuel {}
interface HasO2 {}
interface NoO2 {}

class Rocket<Fuel, O2> {}

public class PhantomJava {
    public static Rocket<NoFuel, NoO2> createRocket() { return new Rocket<NoFuel, NoO2>(); }    
    public static void launch(Rocket<Fueled, HasO2> rocket) { System.out.println("Launch"); }   
    private static <Fuel> Rocket<Fuel, HasO2> addO2(Rocket<Fuel, NoO2> rocket) { return new Rocket<Fuel, HasO2>(); }    
    private static <O2> Rocket<Fueled, O2> addFuel(Rocket<NoFuel, O2> rocket) { return new Rocket<Fueled, O2>(); }

    public static void main(String args[]) {
        // test1
        launch(addFuel(addO2(createRocket())));
        // test2
        launch(addO2(addFuel(createRocket())));
        // test3 - won't compile - no Fuel
        // launch(addO2(createRocket()));
        // test4 - won't compile - no O2
        // launch(addFuel(createRocket()));
        // test5 - won't compile - can't add fuel twice
        // launch(addFuel(addO2(addFuel(createRocket()))));
    }
}

In the example above, the phantom types are used to guarantee, that only a rocket which previously got O2 and Fuel added, will be allowed to execute the launch method - otherwise the compiler will complain. Obviously this is not the intended use of Generics, but it allows to get an error on invalid state transitions sooner: The sooner we get an error, the better - compile time being the optimum.

Here is another nice example on phantom types: http://gabrielsw.blogspot.com/2012/09/phantom-types-in-java.html

ambition-consulting commented 3 years ago

BTW PlantUML diagrams can also be generated by Maven, e.g. using the asciidoc plugin: https://asciidoctor.org/docs/asciidoctor-diagram/

This is what I am actually trying to achieve: Generate PlantUml from StateMachine code, and use that to automatically generate a diagram for my documentation using: https://doctoolchain.github.io/docToolchain/

If you like, I can add a PR this year that generates asciidoc snippets, that can then be included in a documentation, linking to the code and including the PlantUML for diagram generation.

Ultimately, my goal is to not only generate a static state diagram, but also allow them to be generated during runtime about the running stateful object, e.g. to add them to the logs and make those (error) logs more understandable to people not knowing the stateful object, but understanding state diagrams and where the states link to in our code. This could be as simple as drawing the already visited states yellow and the current state red.

davidmoten commented 3 years ago

automatic (offline preferrably) generation of diagrams would be great, thanks for the tips. PR welcome.

Re phantom types, remember we are in a situation where events are thrown at a state machine which brings about transitions. Methods are not called like in the rocket example. In the rocket example, throwing a Launch event at the state machine will not do anything if the state is not ready to launch. This do-nothing behaviour is often desirable as all sorts of spurious events may be occuring.

You seem to be interested in an alternative representation of a state machine that is not event based but rather method based (with strong typing)? Something like that could be generated as well but it is a different beastie.

ambition-consulting commented 3 years ago

I am not yet decided on events vs methods - will sleep over it. As for documentation, if you enable github pages, I could have the PR not only generate ascidocs, but also actually use them with a doctoolchain generated html page - similar to the one linked above, where this project is basically eating theor their "self-generated html page" - dog food.