├── .github
└── workflows
│ ├── sample-ci-jobs.yml
│ └── sample-ci-steps.yml
├── .gitignore
├── Dockerfile
├── README.md
├── api
├── pom.xml
└── src
│ └── main
│ └── java
│ └── br
│ └── com
│ └── mtz
│ └── cleanarch
│ └── api
│ ├── CustomerApiExceptionHandler.java
│ └── controller
│ └── CustomerController.java
├── application
├── pom.xml
└── src
│ └── main
│ └── java
│ └── br
│ └── com
│ └── mtz
│ └── cleanarch
│ └── application
│ ├── CreateCustomerInteractor.java
│ ├── DeleteCustomerInteractor.java
│ ├── FindAllCustomersInteractor.java
│ ├── FindCustomerInteractor.java
│ ├── UpdateCustomerInteractor.java
│ ├── impl
│ ├── CreateCustomerInteractorImpl.java
│ ├── DeleteCustomerInteractorImpl.java
│ ├── FindAllCustomersInteractorImpl.java
│ ├── FindCustomerInteractorImpl.java
│ └── UpdateCustomerInteractorImpl.java
│ ├── request
│ ├── CreateCustomerRequest.java
│ └── UpdateCustomerRequest.java
│ └── response
│ ├── CustomerResponse.java
│ └── PageResponse.java
├── data
├── postman
│ └── Clean Arch Java Demo.postman_collection.json
└── stubby
│ └── integrations.yml
├── docker-compose.yml
├── domain
├── pom.xml
└── src
│ └── main
│ └── java
│ └── br
│ └── com
│ └── mtz
│ └── cleanarch
│ └── domain
│ ├── Customer.java
│ ├── Page.java
│ ├── PageRequest.java
│ ├── exception
│ ├── BusinessException.java
│ └── NotFoundException.java
│ ├── repository
│ └── CustomerRepository.java
│ └── service
│ └── CustomerScoreService.java
├── infrastructure
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── br
│ │ └── com
│ │ └── mtz
│ │ └── cleanarch
│ │ └── infrastructure
│ │ ├── configuration
│ │ └── InfrastructureConfiguration.java
│ │ ├── repository
│ │ ├── CustomerExtractor.java
│ │ └── JdbcCustomerRepository.java
│ │ └── service
│ │ ├── CustomerScoreClientService.java
│ │ └── client
│ │ ├── CustomerScoreClient.java
│ │ ├── request
│ │ └── CustomerScoreRequest.java
│ │ └── response
│ │ ├── CustomerScoreResponse.java
│ │ └── CustomerScoreStatusEnum.java
│ └── resources
│ └── db
│ └── changelog
│ ├── db.changelog-master.yaml
│ └── includes
│ └── 20200805013000_create_table_customers.yaml
├── pom.xml
└── web
├── pom.xml
└── src
└── main
├── java
└── br
│ └── com
│ └── mtz
│ └── cleanarch
│ └── CleanarchApplication.java
└── resources
└── application.properties
/.github/workflows/sample-ci-jobs.yml:
--------------------------------------------------------------------------------
1 | name: sample-ci-jobs
2 |
3 | on:
4 | push:
5 | branches: [ jobs ]
6 |
7 | jobs:
8 | get-aws-password:
9 | runs-on: self-hosted
10 | outputs:
11 | ecr_pwd: ${{ steps.step1.outputs.ecr_pwd }}
12 | steps:
13 | - id: step1
14 | run: |
15 | ECR_PWD=`aws ecr get-login-password --region us-east-1`
16 | echo "::set-output name=ecr_pwd::$ECR_PWD"
17 |
18 | build:
19 | runs-on: self-hosted
20 | needs: get-aws-password
21 | steps:
22 | - uses: actions/checkout@v2
23 | - uses: actions/cache@v1
24 | with:
25 | path: ~/.m2/repository
26 | key: ${{ runner.os }}-maven-${{ hashFiles('./pom.xml') }}
27 | restore-keys: |
28 | ${{ runner.os }}-maven-
29 |
30 | - name: Build with Maven
31 | working-directory: .
32 | run: mvn clean install
33 |
34 | docker_build:
35 | runs-on: self-hosted
36 | needs: [build,get-aws-password]
37 | container:
38 | image: 475383224204.dkr.ecr.us-east-1.amazonaws.com/step-image-docker-build:latest
39 | credentials:
40 | username: ${{ secrets.AWS_ECR_REGISTRY_USERNAME }}
41 | password: ${{ needs.get-aws-password.outputs.ecr_pwd }}
42 | env:
43 | REGISTRY_URL: ${{ secrets.AWS_ECR_REGISTRY }}
44 | REPOSITORY_NAME: sample-jobs
45 | IMAGE_TAG: v1
46 | DOCKERFILE_PATH: .
47 | steps:
48 | - name: Build docker image
49 | run: sh /orange-init.sh
50 |
51 | docker_push:
52 | runs-on: self-hosted
53 | needs: [docker_build,get-aws-password]
54 | container:
55 | image: 475383224204.dkr.ecr.us-east-1.amazonaws.com/step-image-docker-push:latest
56 | credentials:
57 | username: ${{ secrets.AWS_ECR_REGISTRY_USERNAME }}
58 | password: ${{ needs.get-aws-password.outputs.ecr_pwd }}
59 | env:
60 | REGISTRY_URL: ${{ secrets.AWS_ECR_REGISTRY }}
61 | REGISTRY_USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USERNAME }}
62 | REGISTRY_PASSWORD: ${{ needs.get-aws-password.outputs.ecr_pwd }}
63 | REPOSITORY_NAME: sample-jobs
64 | IMAGE_TAG: v1
65 | steps:
66 | - name: Publish docker image
67 | run: sh /orange-init.sh
--------------------------------------------------------------------------------
/.github/workflows/sample-ci-steps.yml:
--------------------------------------------------------------------------------
1 | name: sample-ci-steps
2 |
3 | on:
4 | push:
5 | branches: [ steps ]
6 |
7 | jobs:
8 | build_and_push:
9 | runs-on: self-hosted
10 | steps:
11 | - uses: actions/checkout@v2
12 | - uses: actions/cache@v1
13 | with:
14 | path: ~/.m2/repository
15 | key: ${{ runner.os }}-maven-${{ hashFiles('./pom.xml') }}
16 | restore-keys: |
17 | ${{ runner.os }}-maven-
18 |
19 | - name: Build with Maven
20 | working-directory: .
21 | run: mvn clean install
22 |
23 | - name: Build docker image
24 | uses: docker://docker:dind
25 | with:
26 | args: build . -t ${{ secrets.AWS_ECR_REGISTRY }}/sample-steps:v1
27 |
28 | - name: Configure AWS credentials
29 | uses: aws-actions/configure-aws-credentials@v1
30 | with:
31 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
32 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
33 | aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}
34 | aws-region: us-east-1
35 | - name: Login to Amazon ECR
36 | id: login-ecr
37 | uses: aws-actions/amazon-ecr-login@v1
38 | - name: Publish docker image
39 | uses: docker://docker:dind
40 | with:
41 | args: push ${{ secrets.AWS_ECR_REGISTRY }}/sample-steps:v1
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## MAVEN
2 | target/
3 |
4 | ## INTELLIJ
5 | .idea
6 | *.iws
7 | *.iml
8 | *.ipr
9 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:11
2 | ARG JAR_FILE=web/target/*.jar
3 | COPY ${JAR_FILE} app.jar
4 | ENTRYPOINT ["java","-jar","/app.jar"]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # clean-arch-java-demo
2 | Demo Application to apply Clean Architecture concepts
3 |
--------------------------------------------------------------------------------
/api/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | cleanarch
7 | br.com.mtz
8 | 0.0.1-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | api
13 |
14 |
15 |
16 | br.com.mtz
17 | application
18 | 0.0.1-SNAPSHOT
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/api/src/main/java/br/com/mtz/cleanarch/api/CustomerApiExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.api;
2 |
3 | import br.com.mtz.cleanarch.domain.exception.BusinessException;
4 | import br.com.mtz.cleanarch.domain.exception.NotFoundException;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.http.ResponseEntity;
9 | import org.springframework.web.bind.annotation.ControllerAdvice;
10 | import org.springframework.web.bind.annotation.ExceptionHandler;
11 | import org.springframework.web.bind.annotation.ResponseBody;
12 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | @ControllerAdvice
17 | public class CustomerApiExceptionHandler extends ResponseEntityExceptionHandler {
18 |
19 | public Logger logger = LoggerFactory.getLogger(this.getClass());
20 |
21 | @ExceptionHandler(NotFoundException.class)
22 | @ResponseBody
23 | private ResponseEntity exceptions(NotFoundException exception) {
24 | this.logger.error(exception.getMessage(), exception);
25 | Map attributes = new HashMap() {{
26 | put("resource", exception.getResourceName());
27 | put("value", exception.getId());
28 | }};
29 | return new ResponseEntity(attributes, HttpStatus.NOT_FOUND);
30 | }
31 |
32 | @ExceptionHandler(BusinessException.class)
33 | @ResponseBody
34 | private ResponseEntity exceptions(BusinessException exception) {
35 | this.logger.error(exception.getMessage(), exception);
36 | Map attributes = new HashMap() {{
37 | put("message", exception.getMessage());
38 | }};
39 | return new ResponseEntity(attributes, HttpStatus.BAD_REQUEST);
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/api/src/main/java/br/com/mtz/cleanarch/api/controller/CustomerController.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.api.controller;
2 |
3 | import br.com.mtz.cleanarch.application.*;
4 | import br.com.mtz.cleanarch.application.request.CreateCustomerRequest;
5 | import br.com.mtz.cleanarch.application.request.UpdateCustomerRequest;
6 | import br.com.mtz.cleanarch.application.response.CustomerResponse;
7 | import br.com.mtz.cleanarch.application.response.PageResponse;
8 | import br.com.mtz.cleanarch.domain.PageRequest;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.web.bind.annotation.*;
11 |
12 | @RestController
13 | @RequestMapping(value = "/customers")
14 | public class CustomerController {
15 |
16 | private CreateCustomerInteractor createCustomerInteractor;
17 | private DeleteCustomerInteractor deleteCustomerInteractor;
18 | private FindAllCustomersInteractor findAllCustomersInteractor;
19 | private FindCustomerInteractor findCustomerInteractor;
20 | private UpdateCustomerInteractor updateCustomerInteractor;
21 |
22 | public CustomerController(
23 | CreateCustomerInteractor createCustomerInteractor,
24 | DeleteCustomerInteractor deleteCustomerInteractor,
25 | FindAllCustomersInteractor findAllCustomersInteractor,
26 | FindCustomerInteractor findCustomerInteractor,
27 | UpdateCustomerInteractor updateCustomerInteractor
28 | ) {
29 | this.createCustomerInteractor = createCustomerInteractor;
30 | this.deleteCustomerInteractor = deleteCustomerInteractor;
31 | this.findAllCustomersInteractor = findAllCustomersInteractor;
32 | this.findCustomerInteractor = findCustomerInteractor;
33 | this.updateCustomerInteractor = updateCustomerInteractor;
34 | }
35 |
36 | @PostMapping
37 | @ResponseStatus(HttpStatus.CREATED)
38 | public CustomerResponse create(@RequestBody CreateCustomerRequest request) {
39 | return this.createCustomerInteractor.execute(request);
40 | }
41 |
42 | @DeleteMapping(value = "/{id}")
43 | @ResponseStatus(HttpStatus.NO_CONTENT)
44 | public void delete(@PathVariable("id") String id) {
45 | this.deleteCustomerInteractor.execute(id);
46 | }
47 |
48 | @GetMapping
49 | @ResponseStatus(HttpStatus.OK)
50 | public PageResponse find(PageRequest pageRequest) {
51 | return findAllCustomersInteractor.execute(pageRequest);
52 | }
53 |
54 | @GetMapping(value = "/{id}")
55 | @ResponseStatus(HttpStatus.OK)
56 | public CustomerResponse find(@PathVariable("id") String id) {
57 | return this.findCustomerInteractor.execute(id);
58 | }
59 |
60 | @PutMapping(value = "/{id}")
61 | @ResponseStatus(HttpStatus.OK)
62 | public CustomerResponse update(@PathVariable("id") String id, @RequestBody UpdateCustomerRequest request) {
63 | return this.updateCustomerInteractor.execute(id, request);
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/application/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | cleanarch
7 | br.com.mtz
8 | 0.0.1-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | application
13 |
14 |
15 |
16 | br.com.mtz
17 | domain
18 | 0.0.1-SNAPSHOT
19 |
20 |
21 | javax.inject
22 | javax.inject
23 | 1
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/CreateCustomerInteractor.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application;
2 |
3 | import br.com.mtz.cleanarch.application.request.CreateCustomerRequest;
4 | import br.com.mtz.cleanarch.application.response.CustomerResponse;
5 |
6 | public interface CreateCustomerInteractor {
7 |
8 | CustomerResponse execute(CreateCustomerRequest request);
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/DeleteCustomerInteractor.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application;
2 |
3 | public interface DeleteCustomerInteractor {
4 |
5 | void execute(String id);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/FindAllCustomersInteractor.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application;
2 |
3 | import br.com.mtz.cleanarch.application.response.CustomerResponse;
4 | import br.com.mtz.cleanarch.application.response.PageResponse;
5 | import br.com.mtz.cleanarch.domain.PageRequest;
6 |
7 | public interface FindAllCustomersInteractor {
8 |
9 | PageResponse execute(PageRequest pageRequest);
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/FindCustomerInteractor.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application;
2 |
3 | import br.com.mtz.cleanarch.application.response.CustomerResponse;
4 |
5 | public interface FindCustomerInteractor {
6 |
7 | CustomerResponse execute(String id);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/UpdateCustomerInteractor.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application;
2 |
3 | import br.com.mtz.cleanarch.application.request.UpdateCustomerRequest;
4 | import br.com.mtz.cleanarch.application.response.CustomerResponse;
5 |
6 | public interface UpdateCustomerInteractor {
7 |
8 | CustomerResponse execute(String id, UpdateCustomerRequest request);
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/impl/CreateCustomerInteractorImpl.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application.impl;
2 |
3 | import br.com.mtz.cleanarch.application.CreateCustomerInteractor;
4 | import br.com.mtz.cleanarch.application.request.CreateCustomerRequest;
5 | import br.com.mtz.cleanarch.application.response.CustomerResponse;
6 | import br.com.mtz.cleanarch.domain.Customer;
7 | import br.com.mtz.cleanarch.domain.exception.BusinessException;
8 | import br.com.mtz.cleanarch.domain.repository.CustomerRepository;
9 | import br.com.mtz.cleanarch.domain.service.CustomerScoreService;
10 | import javax.inject.Inject;
11 | import javax.inject.Named;
12 |
13 | @Named
14 | public class CreateCustomerInteractorImpl implements CreateCustomerInteractor {
15 |
16 | private CustomerRepository customerRepository;
17 | private CustomerScoreService customerScoreService;
18 |
19 | @Inject
20 | public CreateCustomerInteractorImpl(
21 | CustomerRepository customerRepository,
22 | CustomerScoreService customerScoreService
23 | ) {
24 | this.customerRepository = customerRepository;
25 | this.customerScoreService = customerScoreService;
26 | }
27 |
28 | @Override
29 | public CustomerResponse execute(CreateCustomerRequest request) {
30 | Customer customer = request.toDomain();
31 | if (this.customerScoreService.isApproved(customer.getCpf())) {
32 | return CustomerResponse.from(customerRepository.create(customer));
33 | } else {
34 | throw new BusinessException("Your cpf is not approved");
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/impl/DeleteCustomerInteractorImpl.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application.impl;
2 |
3 | import br.com.mtz.cleanarch.application.DeleteCustomerInteractor;
4 | import br.com.mtz.cleanarch.domain.Customer;
5 | import br.com.mtz.cleanarch.domain.exception.NotFoundException;
6 | import br.com.mtz.cleanarch.domain.repository.CustomerRepository;
7 | import javax.inject.Inject;
8 | import javax.inject.Named;
9 |
10 | @Named
11 | public class DeleteCustomerInteractorImpl implements DeleteCustomerInteractor {
12 |
13 | private CustomerRepository customerRepository;
14 |
15 | @Inject
16 | public DeleteCustomerInteractorImpl(
17 | CustomerRepository customerRepository
18 | ) {
19 | this.customerRepository = customerRepository;
20 | }
21 |
22 | @Override
23 | public void execute(String id) {
24 | Customer customer = customerRepository.find(id).orElseThrow(() -> new NotFoundException("customer", id));
25 | customerRepository.delete(customer);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/impl/FindAllCustomersInteractorImpl.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application.impl;
2 |
3 | import br.com.mtz.cleanarch.application.FindAllCustomersInteractor;
4 | import br.com.mtz.cleanarch.application.response.CustomerResponse;
5 | import br.com.mtz.cleanarch.application.response.PageResponse;
6 | import br.com.mtz.cleanarch.domain.Customer;
7 | import br.com.mtz.cleanarch.domain.Page;
8 | import br.com.mtz.cleanarch.domain.PageRequest;
9 | import br.com.mtz.cleanarch.domain.repository.CustomerRepository;
10 | import javax.inject.Inject;
11 | import javax.inject.Named;
12 | import java.util.stream.Collectors;
13 |
14 | @Named
15 | public class FindAllCustomersInteractorImpl implements FindAllCustomersInteractor {
16 |
17 | private CustomerRepository customerRepository;
18 |
19 | @Inject
20 | public FindAllCustomersInteractorImpl(
21 | CustomerRepository customerRepository
22 | ) {
23 | this.customerRepository = customerRepository;
24 | }
25 |
26 | @Override
27 | public PageResponse execute(PageRequest pageRequest) {
28 | return convert(customerRepository.find(pageRequest));
29 | }
30 |
31 | private PageResponse convert(Page customers) {
32 | return new PageResponse<>(
33 | customers.getContent().stream().map(CustomerResponse::from).collect(Collectors.toList()),
34 | customers.getPageNumber(),
35 | customers.size(),
36 | customers.isLast(),
37 | customers.totalPages()
38 | );
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/impl/FindCustomerInteractorImpl.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application.impl;
2 |
3 | import br.com.mtz.cleanarch.application.FindCustomerInteractor;
4 | import br.com.mtz.cleanarch.application.response.CustomerResponse;
5 | import br.com.mtz.cleanarch.domain.Customer;
6 | import br.com.mtz.cleanarch.domain.exception.NotFoundException;
7 | import br.com.mtz.cleanarch.domain.repository.CustomerRepository;
8 | import javax.inject.Inject;
9 | import javax.inject.Named;
10 |
11 | @Named
12 | public class FindCustomerInteractorImpl implements FindCustomerInteractor {
13 |
14 | private CustomerRepository customerRepository;
15 |
16 | @Inject
17 | public FindCustomerInteractorImpl(
18 | CustomerRepository customerRepository
19 | ) {
20 | this.customerRepository = customerRepository;
21 | }
22 |
23 | @Override
24 | public CustomerResponse execute(String id) {
25 | Customer customer = customerRepository.find(id).orElseThrow(() -> new NotFoundException("customer", id));
26 | return CustomerResponse.from(customer);
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/impl/UpdateCustomerInteractorImpl.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application.impl;
2 |
3 | import br.com.mtz.cleanarch.application.UpdateCustomerInteractor;
4 | import br.com.mtz.cleanarch.application.request.UpdateCustomerRequest;
5 | import br.com.mtz.cleanarch.application.response.CustomerResponse;
6 | import br.com.mtz.cleanarch.domain.Customer;
7 | import br.com.mtz.cleanarch.domain.exception.NotFoundException;
8 | import br.com.mtz.cleanarch.domain.repository.CustomerRepository;
9 | import javax.inject.Inject;
10 | import javax.inject.Named;
11 |
12 | @Named
13 | public class UpdateCustomerInteractorImpl implements UpdateCustomerInteractor {
14 |
15 | private CustomerRepository customerRepository;
16 |
17 | @Inject
18 | public UpdateCustomerInteractorImpl(
19 | CustomerRepository customerRepository
20 | ) {
21 | this.customerRepository = customerRepository;
22 | }
23 |
24 | @Override
25 | public CustomerResponse execute(String id, UpdateCustomerRequest request) {
26 | Customer customer = customerRepository.find(id).orElseThrow(() -> new NotFoundException("customer", id));
27 | Customer updatedCustomer = new Customer(
28 | customer.getId(),
29 | request.getName(),
30 | customer.getBirthDate(),
31 | request.getCity(),
32 | customer.getCpf()
33 | );
34 | customerRepository.update(updatedCustomer);
35 | return CustomerResponse.from(updatedCustomer);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/request/CreateCustomerRequest.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application.request;
2 |
3 | import br.com.mtz.cleanarch.domain.Customer;
4 | import org.springframework.format.annotation.DateTimeFormat;
5 | import java.time.LocalDate;
6 | import java.util.UUID;
7 |
8 | public class CreateCustomerRequest {
9 |
10 | private String name;
11 | @DateTimeFormat(iso = DateTimeFormat.ISO.DATE, pattern = "dd-MM-yyyy")
12 | private LocalDate birthDate;
13 | private String city;
14 | private String cpf;
15 |
16 | public CreateCustomerRequest(String name, LocalDate birthDate, String city, String cpf) {
17 | this.name = name;
18 | this.birthDate = birthDate;
19 | this.city = city;
20 | this.cpf = cpf;
21 | }
22 |
23 | public String getName() {
24 | return name;
25 | }
26 |
27 | public void setName(String name) {
28 | this.name = name;
29 | }
30 |
31 | public LocalDate getBirthDate() {
32 | return birthDate;
33 | }
34 |
35 | public void setBirthDate(LocalDate birthDate) {
36 | this.birthDate = birthDate;
37 | }
38 |
39 | public String getCity() {
40 | return city;
41 | }
42 |
43 | public void setCity(String city) {
44 | this.city = city;
45 | }
46 |
47 | public String getCpf() {
48 | return cpf;
49 | }
50 |
51 | public void setCpf(String cpf) {
52 | this.cpf = cpf;
53 | }
54 |
55 | public Integer calculateAge() {
56 | return LocalDate.now().getYear() - this.birthDate.getYear();
57 | }
58 |
59 | public Customer toDomain() {
60 | return new Customer(UUID.randomUUID().toString(), this.name, this.birthDate, this.city, this.cpf);
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/request/UpdateCustomerRequest.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application.request;
2 |
3 | public class UpdateCustomerRequest {
4 |
5 | private String name;
6 | private String city;
7 |
8 | public UpdateCustomerRequest(String name, String city) {
9 | this.name = name;
10 | this.city = city;
11 | }
12 |
13 | public String getName() {
14 | return name;
15 | }
16 |
17 | public void setName(String name) {
18 | this.name = name;
19 | }
20 |
21 | public String getCity() {
22 | return city;
23 | }
24 |
25 | public void setCity(String city) {
26 | this.city = city;
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/response/CustomerResponse.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application.response;
2 |
3 | import br.com.mtz.cleanarch.domain.Customer;
4 |
5 | import java.time.LocalDate;
6 |
7 | public class CustomerResponse {
8 |
9 | private String id;
10 | private String name;
11 | private LocalDate birthDate;
12 | private String city;
13 |
14 | public CustomerResponse(String id, String name, LocalDate birthDate, String city) {
15 | this.id = id;
16 | this.name = name;
17 | this.birthDate = birthDate;
18 | this.city = city;
19 | }
20 |
21 | public String getId() {
22 | return id;
23 | }
24 |
25 | public void setId(String id) {
26 | this.id = id;
27 | }
28 |
29 | public String getName() {
30 | return name;
31 | }
32 |
33 | public void setName(String name) {
34 | this.name = name;
35 | }
36 |
37 | public LocalDate getBirthDate() {
38 | return birthDate;
39 | }
40 |
41 | public void setBirthDate(LocalDate birthDate) {
42 | this.birthDate = birthDate;
43 | }
44 |
45 | public String getCity() {
46 | return city;
47 | }
48 |
49 | public void setCity(String city) {
50 | this.city = city;
51 | }
52 |
53 | public static CustomerResponse from(Customer customer) {
54 | return new CustomerResponse(
55 | customer.getId(),
56 | customer.getName(),
57 | customer.getBirthDate(),
58 | customer.getCity()
59 | );
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/application/src/main/java/br/com/mtz/cleanarch/application/response/PageResponse.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.application.response;
2 |
3 | import java.util.List;
4 |
5 | public class PageResponse {
6 |
7 | private List content;
8 | private Integer page;
9 | private Integer size;
10 | private Boolean isLast;
11 | private Integer totalPages;
12 |
13 | public PageResponse(List content, Integer page, Integer size, Boolean isLast, Integer totalPages) {
14 | this.content = content;
15 | this.page = page;
16 | this.size = size;
17 | this.isLast = isLast;
18 | this.totalPages = totalPages;
19 | }
20 |
21 | public List getContent() {
22 | return content;
23 | }
24 |
25 | public void setContent(List content) {
26 | this.content = content;
27 | }
28 |
29 | public Integer getPage() {
30 | return page;
31 | }
32 |
33 | public void setPage(Integer page) {
34 | this.page = page;
35 | }
36 |
37 | public Integer getSize() {
38 | return size;
39 | }
40 |
41 | public void setSize(Integer size) {
42 | this.size = size;
43 | }
44 |
45 | public Boolean getLast() {
46 | return isLast;
47 | }
48 |
49 | public void setLast(Boolean last) {
50 | isLast = last;
51 | }
52 |
53 | public Integer getTotalPages() {
54 | return totalPages;
55 | }
56 |
57 | public void setTotalPages(Integer totalPages) {
58 | this.totalPages = totalPages;
59 | }
60 |
61 | public static PageResponse from(
62 | List content,
63 | Integer page,
64 | Integer size,
65 | Boolean isLast,
66 | Integer totalPages
67 | ) {
68 | return new PageResponse(content, page, size, isLast, totalPages);
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/data/postman/Clean Arch Java Demo.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "_postman_id": "d4776c1d-b385-4f78-95e7-818f46114849",
4 | "name": "Clean Arch Java Demo",
5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
6 | },
7 | "item": [
8 | {
9 | "name": "Create Customer",
10 | "event": [
11 | {
12 | "listen": "test",
13 | "script": {
14 | "id": "befd3c82-4532-457d-bbf0-558ee7a380f6",
15 | "exec": [
16 | "const response = JSON.parse(responseBody);",
17 | "postman.setGlobalVariable(\"customerId\", response[\"id\"]);"
18 | ],
19 | "type": "text/javascript"
20 | }
21 | }
22 | ],
23 | "request": {
24 | "method": "POST",
25 | "header": [],
26 | "body": {
27 | "mode": "raw",
28 | "raw": "{\n \"name\": \"Mtz\",\n \"birthDate\": \"1995-07-24\",\n \"city\": \"Uberlandia\",\n \"cpf\": \"10839869614\"\n}",
29 | "options": {
30 | "raw": {
31 | "language": "json"
32 | }
33 | }
34 | },
35 | "url": {
36 | "raw": "http://localhost:8080/customers",
37 | "protocol": "http",
38 | "host": [
39 | "localhost"
40 | ],
41 | "port": "8080",
42 | "path": [
43 | "customers"
44 | ]
45 | }
46 | },
47 | "response": []
48 | },
49 | {
50 | "name": "Delete Customer",
51 | "request": {
52 | "method": "DELETE",
53 | "header": [],
54 | "url": {
55 | "raw": "http://localhost:8080/customers/{{customerId}}",
56 | "protocol": "http",
57 | "host": [
58 | "localhost"
59 | ],
60 | "port": "8080",
61 | "path": [
62 | "customers",
63 | "{{customerId}}"
64 | ]
65 | }
66 | },
67 | "response": []
68 | },
69 | {
70 | "name": "Find All Customers",
71 | "request": {
72 | "method": "GET",
73 | "header": [],
74 | "url": {
75 | "raw": "http://localhost:8080/customers?page=0&size=20",
76 | "protocol": "http",
77 | "host": [
78 | "localhost"
79 | ],
80 | "port": "8080",
81 | "path": [
82 | "customers"
83 | ],
84 | "query": [
85 | {
86 | "key": "page",
87 | "value": "0"
88 | },
89 | {
90 | "key": "size",
91 | "value": "20"
92 | }
93 | ]
94 | }
95 | },
96 | "response": []
97 | },
98 | {
99 | "name": "Find Customer By Id",
100 | "request": {
101 | "method": "GET",
102 | "header": [],
103 | "url": {
104 | "raw": "http://localhost:8080/customers/{{customerId}}",
105 | "protocol": "http",
106 | "host": [
107 | "localhost"
108 | ],
109 | "port": "8080",
110 | "path": [
111 | "customers",
112 | "{{customerId}}"
113 | ]
114 | }
115 | },
116 | "response": []
117 | },
118 | {
119 | "name": "Update Customer",
120 | "request": {
121 | "method": "PUT",
122 | "header": [],
123 | "body": {
124 | "mode": "raw",
125 | "raw": "{\n \"name\": \"Mtz2\",\n \"city\": \"Uberaba\"\n}",
126 | "options": {
127 | "raw": {
128 | "language": "json"
129 | }
130 | }
131 | },
132 | "url": {
133 | "raw": "http://localhost:8080/customers/{{customerId}}",
134 | "protocol": "http",
135 | "host": [
136 | "localhost"
137 | ],
138 | "port": "8080",
139 | "path": [
140 | "customers",
141 | "{{customerId}}"
142 | ]
143 | }
144 | },
145 | "response": []
146 | },
147 | {
148 | "name": "Serasa Score",
149 | "request": {
150 | "method": "POST",
151 | "header": [],
152 | "body": {
153 | "mode": "raw",
154 | "raw": "{\"cpf\":\"10839869614\"}",
155 | "options": {
156 | "raw": {
157 | "language": "json"
158 | }
159 | }
160 | },
161 | "url": {
162 | "raw": "http://localhost:8883/serasa",
163 | "protocol": "http",
164 | "host": [
165 | "localhost"
166 | ],
167 | "port": "8883",
168 | "path": [
169 | "serasa"
170 | ]
171 | }
172 | },
173 | "response": []
174 | }
175 | ],
176 | "protocolProfileBehavior": {}
177 | }
--------------------------------------------------------------------------------
/data/stubby/integrations.yml:
--------------------------------------------------------------------------------
1 | - request:
2 | method: POST
3 | url: ^/serasa
4 | headers:
5 | content-type: application/json
6 | post: >
7 | {"cpf":"(1.*)"}
8 |
9 | response:
10 | status: 200
11 | headers:
12 | content-type: application/json
13 | body: >
14 | {
15 | "status": "APPROVED"
16 | }
17 |
18 | - request:
19 | method: POST
20 | url: ^/serasa
21 | headers:
22 | content-type: application/json
23 | post: >
24 | {"cpf":"(2.*)"}
25 |
26 | response:
27 | status: 200
28 | headers:
29 | content-type: application/json
30 | body: >
31 | {
32 | "status": "NOT_APPROVED"
33 | }
34 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 |
5 | postgres:
6 | image: postgres:latest
7 | ports:
8 | - 5432:5432
9 | environment:
10 | - POSTGRES_DB=clean-arch-java-demo
11 | - POSTGRES_USER=clean-arch-java-demo
12 | - POSTGRES_PASSWORD=clean-arch-java-demo
13 | networks:
14 | - custom
15 |
16 | stubby4j:
17 | image: sandokandias/stubby4j-docker
18 | ports:
19 | - "8883:8883"
20 | environment:
21 | STUBBY_PORT: 8883
22 | volumes:
23 | - ./data/stubby/integrations.yml:/usr/local/stubby.yml
24 | networks:
25 | - custom
26 |
27 | networks:
28 | custom:
29 | driver: bridge
30 | ipam:
31 | driver: default
32 | config:
33 | - subnet: 192.165.70.0/16
34 |
--------------------------------------------------------------------------------
/domain/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | cleanarch
7 | br.com.mtz
8 | 0.0.1-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | domain
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/domain/src/main/java/br/com/mtz/cleanarch/domain/Customer.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.domain;
2 |
3 | import java.time.LocalDate;
4 | import java.util.Objects;
5 |
6 | public class Customer {
7 |
8 | private String id;
9 | private String name;
10 | private LocalDate birthDate;
11 | private String city;
12 | private String cpf;
13 |
14 | public Customer(String id, String name, LocalDate birthDate, String city, String cpf) {
15 | this.id = id;
16 | this.name = name;
17 | this.birthDate = birthDate;
18 | this.city = city;
19 | this.cpf = cpf;
20 | }
21 |
22 | public String getId() {
23 | return id;
24 | }
25 |
26 | public void setId(String id) {
27 | this.id = id;
28 | }
29 |
30 | public String getName() {
31 | return name;
32 | }
33 |
34 | public void setName(String name) {
35 | this.name = name;
36 | }
37 |
38 | public LocalDate getBirthDate() {
39 | return birthDate;
40 | }
41 |
42 | public void setBirthDate(LocalDate birthDate) {
43 | this.birthDate = birthDate;
44 | }
45 |
46 | public String getCity() {
47 | return city;
48 | }
49 |
50 | public void setCity(String city) {
51 | this.city = city;
52 | }
53 |
54 | public String getCpf() {
55 | return cpf;
56 | }
57 |
58 | public void setCpf(String cpf) {
59 | this.cpf = cpf;
60 | }
61 |
62 | @Override
63 | public boolean equals(Object o) {
64 | if (this == o) return true;
65 | if (o == null || getClass() != o.getClass()) return false;
66 | Customer customer = (Customer) o;
67 | return id.equals(customer.id) &&
68 | name.equals(customer.name) &&
69 | birthDate.equals(customer.birthDate) &&
70 | city.equals(customer.city) &&
71 | cpf.equals(customer.cpf);
72 | }
73 |
74 | @Override
75 | public int hashCode() {
76 | return Objects.hash(id, name, birthDate, city, cpf);
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/domain/src/main/java/br/com/mtz/cleanarch/domain/Page.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.domain;
2 |
3 | import java.util.List;
4 |
5 | public class Page {
6 |
7 | private List content;
8 | private Integer pageNumber;
9 | private Integer pageSize;
10 | private Integer total;
11 |
12 | public Page(List content, Integer pageNumber, Integer pageSize, Integer total) {
13 | this.content = content;
14 | this.pageNumber = pageNumber;
15 | this.pageSize = pageSize;
16 | this.total = total;
17 | }
18 |
19 | public List getContent() {
20 | return content;
21 | }
22 |
23 | public void setContent(List content) {
24 | this.content = content;
25 | }
26 |
27 | public Integer getPageNumber() {
28 | return pageNumber;
29 | }
30 |
31 | public void setPageNumber(Integer pageNumber) {
32 | this.pageNumber = pageNumber;
33 | }
34 |
35 | public Integer getPageSize() {
36 | return pageSize;
37 | }
38 |
39 | public void setPageSize(Integer pageSize) {
40 | this.pageSize = pageSize;
41 | }
42 |
43 | public Integer getTotal() {
44 | return total;
45 | }
46 |
47 | public void setTotal(Integer total) {
48 | this.total = total;
49 | }
50 |
51 | public Boolean isLast() {
52 | return this.pageNumber +1 >= this.totalPages();
53 | }
54 |
55 | public Integer totalPages() {
56 | if (this.content.isEmpty() && this.total == 0) {
57 | return 1;
58 | } else {
59 | return Double.valueOf(Math.ceil(this.total / this.pageSize)).intValue();
60 | }
61 | }
62 |
63 | public Integer size() {
64 | return this.content.size();
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/domain/src/main/java/br/com/mtz/cleanarch/domain/PageRequest.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.domain;
2 |
3 | public class PageRequest {
4 |
5 | private Integer page;
6 | private Integer size;
7 |
8 | public PageRequest(Integer page, Integer size) {
9 | this.page = page;
10 | this.size = size;
11 | }
12 |
13 | public Integer getPage() {
14 | return page;
15 | }
16 |
17 | public void setPage(Integer page) {
18 | this.page = page;
19 | }
20 |
21 | public Integer getSize() {
22 | return size;
23 | }
24 |
25 | public void setSize(Integer size) {
26 | this.size = size;
27 | }
28 |
29 | public Integer offset() {
30 | return page * size;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/domain/src/main/java/br/com/mtz/cleanarch/domain/exception/BusinessException.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.domain.exception;
2 |
3 | public class BusinessException extends RuntimeException {
4 |
5 | private String message;
6 |
7 | public BusinessException(String message) {
8 | super(message);
9 | this.message = message;
10 | }
11 |
12 | @Override
13 | public String getMessage() {
14 | return message;
15 | }
16 |
17 | public void setMessage(String message) {
18 | this.message = message;
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/domain/src/main/java/br/com/mtz/cleanarch/domain/exception/NotFoundException.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.domain.exception;
2 |
3 | public class NotFoundException extends RuntimeException {
4 |
5 | private String resourceName;
6 | private String id;
7 |
8 | public NotFoundException(String resourceName, String id) {
9 | super("Resource not found!");
10 | this.resourceName = resourceName;
11 | this.id = id;
12 | }
13 |
14 | public String getResourceName() {
15 | return resourceName;
16 | }
17 |
18 | public void setResourceName(String resourceName) {
19 | this.resourceName = resourceName;
20 | }
21 |
22 | public String getId() {
23 | return id;
24 | }
25 |
26 | public void setId(String id) {
27 | this.id = id;
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/domain/src/main/java/br/com/mtz/cleanarch/domain/repository/CustomerRepository.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.domain.repository;
2 |
3 | import br.com.mtz.cleanarch.domain.Customer;
4 | import br.com.mtz.cleanarch.domain.Page;
5 | import br.com.mtz.cleanarch.domain.PageRequest;
6 |
7 | import java.util.Optional;
8 |
9 | public interface CustomerRepository {
10 |
11 | Customer create(Customer customer);
12 |
13 | Customer update(Customer customer);
14 |
15 | Optional find(String id);
16 |
17 | Page find(PageRequest page);
18 |
19 | void delete(Customer customer);
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/domain/src/main/java/br/com/mtz/cleanarch/domain/service/CustomerScoreService.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.domain.service;
2 |
3 | public interface CustomerScoreService {
4 |
5 | public Boolean isApproved(String cpf);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/infrastructure/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | cleanarch
7 | br.com.mtz
8 | 0.0.1-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | infrastructure
13 |
14 |
15 |
16 | br.com.mtz
17 | domain
18 | 0.0.1-SNAPSHOT
19 |
20 |
21 | javax.inject
22 | javax.inject
23 | 1
24 |
25 |
26 | org.liquibase
27 | liquibase-core
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/br/com/mtz/cleanarch/infrastructure/configuration/InfrastructureConfiguration.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.infrastructure.configuration;
2 |
3 | import org.springframework.cloud.openfeign.EnableFeignClients;
4 | import org.springframework.context.annotation.Configuration;
5 |
6 | @Configuration
7 | @EnableFeignClients(basePackages = "br.com.mtz.cleanarch.infrastructure.service.client")
8 | public class InfrastructureConfiguration {
9 | }
10 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/br/com/mtz/cleanarch/infrastructure/repository/CustomerExtractor.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.infrastructure.repository;
2 |
3 | import br.com.mtz.cleanarch.domain.Customer;
4 | import org.springframework.dao.DataAccessException;
5 | import org.springframework.jdbc.core.ResultSetExtractor;
6 | import org.springframework.stereotype.Component;
7 | import java.sql.ResultSet;
8 | import java.sql.SQLException;
9 | import java.util.HashSet;
10 | import java.util.Set;
11 |
12 | @Component
13 | public class CustomerExtractor implements ResultSetExtractor> {
14 |
15 | @Override
16 | public Set extractData(ResultSet resultSet) throws SQLException, DataAccessException {
17 | Set customers = new HashSet<>();
18 |
19 | while (resultSet.next()) {
20 | customers.add(mapCustomer(resultSet));
21 | }
22 |
23 | return customers;
24 | }
25 |
26 | private Customer mapCustomer(ResultSet resultSet) throws SQLException {
27 | return new Customer(
28 | resultSet.getString("id"),
29 | resultSet.getString("name"),
30 | resultSet.getTimestamp("birth_date").toLocalDateTime().toLocalDate(),
31 | resultSet.getString("city"),
32 | resultSet.getString("cpf")
33 | );
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/br/com/mtz/cleanarch/infrastructure/repository/JdbcCustomerRepository.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.infrastructure.repository;
2 |
3 | import br.com.mtz.cleanarch.domain.Customer;
4 | import br.com.mtz.cleanarch.domain.Page;
5 | import br.com.mtz.cleanarch.domain.PageRequest;
6 | import br.com.mtz.cleanarch.domain.repository.CustomerRepository;
7 | import org.springframework.jdbc.core.JdbcTemplate;
8 | import org.springframework.stereotype.Repository;
9 | import javax.inject.Inject;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.Optional;
13 | import java.util.Set;
14 | import java.util.stream.Collectors;
15 |
16 | import static java.util.Collections.emptyList;
17 |
18 | @Repository
19 | public class JdbcCustomerRepository implements CustomerRepository {
20 |
21 | private JdbcTemplate jdbcTemplate;
22 | private CustomerExtractor customerExtractor;
23 |
24 | @Inject
25 | public JdbcCustomerRepository(
26 | JdbcTemplate jdbcTemplate,
27 | CustomerExtractor customerExtractor
28 | ) {
29 | this.jdbcTemplate = jdbcTemplate;
30 | this.customerExtractor = customerExtractor;
31 | }
32 |
33 | private static String BASE_QUERY = "SELECT id, name, birth_date, city, cpf FROM customers";
34 |
35 | @Override
36 | public Customer create(Customer customer) {
37 | String statement = "INSERT INTO customers VALUES (?, ?, ?, ?, ?)";
38 | this.jdbcTemplate.update(statement, customer.getId(), customer.getName(), customer.getBirthDate(), customer.getCity(), customer.getCpf());
39 | return find(customer.getId()).get();
40 | }
41 |
42 | @Override
43 | public Customer update(Customer customer) {
44 | String statement = "UPDATE customers SET name = ?, city = ? WHERE id = ?";
45 |
46 | this.jdbcTemplate.update(
47 | statement,
48 | customer.getName(),
49 | customer.getCity(),
50 | customer.getId()
51 | );
52 |
53 | return find(customer.getId()).get();
54 | }
55 |
56 | @Override
57 | public Optional find(String id) {
58 | StringBuilder statement = new StringBuilder(BASE_QUERY)
59 | .append(" WHERE id = ?");
60 | return this.jdbcTemplate.query(statement.toString(), new String[] {id}, customerExtractor).stream().findFirst();
61 | }
62 |
63 | @Override
64 | public Page find(PageRequest page) {
65 | StringBuilder statement = new StringBuilder(BASE_QUERY)
66 | .append(" LIMIT ?")
67 | .append(" OFFSET ?");
68 |
69 | ArrayList arguments = new ArrayList<>();
70 | arguments.add(page.getSize());
71 | arguments.add(page.offset());
72 |
73 | Set customers = this.jdbcTemplate.query(statement.toString(), arguments.toArray(), customerExtractor);
74 |
75 | return new Page(
76 | collectList(customers),
77 | page.getPage(),
78 | page.getSize(),
79 | executeCountQuery()
80 | );
81 | }
82 |
83 | @Override
84 | public void delete(Customer customer) {
85 | String countStatement = "DELETE FROM customers WHERE id = ?";
86 | this.jdbcTemplate.update(countStatement, customer.getId());
87 | }
88 |
89 | private List collectList(Set customers) {
90 | if(customers.isEmpty()) return emptyList();
91 | else return customers.stream().collect(Collectors.toList());
92 | }
93 |
94 | private Integer executeCountQuery() {
95 | String countStatement = "SELECT count(*) AS total FROM customers";
96 | return this.jdbcTemplate.queryForObject(countStatement, Integer.class);
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/br/com/mtz/cleanarch/infrastructure/service/CustomerScoreClientService.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.infrastructure.service;
2 |
3 | import br.com.mtz.cleanarch.domain.service.CustomerScoreService;
4 | import br.com.mtz.cleanarch.infrastructure.service.client.CustomerScoreClient;
5 | import br.com.mtz.cleanarch.infrastructure.service.client.request.CustomerScoreRequest;
6 | import br.com.mtz.cleanarch.infrastructure.service.client.response.CustomerScoreResponse;
7 | import br.com.mtz.cleanarch.infrastructure.service.client.response.CustomerScoreStatusEnum;
8 | import javax.inject.Named;
9 |
10 | @Named
11 | public class CustomerScoreClientService implements CustomerScoreService {
12 |
13 | private CustomerScoreClient customerScoreClient;
14 |
15 | public CustomerScoreClientService(CustomerScoreClient customerScoreClient) {
16 | this.customerScoreClient = customerScoreClient;
17 | }
18 |
19 | @Override
20 | public Boolean isApproved(String cpf) {
21 | CustomerScoreResponse response = this.customerScoreClient.queryScore(new CustomerScoreRequest(cpf));
22 | if(response.getStatus() == CustomerScoreStatusEnum.APPROVED) {
23 | return true;
24 | } else {
25 | return false;
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/br/com/mtz/cleanarch/infrastructure/service/client/CustomerScoreClient.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.infrastructure.service.client;
2 |
3 | import br.com.mtz.cleanarch.infrastructure.service.client.request.CustomerScoreRequest;
4 | import br.com.mtz.cleanarch.infrastructure.service.client.response.CustomerScoreResponse;
5 | import org.springframework.cloud.openfeign.FeignClient;
6 | import org.springframework.http.HttpStatus;
7 | import org.springframework.http.MediaType;
8 | import org.springframework.web.bind.annotation.PostMapping;
9 | import org.springframework.web.bind.annotation.RequestBody;
10 | import org.springframework.web.bind.annotation.ResponseBody;
11 | import org.springframework.web.bind.annotation.ResponseStatus;
12 |
13 | @FeignClient(name = "customerScoreClient", url = "${customer.score.url}")
14 | public interface CustomerScoreClient {
15 |
16 | @ResponseBody
17 | @ResponseStatus(HttpStatus.OK)
18 | @PostMapping(
19 | path = "/serasa",
20 | produces = MediaType.APPLICATION_JSON_VALUE,
21 | consumes = MediaType.APPLICATION_JSON_VALUE
22 | )
23 | CustomerScoreResponse queryScore(@RequestBody CustomerScoreRequest request);
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/br/com/mtz/cleanarch/infrastructure/service/client/request/CustomerScoreRequest.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.infrastructure.service.client.request;
2 |
3 | public class CustomerScoreRequest {
4 |
5 | private String cpf;
6 |
7 | public CustomerScoreRequest(String cpf) {
8 | this.cpf = cpf;
9 | }
10 |
11 | public String getCpf() {
12 | return cpf;
13 | }
14 |
15 | public void setCpf(String cpf) {
16 | this.cpf = cpf;
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/br/com/mtz/cleanarch/infrastructure/service/client/response/CustomerScoreResponse.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.infrastructure.service.client.response;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | public class CustomerScoreResponse {
7 |
8 | private CustomerScoreStatusEnum status;
9 |
10 | @JsonCreator
11 | public CustomerScoreResponse(@JsonProperty("status") CustomerScoreStatusEnum status) {
12 | this.status = status;
13 | }
14 |
15 | public CustomerScoreStatusEnum getStatus() {
16 | return status;
17 | }
18 |
19 | public void setStatus(CustomerScoreStatusEnum status) {
20 | this.status = status;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/br/com/mtz/cleanarch/infrastructure/service/client/response/CustomerScoreStatusEnum.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch.infrastructure.service.client.response;
2 |
3 | public enum CustomerScoreStatusEnum {
4 | APPROVED, NOT_APPROVED
5 | }
6 |
--------------------------------------------------------------------------------
/infrastructure/src/main/resources/db/changelog/db.changelog-master.yaml:
--------------------------------------------------------------------------------
1 | databaseChangeLog:
2 | - include:
3 | file: db/changelog/includes/20200805013000_create_table_customers.yaml
4 |
--------------------------------------------------------------------------------
/infrastructure/src/main/resources/db/changelog/includes/20200805013000_create_table_customers.yaml:
--------------------------------------------------------------------------------
1 | databaseChangeLog:
2 | - changeSet:
3 | id: 20200805013000-1
4 | author: mateus.cruz
5 | comment: "Create table customers"
6 | changes:
7 | - createTable:
8 | tableName: customers
9 | columns:
10 | - column:
11 | name: id
12 | type: varchar(36)
13 | autoIncrement: false
14 | constraints:
15 | primaryKey: true
16 | nullable: false
17 | - column:
18 | name: name
19 | type: varchar(64)
20 | constraints:
21 | nullable: false
22 | - column:
23 | name: birth_date
24 | type: timestamp
25 | constraints:
26 | nullable: false
27 | - column:
28 | name: city
29 | type: varchar(64)
30 | constraints:
31 | nullable: false
32 | - column:
33 | name: cpf
34 | type: varchar(16)
35 | constraints:
36 | nullable: false
37 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | pom
6 |
7 | web
8 | api
9 | application
10 | domain
11 | infrastructure
12 |
13 |
14 | org.springframework.boot
15 | spring-boot-starter-parent
16 | 2.3.2.RELEASE
17 |
18 |
19 | br.com.mtz
20 | cleanarch
21 | 0.0.1-SNAPSHOT
22 | cleanarch
23 | Demo application applying Clean Architecture
24 |
25 |
26 | 11
27 |
28 |
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-data-jpa
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-web
37 |
38 |
39 | org.springframework.cloud
40 | spring-cloud-starter-openfeign
41 | 2.1.1.RELEASE
42 |
43 |
44 | org.postgresql
45 | postgresql
46 | runtime
47 |
48 |
49 | org.springframework.boot
50 | spring-boot-starter-test
51 | test
52 |
53 |
54 | org.junit.vintage
55 | junit-vintage-engine
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/web/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | cleanarch
7 | br.com.mtz
8 | 0.0.1-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | web
13 |
14 |
15 |
16 | br.com.mtz
17 | api
18 | 0.0.1-SNAPSHOT
19 |
20 |
21 | br.com.mtz
22 | infrastructure
23 | 0.0.1-SNAPSHOT
24 |
25 |
26 |
27 |
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-maven-plugin
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/web/src/main/java/br/com/mtz/cleanarch/CleanarchApplication.java:
--------------------------------------------------------------------------------
1 | package br.com.mtz.cleanarch;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class CleanarchApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(CleanarchApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/web/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.driver-class-name=org.postgresql.Driver
2 | spring.datasource.url=jdbc:postgresql://localhost:5432/clean-arch-java-demo
3 | spring.datasource.username=clean-arch-java-demo
4 | spring.datasource.password=clean-arch-java-demo
5 |
6 | spring.jackson.serialization.write-dates-as-timestamps=false
7 |
8 | customer.score.url=http://localhost:8883
9 |
--------------------------------------------------------------------------------