1

I am new to DDD and I am trying to apply it with Hexagonal architecture. I have the following scenario:

  • A user aggregate
  • application service

and third party REST API that I need to call before persisting the user to the repository

So I am trying to implement it using ports and adapters, so I have an interface in my application service layer that represents the third party functionality and I have the concrete implementation of that API calling exist in infrastructure layer as the following image

enter image description here

As you know this interface should take some params and returns specific type, my question is where should I put the types of these params and returned type, should be exist in the infrastructure layer or inside the application service layer?

for example I have the following interface:

class GetSomeDataInput{
  customerId string;
  userName string;
}

class GetSomeDataResult {
  userBlocked boolean;
  userAvailable boolean;
}

interface IThirdPartyAPI {
  getSomeInfo(GetSomeDataInput input): GetSomeDataResult;
}

As you can see the method should take specific param with specific type and return also specific type, and these classes should be implemented in both the caller side which is application service and also on the receiver side which is the concrete implementation so where should I put these types in infrastructure layer or inside the application layer or put them in a shared directory that could be accessed by both

Sorry I just wrote some pseudo code to explain my question, thank you.

Andreas Hütter
  • 3,288
  • 1
  • 11
  • 19
tarek salem
  • 540
  • 1
  • 6
  • 20

2 Answers2

3

These type are part of your domain, they belong to the application service. Your naming should be independent of the external API. You should name it as what it represents from the application service perspective. Hard to tell based on your code, but something like:

class CustomerIdentifiers {
  customerId string;
  userName string;
}

class CustomerAvailability {
  userBlocked boolean;
  userAvailable boolean;
}

interface ICustomerAvailabilityChecker {
  getCurrentAvailability(CustomerIdentifiers id): CustomerAvailability;
}

You need to inject the concrete domain service implementation from infrastructure when you instantiate the application service class. At least as far as I understand DDD we are talking about a domain service here: https://badia-kharroubi.gitbooks.io/microservices-architecture/content/patterns/tactical-patterns/domain-application-infrastructure-services-pattern.html

Ports and adapters are good for the scenario, where you have multiple external domain services and you want to call all of them, like here: https://www.kennethlange.com/ports-and-adapters/ Sometimes they just raise events and there is no response or you get the responses on a different channel, not directly with sync behavior. Be aware that calling an external API takes time, so better to use async non-blocking solutions for it. Afaik. java or whatever your language is supports async - await, which would be probably good here, but it depends on your project.

inf3rno
  • 24,976
  • 11
  • 115
  • 197
  • 1
    thank you for your comment, but what about if the third party depends on another names not in my model for example the third party implementation sends to API `customer_id` not `customerId` and `user_name` not `userName`, and making the concrete implementation of the third party knows about my domain is good? or should I make it agnostic and jist receives it's params in a specific format and return specific type without knowing anything about the caller, because this relates to `infrastructure` layer which means could be used by different use cases – tarek salem Sep 03 '22 at 14:16
  • @tareksalem Params, method names, etc. must be translated. The interface must not depend on the implementation of the external service. You can even cover multiple calls with a single method. Be aware that error handling, exceptions must be covered with domain concepts too, so this is not as simple as it looks like. – inf3rno Sep 03 '22 at 17:46
  • as you can see in the mentioned diagram, the implementation of the external service is depending on the interface not the opposite. As you see the method names , params must be translated which means I need some adapters to be exist between the domain layer and concrete implementation of the external service, so where to put this adapter? and at the end this adapter should take some params and return some types that should be known by the domain layer, so where should I put these types? – tarek salem Sep 04 '22 at 12:27
  • @tareksalem In that case the adapter implements the interface and the external service is injected into the adapter. – inf3rno Sep 04 '22 at 20:06
1

Notice that your question applies to many different architectural styles (DDD, onion, hexagonal, clean, layered). The reason is that the question is actually a fundamental concept in OOP, which is the concept of Interface, and where languages like C# have created some confusion by introducing the keyword interface.

What I mean by that is that a C# interface is not the same as the concept of Interface in all these architectural styles, also known as Contract. As you found in your case, the C# interface defines only one part of the contract. The full contract also contains all the input and output types and optionally custom Exceptions.

Once you see this distinction, it is obvious that all the types used in the contract must be defined in the same place, as they are part of a single unit.

In ports and adapters, onion, etc. the full contract would belong to the Core. In a layered architecture, the full contract would belong to the Data Access Layer.

The advantage of Ports and adapters over layered is that all the names that appear in the contract belong to the Core/Domain, so they align with what domain experts will understand, making the contract a lot more readable. And the translation of these names to technical names which depend on the adapter technology is an internal concern of the adapter itself.

Francesc Castells
  • 2,692
  • 21
  • 25
  • thanks for your note, yes I noted this, the question it self is a fundamental question in OOP rather than DDD. – tarek salem Sep 04 '22 at 18:51
  • Imagine that I have a function that converts a string to int so this function takes one param as string and returns one output which is type int, when you call this function you don't need to have these types on the caller side because they are primitive types and are exist in the language itself and you can access them from anywhere, but when it comes to using some custom types like structs or objects, then you need the expected type to exist on both the caller side and the function itself, what happens if the function itself is implemented in a separate layer and the caller exist in another? – tarek salem Sep 04 '22 at 18:57
  • The same way the adapter/function references the interface that it implements, it also references the types that are part of the contract. Why do you think there is a problem? If you put things together as I explain in my answer it will work – Francesc Castells Sep 04 '22 at 19:36
  • Sorry for confusion, then you mean, if I have a domain layer and infrastructure layer and have adapter between them, then I can put the types that are returned in the adapter layer and they could be borrowed inside the domain layer? – tarek salem Sep 04 '22 at 19:41
  • No. As I try to explain in my answer, the "interface" and the "types" are all part of the same "contract" and they all go together in the same place/layer, in this case, the domain layer, so they are in the same project/package and, many times, even in the same folder. – Francesc Castells Sep 04 '22 at 20:11
  • 1
    thank you very much, accepted as the valid answer – tarek salem Sep 04 '22 at 20:19