-1

Background: I'm building a prototype scientific app with the "Exploration" and "Optimisation" parts. (Other parts might be needed later.) Each part uses functional reactive programming (FRP) and has its own GUI window. "Optimisation" depends on the user's choices within "Exploration"; there's no dependency the other way. Although the language is Scala, integration with Python, and possibly R, will be needed in future.

First, I've built an "Exploration"-only app based on the "onion" (clean) architecture and DDD principles. (The app is implemented as a multi-module sbt project in Intellij; see Figure 1.) I'm happy with this individual app.

"Exploration"-only Project. "Onion modules" shaded. Omitted dependencies: Main on every module; every module on Utils

Figure 1: "Exploration"-only project with the "onion modules" shaded. Omitted dependencies: Main on every module; every module on Utils.

Problems started when adding the "Optimisation" part to the same Intellij project. In DDD terms, "Exploration" and "Optimisation" are distinct bounded contexts. Combining both in one project would require having two "onions" side-by-side. The complexity is noticeably greater than with the "Exploration"-only project.

Tried: I've looked into microservices, which seem attractive, e.g., to separate bounded contexts https://www.infoq.com/presentations/ddd-microservices-2016, but also have drawbacks https://adamdrake.com/posts/2017-05-20-enough-with-the-microservices.html; https://martinfowler.com/bliki/MicroservicePremium.html. As I've little experience with them, they're probably an overkill for this medium-size prototype, but may be useful in future.

My current idea is to split the project into four separate multi-module projects; see Figure 2. The independent "Core Project" contains all the support domains and the abstract core domain. The "Exploration" and "Optimisation" projects depend on it as an external project. The "Main" project, dependant on the other three, wires the whole application and is responsible for passing data from "Exploration" to "Optimisation".

Question: Is this solution reasonable and how could it be improved? Or is there a better solution?

Related: Several SE questions suggest using microservices, e.g. How can you separate a monolith into domain-driven libraries without duplicating interfaces and still keep dependencies simple?. There're also language-speficic questions, e.g. Microservices and shared libraries for Python.

Split project. Between-Project dependencies (dashed lines) go upwards

Figure 2: Split into 4 external projects. Between-project dependencies (dashed lines) point upwards

7
  • What are your criteria for making a decision? Commented May 7, 2018 at 16:29
  • @RobertHarvey I'd like something leading to: (1) clean, well-organised and modular code and (2) rapid development of a working prototype. If the prototype is successful, I'd like some use for its code, rather than starting from scratch. Unfortunately, I don't have enough experience and information to assess, and even to know, the alternatives. Commented May 7, 2018 at 16:59
  • Why do you need microservices? Why not just use ordinary classes? Commented May 7, 2018 at 17:05
  • @RobertHarvey I was influenced by E. Evans' talk on separating bounded contexts by using a microservice per context. But I do think microservices are likely an overkill, at least now, and this is why I put "without microservices" in the question. But I might be wrong, eg, if there's a good MS framework. In any case, would ordinary classes within a "catch-all" project be enough? Or shouldn't I at least define an sbt multi-module project per context (as per Vernon's DDD advice)? Commented May 7, 2018 at 17:26
  • 1
    I believe that the organization of your project from a file and folder perspective should not necessarily correspond to DDD sensibilities. Same with microservices. Microservices should be chosen, not because DDD needs them, but because your implementation could benefit from the features that microservices provides. Commented May 7, 2018 at 17:30

2 Answers 2

3

You touch on many topics and keywords in your question. It shows you put a lot of thought and effort into this. However, you're very imprecise and often a bit off about how things work or what they mean, which makes it very hard to accurately pinpoint precisely what the problem is.

As a quick example:

  • Your question is "how to split" whereas the problem you describe happends when you merge things. Those are opposite things. This suggest to me that the problem is in the opposite direction of where you're looking.
  • Having separate GUI windows is an implementation detail that doesn't belong on the same (high) level discussion that project structure does.
  • To the backend, it doesn't matter what the frontend does or what it looks like. For all you (the backend) care, the frontend could mash all your neat little contexts into a single form. That is unlikely to be a good idea, but the point I'm trying to get across is that it's not the backend's problem if the frontend makes some bad decisions on its own.
  • "Bounded context" does not mean "singleton core of the onion". Your onion core can have many, many bounded contexts. Using the example of webshop software: Person, Product, Order, Inventory bounded contexts co-exist within the same domain (i.e. core of the onion).
  • The separation maintained between these bounded contexts is one of self-discipline, not rigorously enforced via project dependency graph.
  • Microservices are a deployment detail and are not the driving factor for how you structure your internal logic. Microservices are much more relevant in terms of availability, independence and deployability, which are not things you've touched on in your question.
  • Future integration with other languages is irrelevant as to how you structure your current project in a single language. Other language's runtimes are considered external resources as far as your (current language) project is concerned.

I'm going to make an educated guess here, based on what I infer the underlying driving force behind your decisions and argumentations is.

If I'm spot on, the below answer should explain everything. Depending on how off I am, the below answer may only partially apply to your case. Let me know where I'm wrong and I'll correct the answer as needed.

I suspect that you're unhappy that when merging your two bounded contexts into the same domain, there is no rigorously enforced boundary between them anymore (since one could freely reference the other if it wanted to), and you are therefore looking for ways to build a concrete wall between them to ensure that they never reference each other even if they tried.
And because you're looking for ways to divide your code, you've taken a shine to microservices, which have a generally secessionist attitude towards how logical modules should interact with one another.

Am I in the right ballpark here?


Problems started when adding the "Optimisation" part to the same Intellij project. In DDD terms, "Exploration" and "Optimisation" are distinct bounded contexts. Combining both in one project would require having two "onions" side-by-side. The complexity is noticeably greater than with the "Exploration"-only project.

Merging two DDD codebases inherently means that their individual boundaries cease to exist, and they instead use shared boundaries. To describe it visually, you're thinking of your merged codebase this:

  Exploration      Optimization
 -*-*-*-*-*-*-     -*-*-*-*-*-*-

┌─────────────┐   ┌─────────────┐
│ Persistence │   │ Persistence │
└──────▲──────┘   └──────▲──────┘
       │                 │
       │                 │
┌──────┴──────┐   ┌──────┴──────┐
│   Domain    │   │   Domain    │
└──────┬──────┘   └──────┬──────┘
       │                 │
       │                 │
┌──────▼──────┐   ┌──────▼──────┐
│ Application │   │ Application │
└─────────────┘   └─────────────┘

But it should be this:

   Exploration + Optimization
 -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

┌───────────────────────────────┐
│          Persistence          │
└───────────────▲───────────────┘
                │
                │
┌───────────────┴───────────────┐
│             Domain            │
└───────────────┬───────────────┘
                │
                │
┌───────────────▼───────────────┐
│          Application          │
└───────────────────────────────┘

To use your diagrams, I would expect your merged diagram to merge their bubbles. There shouldn't be a separate "View" bubble for exploration and optimization. There should be one "View" bubble which is the combined content of those two bubbles.
When you start applying this to your diagram, you'll notice that all the bubbles of the same name merge into the same blob.

Everything from the same layer should be merged with one another, since it now all belongs to the same layer. Let go of the old boundaries, embrace the new ones (from a code structure perspective. Whether you separate them for the end user is a different story).

Merging the bubbles doesn't mean that these merged bubbles must invariably interact with one another. They often don't. If we were to observe a specific use case threaded across these layers, the entire use case would usually entirely belong to one of the old parts, i.e. it's completely Optimization or completely Exploration logic.

That's not a problem. There is no inherent requirement that code that lives in the same project are forced to interact with one another. It is very common for bounded contexts within the same domain to either never interact with one another, or only marginally interact with other bounded contexts which reference them (e.g. how an Order references the Products of a webshop, even though they're separate bounded contexts in all other regards).


Microservices have their purposes, but they're not the right solution to this particular problem. I suspect you're stumbling on microservices because you're thinking about this problem on the wrong level, leading you to stumble onto concepts that are in fact unrelated but seem relevant because they similarly promote a separation of concerns.

But using

My wife and I want to eat out at different restaurants tonight. How do we divorce so we can go our own ways?

It's not that divorce is inherently a bad idea, it's just that it's not really relevant to the discussion as to what you have for dinner tonight, making it a bad (or at the very least unjustified) idea for the current question.

But example highlights another complication here: maybe there are other unmentioned/unrelated reasons that a divorce is a good idea. Or, in other words, maybe there are other reasons for why you'd want to use microservices.
Just because I now say that you shouldn't be using them, that doesn't mean that you should never use them, it only means that it's not an appropriate solution for the specific problem discussed here.

This makes it very hard to respond to your microservice idea, because I'd run the risk of (a) implying that microservices are always a bad idea or (b) implying that there will never be a reason for you to implement microservices. Neither is the case. It's just a matter of microservices being the wrong solution to this particular problem you've brought up.

0

I see a lot of conflation in your question, so first a little background: Bounded contexts (BC) separate areas of concern in a domain model and clear up confusion in a large system. A BC is anemic(ill-defined) if you don't have a context map. Logical separation exists because you have to translate concepts in one bounded context to other concepts in another. The biggest help comes when you have to map a term in one bounded context to the same term in another bounded context.

In implementation, you'll be using dependency inversion, services and domain events to decouple your domain from your platform. GUI frameworks, Web Services and Databases are all their own domains that are separate from your business domain. It is the interaction of your business domain (problem domain) with all of these implementation domains (solution domains) that is one of the main culprits of accidental complexity in systems. This is why onion architecture is great because it puts these concerns front and center.

It sounds like you're trying to organize your code by introduce a lot of accidental complexity into your system. From your problem statement:

Problems started when adding the "Optimisation" part to the same Intellij project. In DDD terms, "Exploration" and "Optimisation" are distinct bounded contexts. Combining both in one project would require having two "onions" side-by-side. The complexity is noticeably greater than with the "Exploration"-only project.

You only need one onion. You can put as many web service entry points in a single onion as you want. Physical separation is about deployment, instances, scaling, coordinating with different teams, rolling updates, orchestration, etc. It sounds like you're "programming in the small" but worrying about "programming in the large" problems.

Think of your DDD concepts as your way of designing one small piece of your system. The onion architecture is at a larger scale that includes the entire system. You can use DDD on application (solution) domains in your system to work out complex interactions, but that's a bit off the beaten path.

I think of web services more akin to actual DDD Application Services. They are more behavioral and encapsulated. Use Command–query separation web services to expose "read-only" views of your domain model for your interfaces. Bounded contexts can be represented as sub-paths under a single web service route if you want to go that way.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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