Algebraic Data Types, Domains and Services with Julia
Constantin Gonciulea
Technology Executive | Managing Director | Technology Fellow | Product-Centric & Science-Driven Engineering
I finally got some time to play with the Julia language while teaching Domain Driven Design based on Algebraic Data Types. I talked about this topic in a previous article in the larger context of "Sums and Products Everywhere". I will self-plagiarize myself by copying the description of these concepts here.
Domains and APIs are defined using sums and products:
- Sums express single-dimensional exclusive choices (like commands and events)
- Products express multi-dimensional properties or choices that are independent up to validation rules (like entities and value objects)
In the old article I used the simple example of a candy machine dispenser from the excellent "Functional Programming in Scala" book:
I ported the example to Java with an extra library for pattern matching. I will now show a Julia version. Using Julia is definitely a better experience than Java, on par with the Go version, but not as natural as the Scala one, in terms of functional programming. There are libraries for pattern matching in Julia, but I wanted to first see what it feels like to just use the standard language.
The Domain Implementation
Julia has good support for sums (both enums and "sealed traits" Ã la Scala) and products (structs). Here are the inputs (typically commands and queries, think CQRS) and the events of the candy machine dispenser:
The state contains the number of candies, coins and locked flag that allows dispensing a piece of candy or accepting a coin. Note that "methods" are written as functions in Julia:
The Service Logic Implementation
The servicing pattern is shown in the diagram below, a stateful entity is updated by an input, with an event being recorded (event sourcing):
The code is implementing the logic with the signature above:
The sequence of if statements can be replaced by pattern matching (this is where Scala shines), as previously mentioned. Tuples are great in this context.
The Service Endpoint
The service logic above is independent of the transport method (HTTP, binary, etc) and the interaction (web, mobile, text, voice). However, the endpoints make the connection between the service logic and the interaction part of the flow.
Note the type annotations, which are most of the time inferred, but for readability they should be required in some places. Also note the abstraction of the interaction (think client apps) layer.
The Rest: Logging, Interaction, Etc.
In this example the interaction occurs through the command line, and logging prints to the console, but in general these concerns should be designed to be swappable as much as possible.
Running The Endpoint
This is a sample session that shows some logic validation as well. Being event-driven, the logic can be easily tested using scripts with sequences of events.
Conclusions
Julia is a great language, easy to learn and use, not only for data science, but even for general purpose applications. The ecosystem is not, of course, anywhere close to the one in Java for these applications, but it is definitely growing. I haven't got into generics in the few hours I spent with Julia, but from what I have read they are good enough.
As far as the example used here, the domain is very simple, which is the reason it is used for teaching in the first place. However, all layers I am showing are required for applications that need to be maintainable and evolve. I would also give distributed programming a try, so Kubernetes is next on my list, since I feel like I need to get more hands-on with it anyway.
Integration Specialist at CSC - IT Center for Science
3 年That long list of if-elseif statements is very unidiomatic Julia, since you're already representing Input by different subtypes. You should strive to replace the whole thing by multiple separate versions of the serviceLogic function, which dispatch based on the concrete subtype of Input that the first parameter has, like: serviceLogic(input::Exit, state) = (state=state, event=Exited()) function serviceLogic(input::Coin, state) if state.locked return (state = State(false, state.candies, state.coins + 1), event = CoinReceived()) else return (state=state, event=InputIgnored()) end end …and so on. Then whenever you call serviceLogic, the version of the function for that particular type of Input is actually called. Every time you see yourself checking equality with a singleton type value, or doing something like "if typeof(foo) == …", you should probably actually be dispatching your function based on the type instead.
Quantitative Developer
4 å¹´Julia does not need Scala-like pattern matching because it has macros (e.g. see Match.jl).
Very nice. Related?https://youtu.be/w1WMykh7AxA
CIO Advisory Partner | Kyndryl Global Quantum Services & Consulting Leader | CTO | Technology Strategy | Corporate Strategy Innovation Selection Committee Member |AI & ML
4 年If the NY Federal Reserve is using it, can’t be half bad! The Federal Reserve Bank of New York uses Julia to: Estimate models 10x faster Complete 'solve' test 11x faster Reduce number of lines of code in half, saving time, increasing readability and reducing errors https://juliacomputing.com/case-studies/ny-fed/