7

I have a RESTful API service that has three layers: Application/Domain, Infrastructure, and Presentation.

Application/Domain contain my interfaces and models. I currently have three different types of models:

  1. DTO - These are the models my controllers return to the client as well as the models that are passed around all the layers of my application.
  2. POCO - Domain model that has an instance of the corresponding DTO, it has business rules/validations.
  3. Entities - Persistence models that mirror database objects.

Now, if my RESTful API makes a request to another API service and the request requires both a body and response, I would want to create models for both the body and the response right? What would this kind of model be called and which layer would I put it in?

I guess it would have to go in the Application/Domain layer because that's where the interface for my third party API client would also be. But what would I call these models? Are they entities? Are they DTOs?

Hmmm...I guess they could be considered entities. An entity is a model that represents the data in the database and I guess the third party API could also be considered a database of sorts...

I was initially calling the services that connect with dbs repositories and calling services that connect with other APIs ApiClients. But they really are just facade services. So I guess they are the same?

What do you guys think?

2 Answers 2

8

I guess it would have to go in the Application/Domain layer because that's where the interface for my third party API client would also be.

A third party API is, by definition, not part of your current application domain. It is an external service, after all.


Hmmm...I guess they could be considered entities. An entity is a model that represents the data in the database and I guess the third party API could also be considered a database of sorts...

I am sidestepping your "what should I call it?" question because naming isn't quite the main thing to focus on here, but your comment does hit a nail on the head here: external apis are functionally indistinguishable from a database.

In essence, whether you connect to a database server or a web backend which serves data is an irrelevant distinction in terms of how to categorize this logic in your codebase.

As it stands right now, the code calling the API belongs in Infrastructure, just like where your database logic resides.
That being said, depending on size and complexity, you may want to start subdividing the Infrastructure project into separate projects, if that makes more sense to you. In software development, it's always possible that a particular use case may warrant being broken down into smaller parts further.


However, that only established where you put the services that call the API. But where do you put the models for the in/output of your service?

It somewhat depends on whether you are using inverted dependencies or not. If you aren't, then the models can live together with the service in the Infrastructure layer, since your Application will depend on Infrastructure and have access to all of its services and models at the same time.

However, if you're using inverted dependencies, that means that the Application (or Domain) layer defines the service's interface, which needs access to the in/output models, which means that you can't just plop those models in Infrastructure. In this case, they belong to the service's contract, and should be kept closeby to where you store the service interface itself. I personally would put them in the same directory, since they tend to uniquely belong to each other.

That being said, when working on professional (enterprise-grade) projects, I would strongly urge you to separate the interface models from the actual external service models, as a matter of keeping the external service sufficiently abstracted. This ensures that any changes made to the external service (and thus needing to be made to the external service models) don't necessarily have to cause you to change your interface contract itself, as long as you can simply update the mapping between the external and internal models to remain compatible.

Which means:

  • For normal dependencies:
    • One internal (i.e. assembly-private) model in Infrastructure for the service logic
    • One public model in Infrastructure to be consumed by its dependents (i.e. Application)
    • The service class in Infrastructure will map from one to the other as it needs it.
  • For inverted dependencies:
    • One model with the service interface in Application (just like before)
    • One internal (i.e. assembly-private) model in Infrastructure for the service logic
    • The service class in Infrastructure will map from one to the other as it needs it.
14
  • What if, in the inverted dependencies case, the service to be abstracted is very large? Let's say I'm consuming a 3rd party that has a ton of functionality, thus a huge API surface, considering classes, functions, input/output types. Should I abstract all of that into my own interface that lives in an inner layer? It feels like my abstraction will be extremely brittle and tied together with the 3rd party. Aug 22, 2023 at 19:15
  • @RafaelEyng: On the face of it you raise a valid point, but you have subtly picked two conflicting stances. If your persistence layer is not the one defining this interface (= the inverted dependencies case), then the interface (and its models) should not be designed based on persistence considerations (which includes knowing what the external API provides and contains). You would be designing the interface and the models based on what your application needs, not based on what the API offers.
    – Flater
    Aug 22, 2023 at 23:10
  • @RafaelEyng: If your API added a completely new thing, and you want to use it, it is inevitable that you must redevelop. However, if the API changes something and you don't want to change your business logic, then all you have to do is rewrite the Infrastructure mapping in a way that the new API maps to your original model. You can't avoid needing to make changes to Infrastructure when your external components go through breaking changes; but you can contain the redevelopment to the Infrastructure project and nowhere else in your codebase - that's the purpose of designing these interfaces.
    – Flater
    Aug 22, 2023 at 23:13
  • Thanks, what you said makes sense. I'm still unsure about my case, with Stripe. While I want to have general payment use cases that can call some PaymentRepository or PaymentProvider (implemented at the adapters layer?) that can abstract Stripe away, I'm not sure what to do with my need of having Stripe-specific webhooks. At some point in the webhook flow I am able to make it webhook fall into the general case the PaymentProvider implements, but before that, I need the webhook to have access to Stripe-specific calls and types. So that leaves me clueless about where to put the webhook (...) Aug 23, 2023 at 0:02
  • 1
    @RafaelEyng: Consider what we're talking about now, and what the actual question that was being asked. This is a generalized question, but you repeatedly push back on provided advice by pointing at reasons related to your specific use case. This does not match. You need to analyze whether a generalized answer is actually applicable to your scenario; which requires a level of deep functional and technical analysis that simply cannot be done over StackExchange's intentionally restrictive Q&A format.
    – Flater
    Aug 24, 2023 at 0:18
-1

The weird thing is, I can't tell the difference between any of your particular Objects, be they DTO, POCO, or Entity.

A DTO is a POCO. Its a data structure containing data. It is the reason why you can send them across a network.

An Entity on the other hand is business logic. Business logic isn't a data structure. It may encapsulate data, in which case why is that being serialised to a repository? It may manipulate data, in which case that Data is in another DTO/POCO.

I think its more accurate to say this:

  • You have DTO objects (just plain old data usually a bunch of named strings and scopes) written in terms of your provided interface, and third-party interfaces. Their job is to serve as the marshalling ground for requests and responses over any given IO system: The API, Third Party Services, The Database, UI, etc...
  • You have Domain objects (just Plain old Data usually in higher level data abstractions like dates, lists, users, etc...) written to support your domain. These are tailored to the algorithms and business logic employed by your own system.
  • You have Domain/Entity objects representing the system that consume, manipulate, and generate Data Structures.
  • You have Presenter objects representing the logic needed to translate between DTO and Domain objects.
  • You have Interactor/Use Case objects whose job is to map a request/response on an API from the incoming DTO through Presenters into an orchestration of Entity behaviours, Domain Data and then back out through presenters to a DTO.

You may want to look into Bob Martin's clean architecture lectures.

5
  • 1
    "An Entity on the other hand is business logic." Predominantly, "entity" is used to refer to a database model. What you're referring to (business logic) is generally called the "domain object". From a purely English perspective, you're not wrong that "entity" is very vague and pretty much literally just means "thing that exists" and could be applied to almost anything, but from a developer-centric perspective, "entity" tends to imply datalayer.
    – Flater
    May 31, 2021 at 9:15
  • At an English level yes it pretty vague, I think though it has a much more defined meaning in software. For example: ECS - Entity Component System. It probably depends on the neck of programming you come from. However I am using it coming from the domain/business logic angle. In this case I think the existence of the term ERM (Entity Relational Mapping) undermines your definition. If the Entity lives in the data layer, why is it being mapped from the data layer? Sounds more like the Entity lives above the data layer. In which case the fact that it can be serialised is irrelevant to the Entity.
    – Kain0_0
    May 31, 2021 at 9:32
  • I think you're msunderstanding ERM: "In software engineering, an ER model is commonly formed to represent things a business needs to remember in order to perform business processes. Consequently, the ER model becomes an abstract data model, that defines a data or information structure which can be implemented in a database, typically a relational database." The entities in question represent the data models for the database (which, ideally, mimic the domain objects, but that is not always feasible).
    – Flater
    May 31, 2021 at 9:54
  • @Flater. Thankyou for pointing that out. Looks like I need to spend some time reading up on this.
    – Kain0_0
    Jun 1, 2021 at 0:04
  • I can't tell the difference between any of your particular Objects - honestly, I felt like that when reading the list of 5 types of objects that follow that statement. Aug 22, 2023 at 19:21

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.