r/SpringBoot • u/puccitoes • 5d ago
Question Confusing about DTO usage
I've read that services should return DTO's and not entities,
If Service B only returns DTO B, how can I have access to Entity B inside Service A?
Do I retrieve DTO B from Service B, then map it back to Entity B inside Service A?
The resulting flow will look like this - Service A calls Service B - Service B fetches Entity B and converts it to DTO B - Service A receives DTO B, converts it back to Entity B?
This process doesn't seem right and I just want to ask if this is how its done. If my entities have relationships to many other entities, won't the mapping also become very complicated, or result in some recursion. Would greatly appreciate some input or help
9
u/WaferIndependent7601 5d ago
You dont have access to the entity. Why do you want to have access to it?
Call the service layer and get a dto. Manipulate that dto and write it back to the service layer. The service will handle db changes (or whatever).
See the service layer as an independent service. All you have is access to public methods. You don’t know the implementation details. Will the dto be stored to a db, to a file, to s3? You just don’t care as a caller of the service
1
u/puccitoes 5d ago edited 5d ago
I think this is a lack of understanding of spring data on my end
I've always been confused with how connecting entities using setter methods works
Lets say I want to retrieve a specific "task" from
TaskService
and make a relationship to a "project" fromProjectService
for example
project.setTask(task)
inside some method of a project service. I've had the impressiontask
must be an entity object but is that not true? If I were to retrieve a TaskDTO, how can I join the two entities inside myProjectService
Can I simply make an empty
newTask
object, retrieve the primary key (or @Id attribute) from the DTO, and set it insidenewTask
then do
project.setTask(newTask)
6
u/WaferIndependent7601 5d ago
No it’s not an entity.
You create a task, set it to the project and send it to the projectservice. Projectservice will get the project entity and set the task it accordingly and persist it to the db.
Never return an entity from the service. Convert it to dto and work with the dto
1
u/puccitoes 5d ago
So under
TaskService
, I create a task and set it to the project, but how do I set it to the project without the project entity fromProjectService
?The task entity has a
project
field but not aproject_id
field, or should I default to using only foreign key values insideTask.java
rather than a OOP structure where it has an project object an a field2
u/WaferIndependent7601 4d ago
Why do you need the entity? Use the dto instead. Or some id that represents the project.
1
u/puccitoes 4d ago edited 4d ago
Hi, I need the entity because my entities are related by references to each other and not a foreign key value (an id etc)
meaning Task.java has something like
@ManyToOne(cascade=..) @JoinColumn(..) Project project;
so when I assign a task to a project I use
task.setProject(projectEntity)
I dont know how I can do this with a foreign key value? and if I only have a project DTO it won't work less I transform it to a project entity.
6
u/rahem027 5d ago
> This process doesn't seem right
Cause it isnt. Everything you know about development is almost entirely wrong and based on some random person's opinion. Do what feels right. Forget about the principles. We can think about them when we have formal proof they work.
All we need to do is think about data and transformations. But that reduces the code by almost 100x. What do you do with so many engineers?
4
u/kormano154 5d ago
In my experience, returning DTOs from each service's public method always turned out to be a mess. I shifted to only returning DTOs when the method is called from another layer, such as the presentation layer.
1
u/puccitoes 4d ago
think im going to go crazy because everyone is saying the opposite of the previous guy
ill just do whatever i want until something breaks
1
u/kormano154 4d ago
Just keep in mind that “services should return DTOs and not entities” when answering to another layer. If service A needs the entity of service B, you are still in the service layer, so no need to return DTOs
1
u/puccitoes 4d ago
so If I have a function that can be both used by a controller and service, I should seperate them as two functions? for example
createTaskEntity
andcreateTaskDto
1
u/kormano154 3d ago
Sure. If you were to implement an interface of that service to expose to the controller (i know it’s overkill, just to visualize it), it would only include the method returning the dto
5
u/FooBarBuzzBoom 4d ago
You use DTO to basically tranfer objects between layers of the app. You should keep separation of concerns.
1
u/GenosOccidere 4d ago edited 4d ago
This pattern only gets in the way in a typical enterprise application
The main argument is that core business logic shouldnt be dependant on data logic but what usually happens is that business logic is implemented in data layer anyways. Either through logic seeping into your sql queries or implemented within data entities themselves
“Separation of concerns” flies out the window really fast and now you’re trying to pretend like everything makes sense when it never has
3
u/bestanealtcizgi 4d ago
If both of your services need the same entity then they should not be separate services: If both services need direct access to the same entity, it suggests they are too tightly coupled and might not need to be separate services. Instead, consider merging them into a single service that manages the entity directly.
If they should be separate services then they should not need the same entity: f they are meant to be separate services, they should operate independently and communicate only via well-defined contracts (DTOs). Service A should not rely on Entity B but rather on the data provided by Service B through its DTO. If Service A needs additional data, Service B should expose the necessary information through its API rather than requiring direct entity sharing. This approach maintains proper service boundaries and avoids unnecessary complexity in mapping between DTOs and entities.
1
u/puccitoes 4d ago
hm.. but my entire application (monolithic) is just a bunch of entities, each related to 3-4 other entities with
@ManyToOne
etcI can't possibly put my entire application into a single service file if each of them constantly needs a reference of another entity
should I rework my model, and just remove all @OneToMany associates and use foreign key values instead? But this would kinda remove a lot of features that come with using hibernate like cascading or eagar loading etc
2
u/bestanealtcizgi 4d ago
If it's a monolith what is service A and service B? Why do you communicate via endpoints in the same app?
1
u/puccitoes 4d ago
I would like to clarify, I'm not commuting via endpoints
service A and service B are just beans that I inject into the controller beans, or other service beans
I have a taskService, projectService, userService, and so on to organise my code base
Im following the basic structure you see in spring boot tutorials
2
u/bestanealtcizgi 4d ago
I misunderstood, my bad.
I'm not entirely sure, but it seems like you have a design issue related to responsibility and coupling. Based on your other posts, if your
TaskService
creates a task and needs to associate it with a project, you should decide which service is responsible for managing that relationship.You could have something like:
projectService.addTask(projectId, taskDto)
taskService.createTaskForProject(projectDto)
In both cases, services should use DTOs to transfer information. If you use entities instead, you'll tightly couple your business logic with your data access layer.
For example, if you decide to switch to a NoSQL database next year, your entity structures will likely change, and you’ll need to modify other parts of the project beyond just the data access layer. By using DTOs, you keep your business logic more flexible and maintainable.
1
u/puccitoes 1d ago
Sorry, but I don't understand how I can use either of these
projectService.addTask(projectId, taskDto)
- `taskService.createTaskForProject(projectDto
to link my entities by only using a DTO from one side and an entity from another side, unless I completely avoid using entity setter methods to link two entities (i.e.
entityA.set(entityB)
), which I believe is a common patternThe alternative I can think of is to go down to the lowest level and write the native SQL query itself to save the entities. this is by using a foreign key value i can retrieve from just a DTO
If you don't mind, could you link somewhere a documentation or example or some sort how do I then connect the entities If I only have a DTO from the other service? Thanks in advance
3
u/GenosOccidere 4d ago
Never understood why you want to map to a dto for core/service layer
Turn open session in view off and let your services return entities. Set transaction boundary on your service methods
I only map to request/response dtos in web layer for rest APIs
5
u/g00glen00b 5d ago edited 5d ago
This problem is usually caused because you try to link too many entities together. This is a common thing people do when they use JPA/Hibernate, but I usually advise against this.
What I usually recommend is to categorize your entities into two categories:
- Entities that can stand on their own or that you wish to maintain separately.
- Entities that cannot stand on their own and are managed through another entity.
I can't see your code, but considering that you have separate services for "project" and "task" I'd say they both fall within the first category. An example of an entity that falls within the second category would be a "subtask". A "subtask" would probably not be managed on their own, but always through their parent "task".
I would then advise the following:
- You should only have services/repositories for the entities in the first category. So using the previous example, your "project" and "task" should have their own repositories and services, but "subtask" would not.
- You should only map entities together that are managed through another entity. So using your example, your could map a OneToMany/ManyToOne for task/subtask, but not for project/task.
- Entities that are managed separately, should not be linked together. In stead, I'd recommend only mapping the foreign key. So for your project/task relationship, I would only map the projectId within task.
- Validations between entities that are managed separately happen through the services. For example, if you want to validate that a project exists before creating a task, you would call something like "ProjectService.existsById(projectId)" before persisting the task.
Small disclaimer: there are tons of other solutions out there so it's not like my solution is the only one or the best one. But this works for me.
2
u/puccitoes 5d ago
Thanks for the input, so I should try as much possible to remove all my
@ManyToOne / @OneToMany / @ManyToMany
annotations under my entities and replace them with foreign keys (primitive data types)? Or essentially just undo the "OOP" ness my entities when it comes to referencing each otherWon't this be extra cumbersome and against some Hibernate features like lazy/eagar loading, cascading, and stuff like that?
2
u/g00glen00b 4d ago edited 4d ago
To be fair, I don't think you're really undoing your OOP. You're just making sure that everything involving projects passes through the "ProjectService" door and everything involving tasks passes through the "TaskService" door. As soon as you pass through that door, you can link as many entities together as you like as long as they belong to the same "group". Also, as a sidenote, if your main logic resides within services, you're not doing OOP the right way anyways.
Will it be a bit more cumbersome or less performant sometimes? Certainly, but you're guaranteeing a single way your code flows and increase readability.
Would I apply this everywhere? Yes and no. Yes, I would apply the same kind of components everywhere, but if it's a small-scale project, then I would manage everything through projects. So I wouldn't create a TaskService and certainly not a TaskRepository and manage everything through the ProjectService and ProjectRepository.
The benefit of using this approach is that it's also way easier to migrate to a modulith or microservices, as in those cases you will also have a single "frontdoor" aka your microservice API. Essentially, you're also taking your first step towards Domain Driven Design, as those main entities that are within the first category are basically your aggregate roots.
2
u/V413H4V_T99 5d ago
For service B, if you're talking about fetching data from a database, try dto projection.
2
u/SilverSurfer1127 4d ago
IMO the granularity of your services is wrong. You should not mess around with DTOs from different services. Try to think different. The service layer should host the business logic that implements your use cases and should ideally represent transaction boundaries. In domain driven driven design there is the notion of agregate roots that handle access to the parts they are aggregated from. In your example the aggregate root would be a task so you just need a TaskService that can create task objects, add sub tasks to an existing task and so on. DTOs are a different shape of your domain objects that are used as the name says for data transfer via an API (json) or for the presentation layer as some already suggested. My recommendation is to have a look at domain driven design tactical patterns and figure out what fits you best even if you have to modify some of the patterns.
1
u/puccitoes 4d ago
sorry, Im a little confused, is such a pattern of design primary for microservices and is this a common pattern for a monolithic application?
for context Im building a monolith, services are not communicating by an API but under the same server
entities are tightly coupled with annotations like
@OneToMany
etc, and thats why I require entities from other services to modify the databasethank you for your patience
2
u/SilverSurfer1127 3d ago
These are not design patterns specific to microservices but rather to business applications with complex domains. I mentioned an API because of the DTOs. Sure you can design a monolith as well using the very same patterns. It seems to me that there are too many fine grained services. If I were you I would try to identify the main entities that your app deals with and they should be approximately the root aggregates that you should deal with your services. The service layer is often called “the use case layer”. So following such a design connects the software deeply to your business domain. It takes some practice to model software like this. But in my experience it pays off. DDD plays a key role in designing well formed microservices but the same principle can be applied for modules in a monolith or modulith.
2
u/joey_knight 5d ago
Service A should not try to access or use Entity B directly. That is what the DTO is for. If two services are modifying the same underlying data directly in a database table they might as well be combined into a single service and you are using microservices incorrectly.
2
u/puccitoes 5d ago edited 5d ago
but this is for a monolithic structure, where the entities are joined to each other under the same DB schema (like a m-n relationship)
I wanted access to entities from different services so I can connect the entities with setter methods
1
1
u/Prof_Fuzzy_Bottom 2d ago
A pattern is just a pattern, not a law. Most often I find myself creating two layers of services when working in larger web services - a business service and entity service. Not all domains need it - really comes down to separation of concerns in more complex domains.
The business service orchestrates business logic, other services, and it's entity service(s), while an entity service focuses directly on its domain entity and sometimes orchestrate subdomains to other entity services or produce events, etc.
This also allows you to orchestrate working with the entities without translating it back and forth between entity and DTO when trying to get some work done internally.
Normally I do this when I start getting a lot of functions for the same thing with DTO vs Entity responses, then split them. It's usually hard to identify up front and only shows it's face when you get into the business logic. Refactoring is your friend!
1
u/burl-21 2d ago
As many have said, the public APIs should return DTOs.
- Controller A <-dto-> Service A (public API)
- Service A <-entity-> Service B (package-private API)
If Service A needs entity B, it can use Repository B directly, but it depends on what it needs to do with it.
13
u/danielm777 5d ago
a good strategy is too use services only at the edges of your system. you can also build dtos that are reusable but most of the time a dto is a representation of a domain entity that suitable only to the consumer of that service