2

I'm prototyping out an application here where I'm using domain-driven design to identify the domain(s) of the application and I've ended up with something that - at least so far - feels like a fairly decent architecture where each bounded context in my domain is structured as its own hexagonal application. Data flows between each bounded context primarily using domain events as the published language, with adapters in the consuming BC translating relevant events from publishing BCs into domain specific language. To have some solid examples to use in this question, I've chosen to use the BCs Vaughn Vernon's Implementing Domain-Driven Design book where they end up identifying three separate bounded contexts:

  • Identity & access
  • Collaboration
  • Agile product management

These are not my specific bounded contexts but I chose to use these as examples here as they might be more familiar to whoever is reading this. On top of this, the plan is to implement a public RESTful API that spans the system as a whole - a unified API surface across all our internal bounded context for our entire domain. This is where I'm having a hard time finding a solution that feels just right as to where the implementation of the REST API should live and how it should integrate with my domain in a way that doesn't introduce needless coupling or a structural mess. The things I've been thinking about and evaluating so far are:

  1. Each BC has relevant HTTP handlers for its domain implemented as adapters. This is problematic for many reasons:
    • It leads too quickly to exposing our domain model over REST which I'd want to avoid. I want to set up an architecture where it's encouraged to think of these as the two separate things that they are. Relevant reading/watching for this would be this stackoverflow question, DDD & Rest - Domain-Driven APIs for the Web by Oliver Gierke and Domain-Driven Design for RESTful systems by Jim Webber.
    • Our REST API will be hypermedia-driven (HATEOAS), and every resource requested will contain information to the client about what it can do next - both where to find additional related information, and which operations it can perform on the resource it just requested. I'm worried this will introduce unnecessary coupling where an adapter in the agile product management context will have to know what URL an adapter in the collaboration context is using for its resources. As an example, when requesting a sprint resource, one of the links on that resource should be to the discussion for the sprint which is served by an adapter in a different bounded context.
  2. The REST API is its own bounded context. While this might make more sense as the REST API might have its own ubiquitous language to some extent, it also feels challenging to figure out where to draw the line here.
    • Should the API BC use application services in the upstream BCs to provide the API with the data it requires?
    • Should the API BC be as loosely coupled from the upstream BC as possible and have adapters consuming events from upstream BCs, translating them into read models that are tailored for the resources that are exposed in the API?
  3. The REST API is simply a client of the domain, and not represented as a bounded context on its own; it's simply a client of our bounded contexts, using the public API of each upstream bounded context to provide the functionality it needs. While this is fairly similar to #2, it would not necessarily be structured as a hexagonal application but instead just a semi-flat list of HTTP handlers/controllers with no architectural constraints on them outside of that.

Right now I think I'm leaning mostly towards #3, but I think I'm forcing myself there just because the other options have too many open questions with unclear answers - it might not be the ideal route to go. #2 is also an option but I struggle to decide where to draw the line on how it should integrate against its upstream BCs. #1 feels flat out wrong for the reasons I already outlined, but I wanted to include it for completeness.

What I'm looking for here is some input on the way I'm reasoning about this and things that I've missed or are just flat out wrong about. The "right choice" here is likely the one that fits our (the team that will write and maintain this) needs and requirements, and that no option is clearly superior in a general sense but it would be interesting none the less to get some thoughts and ideas around this.

1 Answer 1

2

Normally (I mean barring further requirements which may conflict) I would go for Option #1. Here is why in short:

The REST API and the "model" are naturally coupled. If you try to separate the two you will end up changing both most of the time. New model feature? Redesign/Refactor model objects? Different supported workflow through the model? For any of these you will have to modify the model, then the REST API.

Here are some points to counter your problems with it:

It leads too quickly to exposing our domain model over REST...

Seems to me you are talking about data. If your domain model is only data, then you are already setting yourself up for failure. The data is only secondary for both DDD and REST. DDD should be about modelling the behavior and functions using the ubiquitous language and REST is about creating an application through state transitions.

So you are right, the DDD "model" should not be directly exposed, but if you feel you have to do it you are probably on the wrong track.

...product management context will have to know what URL an adapter in the collaboration context is using for its resources...

If you mean that the product management context will have to know how to construct URLs for collaboration, then no. Nobody using REST should ever know how to construct URLs.

What should happen is that when creating a Sprint, the product management app should ask the forum app to create a discussion group or whatever for that Sprint. The collaboration app will then provide a suitable URL for its Web interface. So the product management will not just simply "know" the URL out of nowhere, it will receive it. There's nothing wrong with that.

The management context will have to depend on the collaboration context of course, but that is also ok. Happens on the web all the time, things like Disqus, etc.

6
  • Thanks, helpful input. So say my collaboration context yields a domain event informing that a discussion has been created and my agile pm context picks it up and sees that it needs to be associated with one of its sprints. For the agile pm context to then know the URL to that discussion, it likely needs to be part of the domain event. Domain events are immutable, meaning I can never do changes to the URL-space of the collaboration API as URLs pointing there are forever referenced in my events. It's of course possible to do this in less strict ways, I'm just curious about your thoughts on it. Commented Sep 19, 2019 at 12:33
  • 1
    I assumed the agile pm would just use the REST API of the collaboration app (would be a client of it). Therefore receiving an URL is natural there. Using events seems like an overkill, since it is more of a request-response thing, but would work. URLs should be forever, no one likes broken links :) But if you change your URL space you can use 301 (Moved Permanently), to which the proper response of the server would be to change its saved URL to the new one. Commented Sep 19, 2019 at 13:33
  • URLs should be forever, sure, but it also breaks the whole concept of hypermedia where URLs are discoverable - not static. Whether or not the URL is received from an API call or consuming an event is not that important, but if the agile pm context is to store URIs for content in the collab context (that the collab context has issued) it does violate the REST guidelines to some extent. I guess another option could be that the collab context exposes functionality to build the URL of a discussion as part of its app service and the agilepm context consumes that - but it may be too much coupling. Commented Sep 20, 2019 at 6:25
  • 1
    @TrondNordheim I don't see storing URIs as violating anything in REST, can you elaborate what you mean? In fact storing URIs is one of the expected use-cases, called bookmarks. "Discoverable" doesn't mean that you can't remember the URI once you discovered it, it just means that you don't come up with or construct the URI yourself out of nothing. Commented Sep 20, 2019 at 11:42
  • 1
    @TrondNordheim The client is not allowed to hardcode/configure/know URIs or URI patterns from "nothing" (other than some small amount of entry points). As you said the client must use controls exposed by the server to discover resources. However, URIs already discovered can be remembered, i.e. a Sprint object can remember the URI to its discussion page after it discovered it (i.e. created it). Look at the last 2 paragraphs here: ics.uci.edu/~fielding/pubs/dissertation/… Commented Sep 20, 2019 at 14:04

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