Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
peholmst committed Sep 25, 2024
1 parent 8cbcb5b commit ade5a05
Showing 1 changed file with 52 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ image::images/application-services.png[The presentation layer calls three applic

In code, the services are Spring Beans, and the components Java packages.

Services should have _high cohesion_. This means that you all the methods in your service should relate to the same thing. For example, a `PaymentService` could look like this:
Services should have _high cohesion_. This means that all the methods in your service should relate to the same thing. For example, all methods in this `PaymentService` are directly related to the business activity of handling payments:

[source,java]
----
Expand All @@ -23,8 +23,6 @@ public interface PaymentService {
}
----

All the methods in this service are directly related to the business activity of handling payments.

Application services need not be related to business activities to be highly cohesive. They could also be related to a specific view in the user interface, or even a specific user interface component.

For example, if you are using <<{articles}/components/charts#,Vaadin Charts>>, it is easier if the data returned by your service can be directly converted into <<{articles}/components/charts/data#,chart data>>. If you are building a payment summary view, you might be tempted to add such a method to the `PaymentService`.
Expand Down Expand Up @@ -63,7 +61,7 @@ public class PaymentService {

The advantage with this approach is that you have one Java file less to maintain. Both approaches work fine with Vaadin.

== Entities vs DTO:s
== Input and Output

Application services often need to communicate with <<{articles}/building-apps/application-layer/persistence/repositories#,repositories>> to fetch and store data. They also need to pass this data to the presentation layer. For this, there are two options: pass the entities directly, or pass Data Transfer Objects (DTO:s). Both have their own pros and cons.

Expand Down Expand Up @@ -103,7 +101,56 @@ In both of these cases, the user interface and the entities are likely to change

=== Data Transfer Objects

// TODO Continue here
Some times, it is not a good idea for the application services to return the entities themselves.

For instance, the domain model may contain business logic that must be called within some context that is not available in the presentation layer. It might require access to other services, or run inside a transaction.

In other cases, the user interface may need only a subset of the data stored inside a single entity, or a combination of data from multiple entities. Fetching and returning the full entities would be a waste of computing resources.

You may also have a situation where the domain model and user interface are changing independently of each other. For example, the domain model may have to be adjusted every year due to government regulations while the user interface remains more or less the same.

In this case, the application services should accept DTO:s as input, and return DTO:s as output. The entities should no longer be part of the application layer API.

This adds another responsibility to the application service: mapping between entities and DTO:s.

If you are using <<{articles}/building-apps/application-layer/persistence/repositories#query-objects,query objects>>, you can do the mapping in them by returning their DTO:s directly. In this case, the query object DTO:s become part of the application layer API.

For storing data, your services typically have to copy data from the DTO to the entity. For example, like this:

[source,java]
----
@Service
public class CustomerCrudService {
private final CustomerRepository repository;
CustomerCrudService(CustomerRepository repository) {
this.repository = repository;
}
// In this example, CustomerForm is a Java record.
public CustomerForm save(CustomerForm customerForm) {
var entity = Optional.ofNullable(customerForm.getId())
.flatMap(repository::findById)
.orElseGet(Customer::new);
entity.setName(customerForm.name());
entity.setEmail(customerForm.email());
...
return toCustomerForm(repository.saveAndFlush(entity));
}
private CustomerForm toCustomerForm(Customer entity) {
return new CustomerForm(entity.getId(), entity.getName(), entity.getEmail(), ...);
}
}
----

When using DTO:s, you have more code to maintain. Also, some changes, like adding a new field to the application, requires more work. However, your user interface and domain model are isolated from each other, and can evolve independently.

=== Domain Payload Objects

If you are using <<{articles}/building-apps/application-layer/domain-primitives#,domain primitives>>, you can, and should, use them in your DTO:s as well. In this case, the DTO:s are called _Domain Payload Objects_ (DPO). They are used in the exact same way as DTO:s.

== Cross-Cutting Concerns

Expand Down

0 comments on commit ade5a05

Please sign in to comment.