DEV Community

Hernani Almeida
Hernani Almeida

Posted on

Desacoplando a camada de domínio de uma aplicação das outras camadas

Ferramentas necessárias:

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 conforme imagem abaixo
Image description
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 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;
    }

}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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> {
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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**
Enter fullscreen mode Exit fullscreen mode

Image description
Image description

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

Discussion (0)