5

Our team have been debating approaches to integrating external or third party systems when using DDD. The literature is extensive, but sometimes contradictory. Just like a UL helps us better understand and communicate about the domain, we wanted to do a better job of defining the different approaches, and when we might use each? We are not experts, so would be interested in any insights or feedback the community might have, and confirmation we are on the right track.

When integrating with a third-party technology, we identified three different approaches we have used in the past: Adapters (specifically in regards to the Ports & Adapters Pattern), Anti-Corruption Layer and Bounded Contexts.

Acknowledging that there is overlap between each concept, we defined the following team guidelines:

  • An external system is always a separate bounded context — by its nature, the solution will use a different language to that of our core domain.

When deciding how to integrate, use the following guidance:

  • Adapter: When the technology or interface with the external or third-party system is relatively stable, and any data translation required is minimal, or automated, use a basic port and adapter. If the service is integral to the domain model, provide an interface in the domain (as a domain service). Otherwise call directly from the Application layer. This is analogous to what is sometimes referred to as the infrastructure layer. Also referred to as a gateway. Examples include Repositories, Payment Gateways etc

  • Anti-corruption Layer: If the translation required is more complex in nature, or there is a high level of impedance between your context, and the third-party service, implement an ACL in your bounded context. This will include Adapter(s), and specialised Translation services for performing the complicated data transpositions needed. The ACL may provide a facade to set of more complex services provided by the external system. All communication with the ACL happens in the language of the bounded context. The ACL should limit itself to data translations.

  • Bounded Contexts: If you are looking to expand on the functionality of the third party service then create your own bounded context that wraps the external system, and adds to the feature set. Communication with this bounded context can still happen via an adapter or ACL. Or integration may now be achieved through messaging — your new bounded context can have its own adapter for publishing and consuming messages to and from other contexts.

Does anyone have any constructive feedback or critical ideas that they think would help improve our definitions. Or spots something that is incorrect or problematic?

Steven
  • 171
  • 7

1 Answers1

2

These all are not wrong, and have some value, but I feel you're going at it with a wrong attitude.

The domain doesn't care whether something is external to the application or not. Separating "external" things from internal things is an arbitrary decision. It is a technical separation and not inherent to the domain.

An example: Let's say we're writing a middleware software for banking, and we want to enable a functionality to freeze all credit cards of the customer. I would model this thusly:

public interface Customer {
   void freezeAllCreditCards();
}

Now do we care from the perspective of the domain whether the implementation is in-memory, executes an SQL statement, does a SOAP request to a third-party system, or even all of them? I would argue we don't care.

So the guideline should be: Model your domain! Regardless of whether parts of it are external or not, because that should be implementation detail.

Robert Bräutigam
  • 11,473
  • 1
  • 17
  • 36
  • A domain model is a logical abstraction of the functional requirements of a system. The purpose of which is to *isolate* these requirements from everything else (so no dependencies on anything other than the domain). This not only makes it easy to test, but also that the only reason it would change is because the requirements have changed. So by definition we do care how `freezeAllCreditCards` is implemented - it must occur in-memory. If our method is talking to external systems then it would be better understood to be part of our "service" layer. – user3347715 Jul 13 '20 at 20:03
  • @king-side-slide Talking to an external system _is_ a requirement of the system. – Robert Bräutigam Jul 13 '20 at 21:08
  • There are many requirements in a system that are outside the purview of DDD. The domain is just one piece of a system. It is special in that it exists to model *your* problems. This can *always* be achieved without I/O (remember the domain model is a *logical* abstraction). I'm certainly not taking the opinion that OP shouldn't implement the requirements of their system. The question is about "how" that can be achieved idiomatic to DDD. Your answer above suggests simply injecting whatever dependencies are necessary into the domain. This defeats the purpose of the exercise entirely. – user3347715 Jul 14 '20 at 00:33
  • @king-side-slide I've read the blue book and didn't come away with that interpretation, but I've heard it many times. Seems illogical to me, because why would the business care if some _business function_ is implemented partly outside the application. But I might be wrong concerning DDD. If DDD is what you say it is, I'm definitely not onboard with it. – Robert Bräutigam Jul 14 '20 at 07:07
  • DDD isn't a good fit for every kind of problem! The benefits of the exercise are best-realized for systems where the bulk of the *logic* is contained within the system itself. This means applications that either don't contain very much logic (CRUD) or those that rely heavily on 3rd party services probably don't need the added overhead. The former is mostly data transfer and the latter is mostly glue. This isn't to say either of the above couldn't benefit from the perspective that DDD seeks to provide, just that the benefit is slight. – user3347715 Jul 14 '20 at 15:07
  • For example, let us model `Customer::freezeCreditCards` both ways. A DDD approach would 1. Load the `Customer` aggregate. 2. Invoke our method which likely loops over the aggregate's internal collection of `CreditCard` and flips a `frozen` bit. 3. (This is where OP's question comes in - How do we propagate that change to external systems?) We save our version of the modified `Customer` aggregate and inform some external system a change is necessary (there a number of ways to model this in a way to preserve varying levels of consistency). – user3347715 Jul 14 '20 at 15:07
  • A service-oriented approach of the above is about the same except it combines steps 2 and 3. This is not ideal. In terms of flow of control, the DDD version isolates the domain logic to step 2. The service-oriented approach has the FOC going back and forth between the domain and the "outer" layers. Now imagine how both FOCs would look when super-imposed on the hexagonal architecture. The former is "into the core and back out" while the latter "oscillates" between the core and periphery twice. This is an indication of an inverted dependency structure. – user3347715 Jul 14 '20 at 15:07
  • I apologize for the length! I tried to keep this succinct – user3347715 Jul 14 '20 at 15:08
  • Thanks for the response and conversation! Agree that "model your domain" should be a principal guideline. By the same token, we do need to implement the model, and DDD certainly has an opinion on implementation details. Repositories, ACLs, etc are all part of the DDD secret sauce, providing guidance on how to best protect your domain from technical details. It's in that vein that I propose my question. Full agreement these are technical considerations. Having made further progress, I feel fairly confident these are useful guidelines for our team to follow but welcome any feedback. – Steven Aug 05 '20 at 13:42