readme
This commit is contained in:
53
README.md
53
README.md
@@ -15,11 +15,12 @@ Most of the information on how to design in this way are from the following book
|
||||
To run this application you need to first run a
|
||||
|
||||
```cmd
|
||||
mvn clean install
|
||||
mvn clean
|
||||
```
|
||||
|
||||
in the root directory of this project. This will download all required dependecies and bundle your
|
||||
project properly.
|
||||
project properly. It will also auto generate some mapping classes with
|
||||
the [mapstruct](https://mapstruct.org/) plugin.
|
||||
|
||||
The database for this application is a mongo db. You either need to have one running on your system,
|
||||
or you can alternatively start the MongoDB in docker with the included docker-compose file.
|
||||
@@ -50,7 +51,42 @@ way.
|
||||
|
||||
### Application architecture
|
||||
|
||||

|
||||
This application tries to follow the design princples as described in Clean Architecture. To achieve
|
||||
key characteristics of a good architecture (Single Responsibility, Flexibility, Testability, ... ),
|
||||
one very defining lession of Robert C. Martins book is that the business logik should be at the core
|
||||
of the system.
|
||||
|
||||

|
||||
|
||||
This diagram shows where dependencies in a system should point to achieve this. How to implement
|
||||
this however, feels very abstract.
|
||||
|
||||
Alistair Cockburn took this concept and
|
||||
definied [hexagonal architecture](https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)).
|
||||
Alternatively this style is also known as the ports and adapters architecture. Perhaps more fitting,
|
||||
but certainly not as exciting sounding as a hexagon.
|
||||
|
||||

|
||||
|
||||
In the middle lives the **Domain**. This is where all the business logik is contained. Ideally this
|
||||
code is free of any framework specific annotations. This is especially great for unit testing your
|
||||
logik. No spring context, that has to boot, or any services that need to be mocked. Just plain
|
||||
normal Java, which is easy to understand and fast to execute.
|
||||
|
||||
Ecapsulating these Domain models are the **Use Cases**. These represent the features that our
|
||||
application provides. They receive commands and query / write the data to the adapters.
|
||||
|
||||
Between the Use Cases and the **Adapters** are the **Ports**. Especially the output ports are
|
||||
important to control the direction of dependencies. In Java these represent interfaces. The output
|
||||
adapters implement the output ports and therefore have to follow the contract, that the core of the
|
||||
application defines for them. The input adapters only interact with the input ports and never with
|
||||
the implementing serivces directly.
|
||||
|
||||
This clear separation of concerns greatly increases flexibiliy. If you wanted to switch from an SQL
|
||||
to NoSQL DB, you'd just have to rewrite your Persistence Adapter, but the rest of the application
|
||||
should not be affected by your change.
|
||||
|
||||

|
||||
|
||||
### Advantages of separated models on each layer in this example
|
||||
|
||||
@@ -58,7 +94,10 @@ way.
|
||||
the concern in what way to display the data to the client.
|
||||
|
||||
|
||||
- The customer can be persisted different from the domain models structure. The layout in this
|
||||
example has no benefit, but assume structuring your data in this way would give you a much needed
|
||||
performance boost. This way the concern on how to handle data persistence is independent from the
|
||||
business layer and can be handled by the persistence module.
|
||||
- The AddressType properties can be annotated with web specific annotiations to control
|
||||
serialization, without cluttering the domain.
|
||||
|
||||
|
||||
- The customer can be persisted different from the domain models structure. This way the concern on
|
||||
how to handle data persistence is independent from the business layer and can be handled by the
|
||||
persistence module.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package de.strasser.peter.hexagonal.addressvalidation;
|
||||
|
||||
import de.strasser.peter.hexagonal.application.customer.domain.Address;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.AddressValidatorAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.AddressValidatorPort;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.commands.ValidateAddressCommand;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
class AddressValidator implements AddressValidatorAdapter {
|
||||
class AddressValidator implements AddressValidatorPort {
|
||||
|
||||
@Override
|
||||
public Address validate(ValidateAddressCommand validateAddressCommand) throws InvalidAddressExc {
|
||||
|
||||
@@ -2,8 +2,8 @@ package de.strasser.peter.hexagonal.persistence;
|
||||
|
||||
import de.strasser.peter.hexagonal.application.customer.domain.Customer;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.in.QueryAllCustomersCRUD;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.LoadCustomerAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.LoadCustomerPort;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerPort;
|
||||
import de.strasser.peter.hexagonal.persistence.errors.CustomerDoesNotExistExc;
|
||||
import de.strasser.peter.hexagonal.persistence.mapper.CustomerMapper;
|
||||
import de.strasser.peter.hexagonal.persistence.model.CustomerEntity;
|
||||
@@ -19,7 +19,7 @@ import java.util.List;
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerDao
|
||||
implements SaveCustomerAdapter, LoadCustomerAdapter, QueryAllCustomersCRUD {
|
||||
implements SaveCustomerPort, LoadCustomerPort, QueryAllCustomersCRUD {
|
||||
private final CustomerRepository customerRepository;
|
||||
private final CustomerMapper customerMapper;
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.strasser.peter.hexagonal.web.dto.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public enum AddressTypeResponse {
|
||||
@JsonProperty("default")
|
||||
DEFAULT,
|
||||
@JsonProperty("shipping")
|
||||
SHIPPING,
|
||||
@JsonProperty("billing")
|
||||
BILLING;
|
||||
}
|
||||
@@ -16,6 +16,6 @@ public class CustomerResponse {
|
||||
private BigInteger id;
|
||||
private String name;
|
||||
private LocalDate birthday;
|
||||
private Map<Address.AddressType, AddressResponse> addresses;
|
||||
private Map<AddressTypeResponse, AddressResponse> addresses;
|
||||
private int age;
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package de.strasser.peter.hexagonal.web.mapper;
|
||||
|
||||
import de.strasser.peter.hexagonal.application.customer.domain.Address;
|
||||
import de.strasser.peter.hexagonal.web.dto.response.AddressResponse;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface AddressWebMapper {
|
||||
AddressResponse toResponse(Address address);
|
||||
|
||||
Map<Address.AddressType, AddressResponse> toResponse(Map<Address.AddressType, Address> addresses);
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
package de.strasser.peter.hexagonal.web.mapper;
|
||||
|
||||
import de.strasser.peter.hexagonal.application.customer.domain.Address;
|
||||
import de.strasser.peter.hexagonal.application.customer.domain.Customer;
|
||||
import de.strasser.peter.hexagonal.web.dto.response.AddressResponse;
|
||||
import de.strasser.peter.hexagonal.web.dto.response.CustomerResponse;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface CustomerWebMapper {
|
||||
|
||||
@@ -3,6 +3,6 @@ package de.strasser.peter.hexagonal.application.customer.port.out;
|
||||
import de.strasser.peter.hexagonal.application.customer.domain.Address;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.commands.ValidateAddressCommand;
|
||||
|
||||
public interface AddressValidatorAdapter {
|
||||
public interface AddressValidatorPort {
|
||||
Address validate(ValidateAddressCommand validateAddressCommand);
|
||||
}
|
||||
@@ -4,6 +4,6 @@ import de.strasser.peter.hexagonal.application.customer.domain.Customer;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public interface LoadCustomerAdapter {
|
||||
public interface LoadCustomerPort {
|
||||
Customer findById(BigInteger id);
|
||||
}
|
||||
@@ -2,6 +2,6 @@ package de.strasser.peter.hexagonal.application.customer.port.out;
|
||||
|
||||
import de.strasser.peter.hexagonal.application.customer.domain.Customer;
|
||||
|
||||
public interface SaveCustomerAdapter {
|
||||
public interface SaveCustomerPort {
|
||||
void upsert(Customer customer);
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import de.strasser.peter.hexagonal.application.customer.domain.Customer;
|
||||
import de.strasser.peter.hexagonal.application.customer.mapper.AddAddressMapper;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.in.AddAddressUseCase;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.in.commands.AddAddressCommand;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.AddressValidatorAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.LoadCustomerAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.AddressValidatorPort;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.LoadCustomerPort;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerPort;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -22,9 +22,9 @@ import java.util.Map;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
class AddressService implements AddAddressUseCase {
|
||||
private final SaveCustomerAdapter saveCustomerAdapter;
|
||||
private final AddressValidatorAdapter addressValidatorAdapter;
|
||||
private final LoadCustomerAdapter loadCustomerAdapter;
|
||||
private final SaveCustomerPort saveCustomerAdapter;
|
||||
private final AddressValidatorPort addressValidatorAdapter;
|
||||
private final LoadCustomerPort loadCustomerAdapter;
|
||||
private final AddAddressMapper addAddressMapper;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,7 +3,7 @@ package de.strasser.peter.hexagonal.application.customer.service;
|
||||
import de.strasser.peter.hexagonal.application.customer.domain.Customer;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.in.RegisterCustomerUseCase;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.in.commands.RegisterCustomerCommand;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerPort;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -16,7 +16,7 @@ import javax.validation.Valid;
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
class RegisterCustomerService implements RegisterCustomerUseCase {
|
||||
private final SaveCustomerAdapter saveUser;
|
||||
private final SaveCustomerPort saveUser;
|
||||
|
||||
@Override
|
||||
public void register(@Valid RegisterCustomerCommand registerCmd) {
|
||||
|
||||
@@ -4,9 +4,9 @@ import de.strasser.peter.hexagonal.application.customer.domain.Address;
|
||||
import de.strasser.peter.hexagonal.application.customer.domain.Customer;
|
||||
import de.strasser.peter.hexagonal.application.customer.mapper.AddAddressMapper;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.in.commands.AddAddressCommand;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.AddressValidatorAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.LoadCustomerAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.AddressValidatorPort;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.LoadCustomerPort;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerPort;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.commands.ValidateAddressCommand;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -29,9 +29,9 @@ import static org.mockito.BDDMockito.then;
|
||||
class AddressServiceTest {
|
||||
|
||||
@Autowired private AddressService sut;
|
||||
@MockBean private SaveCustomerAdapter saveCustomerAdapterMock;
|
||||
@MockBean private AddressValidatorAdapter addressValidatorAdapterMock;
|
||||
@MockBean private LoadCustomerAdapter loadCustomerAdapterMock;
|
||||
@MockBean private SaveCustomerPort saveCustomerAdapterMock;
|
||||
@MockBean private AddressValidatorPort addressValidatorAdapterMock;
|
||||
@MockBean private LoadCustomerPort loadCustomerAdapterMock;
|
||||
@SpyBean private AddAddressMapper addAddressMapperMock;
|
||||
|
||||
@Test
|
||||
|
||||
@@ -2,9 +2,9 @@ package de.strasser.peter.hexagonal.application.customer.service;
|
||||
|
||||
import de.strasser.peter.hexagonal.application.customer.mapper.AddAddressMapper;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.in.commands.RegisterCustomerCommand;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.AddressValidatorAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.LoadCustomerAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerAdapter;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.AddressValidatorPort;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.LoadCustomerPort;
|
||||
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerPort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -25,11 +25,11 @@ class RegisterCustomerServiceTest {
|
||||
private RegisterCustomerService sut;
|
||||
|
||||
@MockBean
|
||||
private SaveCustomerAdapter saveCustomerAdapterMock;
|
||||
private SaveCustomerPort saveCustomerAdapterMock;
|
||||
@MockBean
|
||||
private AddressValidatorAdapter addressValidatorAdapterMock;
|
||||
private AddressValidatorPort addressValidatorAdapterMock;
|
||||
@MockBean
|
||||
private LoadCustomerAdapter loadCustomerAdapterMock;
|
||||
private LoadCustomerPort loadCustomerAdapterMock;
|
||||
@MockBean
|
||||
private AddAddressMapper addAddressMapperMock;
|
||||
|
||||
|
||||
BIN
documentation/Hexagonal.png
Normal file
BIN
documentation/Hexagonal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
BIN
documentation/circles.png
Normal file
BIN
documentation/circles.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
documentation/dependencies.png
Normal file
BIN
documentation/dependencies.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
@@ -1 +1 @@
|
||||
<mxfile host="app.diagrams.net" modified="2021-04-11T15:02:04.080Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" etag="5IbEkqFB05nkIMoXACCY" version="14.4.9" type="device"><diagram id="gx9Nw2zk0I0ZlYx_-rDY" name="Page-1">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</diagram></mxfile>
|
||||
<mxfile host="app.diagrams.net" modified="2021-04-12T20:12:54.578Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" etag="Mklv7aI7NHlyKOI3eBcS" version="14.5.10" type="device"><diagram id="rH_L7xBtymPGQX_kqgda" name="Page-1">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</diagram></mxfile>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 102 KiB |
Reference in New Issue
Block a user