Desacoplando a camada de domínio de uma aplicação das outras camadas
Tags: java, simplesoftwarearchitecture, ddd, dynamodb
Published: 2022-04-19 00:21:15

Ferramentas necessárias:
* [Java SDK](https://www.oracle.com/java/technologies/downloads/)
* [Docker](https://docs.docker.com/desktop/windows/install/#:~:text=%20Install%20Docker%20Desktop%20on%20Windows%20%F0%9F%94%97%20,to%20authorize%20the%20installer%20and%20proceed...%20More%20)
* [Postman](https://www.postman.com/downloads/)
* Ide de sua preferencia

Um dos pontos essenciais de uma arquitetura de software e manter o domínio da aplicação(coração da aplicação, entidades e regras de negócios de uma aplicação) desacoplada das outras camadas da aplicação, de modo que caso tenha necessidade de uma mudança, por exemplo a troca de uma base de dados x para uma base de dados y, isso seja feito de uma maneira simples.
Estou estudando e adquirindo conhecimento nesse assunto de designers e estrutura de código mas neste artigo quero dividir o pouco que tenho aprendido sobre o assunto. 
Vamos criar uma aplicação utilizando classes abstratas para deixar nosso domínio isolado, e iremos utilizar no inicio o banco de dados Postgres como database da nossa aplicação, e depois faremos a transição da nossa aplicação para o DybamoDb da AWS de uma forma simples sem ter que alterar nosso domínio.
Para iniciarmos crie seu projeto Spring através do site [Spring Initializr](https://start.spring.io/) conforme imagem abaixo
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qho6xah0xod5e2ucjlz.png)
Vamos construir nossa aplicação utilizando os princípios do design pattern DDD, para isso ela será dividida em 3 packages. 
* **Core:** Esse package e onde ficara o coração da nossa aplicação, a parte de domínio e de regras de negocio que ficara isolada e independente das outras camadas de nossa aplicação.
* **Infrastructure:** Esse package e onde ficara a parte de configuração de nossa aplicação, configuração de banco de dados e repositórios de acesso ao database, nele e que vamos fazer a transição do Postgres para o dynamoDb.
* **Application:** Esse package e onde ficara a parte de acesso a nossa aplicação por aplicações externas, neste artigo nossa aplicação será acessada via requisição Http.
conforme a imagem abaixo.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w44btv0go4a088qiyidl.png)
Dentro do package Core crie um package com o nome `usuario`, nele vamos armazenar as classes de domínio e regras de negocio referente a um usuario de nossa aplicação, dentro do package usuário iremos criar as seguintes classes:
`Usuario` - Nossa classe Entity
```
import javax.persistence.*;

@Entity
public class Usuario {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userId;

    private String name;

    private String email;

    private String cpf;

    @Deprecated
    public Usuario() {
    }

    public Usuario(String name, String email, String cpf) {
        this.name = name;
        this.email = email;
        this.cpf = cpf;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public String getCpf() {
        return cpf;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setCpf(String cpf) {
        this.cpf = cpf;
    }

}
```

`UsuarioRepository` - Uma interface com os métodos de acesso ao banco de dados relacionado a um Usuário.
```
import java.util.List;
import java.util.Optional;

public interface UsuarioRepository {

    public Usuario createUser(Usuario user);
    public Usuario updateUser(Usuario user);
    public Optional<Usuario> findByUserId(Long userId);
    public List<Usuario> findAllUser();
    public void deleteUser(Usuario user);
}
```

`UsuarioService` - Uma interface com os métodos que utilizaremos para atender a regra de negocio de nossa aplicação relacionada a um Usuário.
```
import com.example.ddd.application.inputDto.user.UsuarioForm;
import com.example.ddd.application.outputDto.user.UsuarioDto;

import java.util.List;


public interface UsuarioService {
    public Usuario createUser(Usuario usuario);
    public String updateUser(UsuarioForm usuarioForm, Long userId);
    public List<UsuarioDto> findAllUser();
    public UsuarioDto findByUserId(Long usuarioId);
    public void deleteUser(Long usuarioId);
}
```

`UsuarioServiceImpl` - Nesta classe implementamos os metodos contidos na interface `UsuarioService` e realizamos a logica necessaria para receber e expor os dados para aplicações externas.
```
import com.example.ddd.application.inputDto.user.UsuarioForm;
import com.example.ddd.application.outputDto.user.UsuarioDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class UsuarioServiceImpl implements UsuarioService {

    @Autowired
    private UsuarioRepository usuarioRepository;

    public UsuarioServiceImpl(UsuarioRepository usuarioRepository) {
        this.usuarioRepository = usuarioRepository;
    }

    @Override
    public Usuario createUser(Usuario usuario) {
        return usuarioRepository.createUser(usuario);
    }

    @Override
    public String updateUser(UsuarioForm usuarioForm, Long userId) {
        Usuario usuario = usuarioRepository.findByUserId(userId)
                .orElseThrow(() -> new IllegalArgumentException("Nao ha usuario com o id: " + userId.toString()));
        Usuario usuarioUpdate = usuarioForm.converte();
        usuarioUpdate.setUserId(usuario.getUserId());
        usuarioRepository.updateUser(usuarioUpdate);
        return "atualizado";
    }

    @Override
    public List<UsuarioDto> findAllUser() {
        return usuarioRepository.findAllUser().stream().map(it -> {
            return new UsuarioDto(it.getUserId(), it.getName(), it.getEmail(), it.getCpf());
        }).collect(Collectors.toList());
    }

    @Override
    public UsuarioDto findByUserId(Long usuarioId) {
        Usuario usuario = usuarioRepository.findByUserId(usuarioId)
                .orElseThrow(() -> new IllegalArgumentException("Nao ha usuario cadastrado com o id: " + usuarioId.toString()));
        return new UsuarioDto(usuario.getUserId(), usuario.getName(), usuario.getEmail(), usuario.getCpf());
    }

    @Override
    public void deleteUser(Long usuarioId) {
      Usuario usuario = usuarioRepository.findByUserId(usuarioId)
              .orElseThrow(() -> new IllegalArgumentException("Nao ha usuario cadastrado com o id: " + usuarioId.toString()));
      usuarioRepository.deleteUser(usuario);
    }
}
```
Feito isso vamos agora dentro do package `infrastructure` criar as classes de configuração e repositórios de nossa aplicação.
A parte de configuração do nosso banco de dados postgres armazenaremos dentro do nosso arquivo application.properties.
`Application.properties`
```
# datasource
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/testearq
spring.datasource.username=postgres
spring.datasource.password=password

# jpa
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update

# Mostrar Sql no terminal
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

server.error.include-message=always
```

Vamos criar agora dentro no package `infrastructure` de repositories e dentro desse package um package usuarioRepository que ira armazenar as classes responsáveis ao acesso do banco de dados relacionados ao domínio do Usuário.
`UsuarioAbstractRepositoryDB` - Interface que nos permite a vários métodos relacionados ao banco de dados através do JPA.
```
import com.example.ddd.core.usuario.Usuario;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UsuarioAbstractRepositoryDB extends JpaRepository<Usuario, Long> {
}
```

`UsuarioRepositoryImpl` - Esta classe implementa os métodos que criamos na interface `UsuarioRepository` e tem injetada dentro dela um `UsuarioAbstractRepositoryDB`, para cada método implementado da interface `UsuarioRepository` relacionamos com o método de acesso ao banco de dados que obtemos através da injeção da classe `UsuarioAbstractRepositoryDB`.
```

import com.example.ddd.core.usuario.Usuario;
import com.example.ddd.core.usuario.UsuarioRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Optional;

@Component
public class UsuarioRepositoryImpl implements UsuarioRepository {

    @Autowired
    private UsuarioAbstractRepositoryDB usuarioAbstractRepositoryDB;

    public UsuarioRepositoryImpl(UsuarioAbstractRepositoryDB usuarioAbstractRepositoryDB) {
        this.usuarioAbstractRepositoryDB = usuarioAbstractRepositoryDB;
    }

    @Override
    public Usuario createUser(Usuario user) {
        return usuarioAbstractRepositoryDB.save(user);
    }

    @Override
    public Usuario updateUser(Usuario user) {
        return usuarioAbstractRepositoryDB.save(user);
    }

    @Override
    public Optional<Usuario> findByUserId(Long userId) {
        return usuarioAbstractRepositoryDB.findById(userId);
    }

    @Override
    public List<Usuario> findAllUser() {
        return usuarioAbstractRepositoryDB.findAll();
    }

    @Override
    public void deleteUser(Usuario user) {
        usuarioAbstractRepositoryDB.delete(user);
    }
}
```
E aqui que desacoplamos nosso domínio da parte de configuração e acesso a banco de dados, pois independente do banco de dados que utilizarmos temos uma classe que acessa nosso banco de dados e, atraves da abstração de classe, implementa os métodos contido em nossa interface dentro do nosso domínio, veja que a interface `UsuarioRepository` não depende de qual banco de dados iremos utilizar para implementa-la.

Vamos agora estruturar o acesso a nossa aplicação, dentro do package application, crie os packages `controllers`, `inputDto`, `outputDto`.
Dentro do package controllers crie o package `usuarioController` e dentro dela a classe UsuarioController que terá os métodos de acesso a nossa aplicação via Http.
`UsuarioController`
```
import com.example.ddd.application.inputDto.user.UsuarioForm;
import com.example.ddd.application.outputDto.user.UsuarioDto;
import com.example.ddd.core.usuario.Usuario;
import com.example.ddd.core.usuario.UsuarioService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("api/v1/")
public class UsuarioController {

    private UsuarioService usuarioService;

    public UsuarioController(UsuarioService usuarioService) {
        this.usuarioService = usuarioService;
    }

    @PostMapping("user")
    public String createUser(@RequestBody UsuarioForm usuarioForm){
        Usuario user = usuarioForm.converte();
        System.out.println(user.toString());
        usuarioService.createUser(user);
        return "ok";
    }

    @GetMapping("user")
    public List<UsuarioDto> findAllUser(){
        return usuarioService.findAllUser();
    }

    @PutMapping("user/{usuarioId}")
    public String updateUser(@RequestBody UsuarioForm usuarioForm, @PathVariable("usuarioId") Long usuarioId){
        usuarioService.updateUser(usuarioForm, usuarioId);
        return "atualizado";
    }

    @GetMapping("user/{usuarioId}")
    public UsuarioDto updateUser(@PathVariable("usuarioId") Long usuarioId){
        return usuarioService.findByUserId(usuarioId);
    }

    @DeleteMapping("user/{usuarioId}")
    public void deleteUser(@PathVariable("usuarioId") Long usuarioId){
        usuarioService.deleteUser(usuarioId);
    }
}
```
Dentro do package `inputDto` crie um package `usuario` e dentro dele a classe UsuarioForm, que ira mapear os dados que recebemos atraves da requisição Http e converte esses dados para nossa classe Entity.
`UsuarioForm`
```
import com.example.ddd.core.usuario.Usuario;

public class UsuarioForm {

    private String name;

    private String email;

    private String cpf;

    public UsuarioForm(String name, String email, String cpf) {
        this.name = name;
        this.email = email;
        this.cpf = cpf;
    }

    public Usuario converte() {

        return new Usuario(name,email,cpf);
    }
}

``` 

Dentro do package `outputDto` crie um package `usuario` e dentro dele a classe UsuarioDto, que ira mapear os dados que iremos retornar para nossa requisição Http .
`UsuarioDto`
```
public class UsuarioDto {

    private Long id;

    private String name;

    private String email;

    private String cpf;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public String getCpf() {
        return cpf;
    }

    public UsuarioDto(Long id, String name, String email, String cpf) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.cpf = cpf;
    }
}
```
Vamos agora rodar nossa aplicação e testa-la via Postman, mas para isso será necessário subirmos nosso banco de dados Postgres, na raiz de nossa aplicação crie um arquivo `docker-compose.yml`.
`docker-compose.yml`
```
version: '3'

services:

  postgres:
    image: 'postgres:alpine'
    volumes:
      - postgres-volume:/var/lib/postgresql/data
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password

volumes:
  postgres-volume:
```
Pelo cmd entre na pasta onde contem o arquivo `docker-compose.yml` e rode o seguinte comando ```docker compose up -d```

                **Usuario Cadastrado com Sucesso**
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87i0x0r7s982s9w5vcez.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5rkiutdlkiuoxbrk49tq.png)

Na segunda parte deste post vamos fazer a transição da nossa aplicação do Postgres para o dynamoDb, para acessar e so clicar no link abaixo.

[parte 2](https://dev.to/2020nani/desacoplando-a-camada-de-dominio-de-uma-aplicacao-das-outras-camadas-parte-2-1fkh)