├── .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 | --------------------------------------------------------------------------------