DEV Community

loading...

Arquitetura limpa

guilhermegarcia86
Originally published at programadev.com.br Updated on ・28 min read

Arquitetura e Arquitetura limpa

O que é Arquitetura?

Resumindo arquitetura de software pode ser descrito da seguinte forma: "... a arquitetura envolve: decisões sobre as estruturas que formarão o sistema, controle, protocolos de comunicação, sincronização e acesso a dados, atribuição de funcionalidade a elementos do sistema, distribuição física dos elementos escalabilidade e desempenho e outros atributos de qualidade."

Quebrando um pouco mais essa explicação e tentando deixá-la mais suscinta eu diria que a arquitetura de software é a ideia que trata da relação entre o mapeamento de componentes de um software e os detalhes que são levados em conta na hora de implementar esses elementos na forma de código.

Resumindo ainda mais a arquitetura consiste em um modelo de alto nível que possibilita um entendimento e uma análise mais fácil do software a ser desenvolvido.

Como o nome diz e levando pro mundo real é como ver um arquiteto de uma casa onde ele desenha a planta e todas as partes da construção se encaixam e como elas devem interagir uma com a outra.

Por que existe?

A ideia de arquitetura de software surgiu nos anos 60 e se tornou popular nos anos 90.

A ideia era enfatizar a importância de estruturar um sistema antes de seu desenvolvimento.

O que resolve?

A ideia é que uma boa arquitetura resolva parafrasenado Robert Martin (Uncle Bob):

"O objetivo da arquitetura de software é minimizar os recursos humanos necessários para construir e manter um determinado sistema."

A ideia é que com uma boa arquitetura o custo para mudanças não seja alto, que uma simples mudança não entrave a aplicação.

Arquitetura limpa

Com esses conceitos em mente por volta de 2012 Robert C. Martin (Uncle Bob) criou a Arquitetura Limpa, um estilo com similaridades com a Arquitetura Cebola e a Arquitetura Hexagonal.

O que resolve?

A arquitetura limpa tem como ideia principal, a modulação das informações que serão codificadas, facilitando a manutenção; os módulos precisam ser independentes o suficiente para que possam ser trabalhados pelos desenvolvedores em equipes diferentes

  • Independência entre componentes, quer dizer cada módulo não conhece o outro, então mudanças em cada módulo não quebram ou necessitam de ajustes nos demais.

  • Independência de framework, os frameworks que tanto gostamos aqui são tratados como meros detalhes, as aplicações não são mais amarradas ao framework, podendo assim haver substituição rápida de um framework por outro sem nenhum impacto na aplicação.

  • Independência de banco de dados, assim como os frameworks o banco de dados é tratado como um detalhe.

  • Testabilidade aqui vale um ponto importante, quanto mais fácil for pro seu sistema ser testado menos acoplamento ele terá isso significa que mudanças serão faceis de ocorrer e de serem testadas.

  • Independência de interface de usuário, seja um GUI, API ou que quer que seja deve haver independência e não deve interferir no funcionamento do sistema.

  • Independência de agentes externos, a nossa regra de negócio não deve depender de nada externo.

Como funciona?

Neste modelo proposto por Robert C. Martin, Uncle Bob, a arquitetura é representada por camadas circulares concêntricas passando a proposta de baixo acoplamento e alta coesão:

Acoplamento

Dizemos sobre acoplamento em um software quando as partes que o compõe são altamente dependentes umas das outras o que dificulta a manutenção os testes e ainda mais mudanças.

Coesão

Dizemos sobre baixa coesão em um software quando uma parte dele realiza diversas tarefas ou possui multiplas responsabilidades.

Buscamos sempre um sistema que tenha baixo acoplamento e alta coesão. Na imagem abaixo vemos como a Arquitetura Limpa demonstra como resolver essas questões:

Começando do centro pra fora:

Entidades

A Entidade é a camada mais ao centro e mais alta na Arquitetura Limpa, é aqui onde devem ficar os objetos de domínio da aplicação, as regras de negócio cruciais e que não irão mudar com facilidade.

Casos de Uso

Casos de uso contém regras de negócio mais específicas referente à aplicação, ele especifíca a entrada a ser fornecida, a saída a ser retornada e os passos de processamento envolvidos.

Adaptadores de Interface

Camada que tem como finalidade converter dados da maneira mais acessível e conveniente possível para as camadas Entidades e Casos de Uso. Um exemplo seria o uso de Mapper's, onde eu poderia controlar as estruturas transmitidas entre Casos de Uso e Entidades com o interface do usuário, por exemplo.

Frameworks e Drivers

Contém qualquer frameworks ou ferramentas para poder rodar na aplicação.

Exemplo prático

Ápos toda a teoria vamos mostrar na prática com um projeto simples onde teremos três pontos de entrada da aplicação.

Com o desenrolar do projeto vamos perceber que nesse modelo arquitetural o mais importante são as camadas mais internas e as mais externas serão tratados como detalhe e é aí que mora a quebra de tabu da Arquitetura Limpa, pois o foco é no negócio e não nas tecnologias; mas ainda estamos trabalhando com um sistema automatizado e precisamos seguir alguns paradigmas, mas só o que é realmente indispensável.

Vamos fazer um projeto de cadastro de Power Rangers, nele um usuário vai enviar os seus dados e a aplicação irá criar um Ranger de uma cor dependendo de algumas características.

Vamos usar a linguagem Java como um projeto modular Maven e a partir dele conseguimos modularizar as nossas camadas.

Criando projeto

Dentro do nosso diretória vmos criar um arquivos pom.xml, esse arquivos vai ser o raiz da nossa aplicação, é nele que teremos as dependências declaradas com suas versões que usuaremos e também teremos aqui a declaração dos nossos módulos, segue o exemplo:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gogo.powerrangers</groupId>
    <artifactId>clean-architecture-example</artifactId>
    <packaging>pom</packaging>
    <version>1.0</version>
    <modules>
        <module>entity</module>
    </modules>

    <properties>
        <revision>1.0</revision>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>11</java.version>
        <lombok-version>1.18.10</lombok-version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <junit-jupiter.version>5.5.1</junit-jupiter.version>
        <junit-platform>1.5.1</junit-platform>
    </properties>

    <dependencyManagement>
        <dependencies>

            <!-- Jupiter -->
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-api</artifactId>
                <version>${junit-jupiter.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-engine</artifactId>
                <version>${junit-jupiter.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.junit.platform</groupId>
                <artifactId>junit-platform-launcher</artifactId>
                <version>${junit-platform}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.junit.platform</groupId>
                <artifactId>junit-platform-runner</artifactId>
                <version>${junit-platform}</version>
                <scope>test</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>


</project>
Enter fullscreen mode Exit fullscreen mode

Aqui temos o mínimo para começar, temos a declaração da versão do java, o JUnit que será o nosso framework de testes unitários e temos a declaração do nosso primeiro módulo chamado entity.

Entidade

Agora vamos para a nossa entidade, esse é o ponto mais ao centro e mais acima do nosso projeto, dentro dele devemos ter os nossos objetos de domínio e regras de negócio que podem viver sem um sistema automatizado. Esse é o módulo que será visto por todos os outros mas não conhece os demais, ele é totalmente isolado de dependências externas.

Então pensando no nosso projeto aqui modelamos o nosso domínio de ususários, onde vamos ter o nome, email, idade, personalidade e o nome do ranger que será criado.

Vamos criar um diretório onde está o nosso pom raiz com o nome de entity e dentro dele vamos criar o pom.xml da nossa entity:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <artifactId>clean-architecture-example</artifactId>
        <groupId>com.gogo.powerrangers</groupId>
        <version>1.0</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>entity</artifactId>
    <version>${revision}</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <!-- Jupiter -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-runner</artifactId>
        </dependency>

    </dependencies>

</project>
Enter fullscreen mode Exit fullscreen mode

Podemos ver que nesse pom temos somente a versão do Java e o JUnit que nos ajudará a fazer os testes unitários.

Vamos criar a nosso primeira classe de domínio que chamaremos de User e vamos ter os atributos que definimos do nosso usuários:

package com.gogo.powerrangers.entity;

public class User {

    private final String name;
    private final String email;
    private final int age;
    private final Personality personality;
    private final String ranger;

    User(String name, String email, int age, Personality personality, String ranger) {
        this.name = name;
        this.email = email;
        this.age = age;
        this.personality = personality;
        this.ranger = ranger;
    }

    public String getRanger() {
        return ranger;
    }
    public String getName() {
        return name;
    }
    public String getEmail() {
        return email;
    }
    public int getAge() {
        return age;
    }
    public Personality getPersonality() {
        return personality;
    }
}
Enter fullscreen mode Exit fullscreen mode

Temos aqui a nossa entidade e colocamos um Enum pra Personalidade e um construtor com acessibilidade package default que é um construtor que só pode ser acessado dentro do pacote onde ele foi declarado e criamos apenas os getters.

Agora temos a nossa entidade que é um POJO e vamos adicionar nesse projeto a regra de negócio que pode viver sem existir uma aplicação.

No universo dos Power Rangers cada ranger é escolhido de acordo com a sua personalidade, pra cada tipo de personalidade uma cor diferente e essa regra é independente de existir um software ou não, é uma regra dos Power Rangers então essa regra pertence a Entidade.

Pra isso eu vou separar essa definição para acontecer no momento em que um usuário for criado, então vamos usar um padrão de Builder para criar o nosso usuário e definir qual a cor do ranger de acordo com a sua pernsonalidade:

package com.gogo.powerrangers.entity;

public final class UserBuilder {

    private String name;
    private String email;
    private int age;
    private Personality personality;
    private String ranger;

    UserBuilder() {
    }

    public UserBuilder name(String name) {
        this.name = name;
        return this;
    }

    public UserBuilder email(String email) {
        this.email = email;
        return this;
    }

    public UserBuilder age(int age) {
        this.age = age;
        return this;
    }

    public UserBuilder personality(String personality) {
        this.personality = Personality.of(personality);
        this.ranger = this.discoverRanger(this.personality);
        return this;
    }

    public User build() {
        return new User(this.name, this.email, this.age, this.personality, this.ranger);
    }

    private String discoverRanger(Personality personality) {
        switch (personality) {
            case LIDERANCA:
                return "Vermelho";

            case ENTUSIASMO:
                return "Preto";

            case TRANQUILIDADE:
                return "Amarelo";

            case INTELIGENCIA:
                return "Azul";

            case RIQUEZA:
                return "Rosa";

            case PERSISTENCIA:
                return "Verde";

            case FORCA:
                return "Branco";

            default:
                return "";
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Aqui temos a criação de um User e já temos a definição da cor do ranger de acordo com a personalidade, vamos também adicionar o nosso builder dentro da nossa classe User:

    public static UserBuilder builder() {
        return new UserBuilder();
    }
Enter fullscreen mode Exit fullscreen mode

Casos de uso

Agora que criamos a nossa entidade vamos criar a próxima camada que são os Casos de Uso. Nem toda regra de negócio é pura como a regra de negócio que está na Entidade, algumas regras de negócio fazem sentido existirem em um sistema automatizado, software, e é aqui que eles são usados, nos Casos de Uso, aqui faremos validações, controle de fluxo e temos as portas de comunicação com os adapatadores, como no caso de persistência de dados.

Então vamos criar um novo diretório chamado usecase e nele teremos um arquivo pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>clean-architecture-example</artifactId>
        <groupId>com.gogo.powerrangers</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>usecase</artifactId>
    <version>${revision}</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>entity</artifactId>
            <version>${revision}</version>
        </dependency>

        <!-- Unit Test -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-runner</artifactId>
        </dependency>
    </dependencies>
</project>
Enter fullscreen mode Exit fullscreen mode

Aqui temos a dependência da entity e vamos criar o nosso primeiro caso de uso, a criação de um usuário. Criamos uma classe com nome CreateUser com um método create que irá receber um User e irá aplicar as validações necessárias para criar um usuário e iremos persistir essa informação em algum lugar. Não especificamos onde iremos persistir pois aqui isso é um mero detalhe que não é da preocupação dos Casos de Uso, podemos usar JDBC puro, Spring Data, cache em memória ou arquivo de texto essa responsabilidade não nos interessa aqui.

package com.gogo.powerrangers.usecase;

import com.gogo.powerrangers.entity.User;

public class CreateUser {

    public CreateUser() {

    }

    public User create(final User user) {


        return user;
    }
}
Enter fullscreen mode Exit fullscreen mode

Vamos adicionar primeiramente a nossa validação, aqui vamos imaginar que o usuário não pode ser menor de 18 anos e não pode ser repetido e iremos verificar pelo email essa informação.
Vamos criar uma classe chamada UserValidator:

package com.gogo.powerrangers.usecase.validator;

import com.gogo.powerrangers.entity.User;
import com.gogo.powerrangers.usecase.exception.PowerRangerNotFoundException;
import com.gogo.powerrangers.usecase.exception.UserValidationException;

import static java.util.Objects.isNull;

public class UserValidator {

    public static void validateCreateUser(final User user) {
        if(isNull(user)) {
            throw new UserValidationException("Usuario nao pode ser null");
        }
        if(user.getAge() < 18) {
            throw new UserValidationException("Usuario deve ser maior de 18 anos");
        }
        if(user.getPersonality().getPersonality().isEmpty()){
            throw new PowerRangerNotFoundException("Power Ranger não localizado com personalidade informada");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Aqui temos a nossa validação e customizamos as nossas Exceptions com a UserValidationException e a PowerRangerNotFoundException, em seguida acionamos o nosso método estático a nossa classe de criação de usuário:

package com.gogo.powerrangers.usecase;

import com.gogo.powerrangers.entity.User;
import com.gogo.powerrangers.usecase.exception.UserAlreadyExistsException;
import com.gogo.powerrangers.usecase.validator.UserValidator;

public class CreateUser {

    private final UserRepository repository;

    public CreateUser(UserRepository repository) {
        this.repository = repository;
    }

    public User create(final User user) {

        UserValidator.validateCreateUser(user);
        if (repository.findByEmail(user.getEmail()).isPresent()) {
            throw new UserAlreadyExistsException(user.getEmail());
        }

        return user;
    }
}

Enter fullscreen mode Exit fullscreen mode

Pronto temos a nossa validação e agora precisamos de alguma forma informar que queremos persistir essa informação, porém como fazer isso se os drivers e frameworks estão na camada mais externa e a ideia aqui é deixar o Caso de Uso desacoplado de deles?

Usaremos interfaces e inversão de controle, trocando em miúdos vamos dizer na nossa classe CreateUser que queremos salvar um usuário mas como ele será salvo já não nos importa.

Então vamos criar a interface UserRepository com os métodos que queremos:

package com.gogo.powerrangers.usecase.port;

import com.gogo.powerrangers.entity.User;

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

public interface UserRepository {

    User create(User user);

    Optional<User> findByEmail(String email);

    Optional<List<User>> findAllUsers();
}
Enter fullscreen mode Exit fullscreen mode

O resultado final da CreateUser fica:

package com.gogo.powerrangers.usecase;

import com.gogo.powerrangers.entity.User;
import com.gogo.powerrangers.usecase.exception.UserAlreadyExistsException;
import com.gogo.powerrangers.usecase.port.UserRepository;
import com.gogo.powerrangers.usecase.validator.UserValidator;

public class CreateUser {

    private final UserRepository repository;

    public CreateUser(UserRepository repository) {
        this.repository = repository;
    }

    public User create(final User user) {

        UserValidator.validateCreateUser(user);
        if (repository.findByEmail(user.getEmail()).isPresent()) {
            throw new UserAlreadyExistsException(user.getEmail());
        }

        var createdUser = repository.create(user);

        return createdUser;
    }
}
Enter fullscreen mode Exit fullscreen mode

Adaptadores de interface

Nessa camada podemos ver que exitem os nossos Controllers, Gateways e Presenters, aqui temos a comunicação pra dentro das nossa Entidades mas também a comunicação externa e representação do objeto de retorno que será exposto.

Vamos criar um diretório chamado adapter e dentro dele outro diretório chamado controller e um arquivos pom.xml que terá como dependência a entity e aa usecase:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>clean-architecture-example</artifactId>
        <groupId>com.gogo.powerrangers</groupId>
        <version>1.0</version>
        <relativePath>../../../clean-architecture-example/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>controller</artifactId>
    <version>${revision}</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>entity</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>usecase</artifactId>
            <version>${revision}</version>
        </dependency>
</project>
Enter fullscreen mode Exit fullscreen mode

Vamos começar aqui criando o nosso objeto de resposta da nossa aplicação, não queremos que a nossa entidade seja retornada aqui pois caso a apresentação seja alterada temos um ponto único de alteração e podemos ainda aqui realizar qualquer transformação que seja importante para exibição. Então criamos a classe UserModel:

package com.gogo.powerrangers.model;

import com.gogo.powerrangers.entity.User;

public class UserModel {

    private String name;
    private String email;
    private int age;
    private String personality;
    private String ranger;

    public static UserModel mapToUserModel(User user) {

        var userModel = new UserModel();
        userModel.name = user.getName();
        userModel.email = user.getEmail();
        userModel.age = user.getAge();
        userModel.personality = user.getPersonality().getPersonality();
        userModel.ranger = user.getRanger();

        return userModel;
    }

    public static User mapToUser(UserModel userModel) {
        //@formatter:off
        return User.builder().name(userModel.getName())
                             .age(userModel.getAge())
                             .email(userModel.getEmail())
                             .personality(userModel.getPersonality())
                             .build();
        //@formatter:on
    }

    @Override
    public String toString() {
        return "UserModel{" +
                "name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                ", personality='" + personality + '\'' +
                ", ranger='" + ranger + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPersonality() {
        return personality;
    }

    public void setPersonality(String personality) {
        this.personality = personality;
    }

    public String getRanger() {
        return ranger;
    }

    public void setRanger(String ranger) {
        this.ranger = ranger;
    }
}
Enter fullscreen mode Exit fullscreen mode

Aqui temos os métodos que fazem a mudança de Model-to-User e User-to-Model e agora vamos criar o nosso controlador:

package com.gogo.powerrangers;

import com.gogo.powerrangers.model.UserModel;
import com.gogo.powerrangers.usecase.CreateUser;

public class UserController {

    private final CreateUser createUser;

    public UserController(CreateUser createUser){
        this.createUser = createUser;
    }

    public UserModel createUser(UserModel userModel){

        var user = createUser.create(UserModel.mapToUser(userModel));

        return UserModel.mapToUserModel(user);
    }
}
Enter fullscreen mode Exit fullscreen mode

Frameworks e Drivers

Aqui é a nossa última camada, aqui temos os Drivers, Frameworks, UI e qualquer Dispositivo ou chamada externa em nossa aplicação é a camada mais "suja" pois é aqui que temos a entrada da nossa aplicação, ela conhece todas as outras camadas porém não é conhecida por nenhuma.

Qual o benefício disso?

O benefício é que com isso temos uma aplicação altamente desacoplada, as camadas mais internas não tem conhecimento de como a aplicação é executada, se estamos usando uma aplicação Web, linha de comando, desktop e etc, isso torna a aplicação plugável a qualquer framework ou driver, contanto que ele siga a contrato, interface, que definimos na camada de Caso de Uso.

Aqui vamos criar três pontos de entrada, um com Java puro executando por terminal e com um banco em memória, outro com Spring Boot e persistência com JDBC Template e outro com VertX e Hibernate.

Aplicação Java executada pelo terminal

Começando pela aplicação Java puro executado pelo terminal. Dentro do diretório adapter vamos criar um outro diretório chamado repository e dentro dele outro diretório chamado in-memory-db e dentro dele um arquivo pom.xm:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>clean-architecture-example</artifactId>
        <groupId>com.gogo.powerrangers</groupId>
        <version>1.0</version>
        <relativePath>../../../../clean-architecture-example/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>in-memory-db</artifactId>
    <version>${revision}</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>entity</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>usecase</artifactId>
            <version>${revision}</version>
        </dependency>

        <!-- Unit Test -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-runner</artifactId>
        </dependency>
    </dependencies>
</project>
Enter fullscreen mode Exit fullscreen mode

E vamos criar a classe InMemoryUserRepository que implementa UserRepository:

package com.gogo.powerrangers.db;

import com.gogo.powerrangers.entity.User;
import com.gogo.powerrangers.usecase.port.UserRepository;

import java.util.*;

public class InMemoryUserRepository implements UserRepository {

    private final Map<String, User> inMemoryDb = new HashMap<>();

    @Override
    public User create(User user) {
        inMemoryDb.put(user.getEmail(), user);
        return user;
    }

    @Override
    public Optional<User> findByEmail(String email) {
        return inMemoryDb.values().stream().filter(user -> user.getEmail().equals(email)).findAny();
    }

    @Override
    public Optional<List<User>> findAllUsers() {
        return Optional.of(new ArrayList<>(inMemoryDb.values()));
    }
}
Enter fullscreen mode Exit fullscreen mode

E aqui temos um Map e simulamos em cache as operações de persistência.

Agora vamos criar um diretório a partir do nosso diretório raiz chamada application e dentro desse repositório um diretórioa chamadp manual-app e dentro dele um pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>clean-architecture-example</artifactId>
        <groupId>com.gogo.powerrangers</groupId>
        <version>1.0</version>
        <relativePath>../../../clean-architecture-example/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>manual-app</artifactId>
    <version>${revision}</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <!-- Build an executable JAR -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.gogo.powerrangers.Main</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>entity</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>usecase</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>controller</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>in-memory-db</artifactId>
            <version>${revision}</version>
        </dependency>

        <!-- Unit Test -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-runner</artifactId>
        </dependency>
    </dependencies>
</project>
Enter fullscreen mode Exit fullscreen mode

Aqui podemos verificar que nas dependências temos acesso as outras camadas, agora precisamos criar a classe Main que irá executar essa aplicação, mas antes vamos precisar fazer o controle e injeção das dependências, pra vamos criar uma classe de configuração chamada ManualConfig:

package com.gogo.powerrangers.config;

import com.gogo.powerrangers.db.InMemoryUserRepository;
import com.gogo.powerrangers.usecase.CreateUser;
import com.gogo.powerrangers.usecase.SearchUser;

public class ManualConfig {

    private final InMemoryUserRepository dataBase = new InMemoryUserRepository();

    public CreateUser createUser(){
        return new CreateUser(dataBase);
    }
}
Enter fullscreen mode Exit fullscreen mode

Aqui temos a criação da instância do InMemoryUserRepository e a injeção dessa dependência na classe CreateUser que irá usar essa instância para realizar a persistência.

Vamos criar agora a classe Main:

package com.gogo.powerrangers;

import com.gogo.powerrangers.config.ManualConfig;
import com.gogo.powerrangers.model.UserModel;

public class Main {

    public static void main(String[] args) {

        var config = new ManualConfig();
        var createUser = config.createUser();
        var controller = new UserController(createUser);

        var userModel = new UserModel();
        userModel.setName(args[0]);
        userModel.setEmail(args[1]);
        userModel.setAge(Integer.parseInt(args[2]));
        userModel.setPersonality(args[3]);

        final var userCreated = controller.createUser(userModel);

        System.out.println(userCreated);
    }
}
Enter fullscreen mode Exit fullscreen mode

Se executarmos essa aplicação pelo terminal:

java -jar target/manual-app-1.0.jar Guilherme fake@mail.com 34 Persistência
Enter fullscreen mode Exit fullscreen mode

Temos o retorno:

UserModel{name='Guilherme', email='guiherme@gmail.com', age=34, personality='Persistência', ranger='Verde'}
Enter fullscreen mode Exit fullscreen mode

Spring Boot e JDBC Template

Agora vamos fazer a aplicação com um frameworks web e outro pra banco de dados.

Antes de mais nada vamos adicionar ao nosso pom raiz as dependências dos frameworks:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.3.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.3.0.RELEASE</version>
    <scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.200</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Agora vamos usar o JDBC Template, vamos então criar um diretório em repository chamado spring-jdbc e vamos criar o nosso pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>clean-architecture-example</artifactId>
        <groupId>com.gogo.powerrangers</groupId>
        <version>1.0</version>
        <relativePath>../../../../clean-architecture-example/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-jdbc</artifactId>
    <version>${revision}</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>entity</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>usecase</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
</project>
Enter fullscreen mode Exit fullscreen mode

O nosso pom agora tem as dependências das camadas da nossa aplicação e a dos drivers e framework jdbc, vamos criar o objeto que será persistido no nosso banco de dados chamado UserEntity:

package com.gogo.powerrangers.entity;

public class UserEntity {

    private String id;
    private String name;
    private String email;
    private int age;
    private String personality;
    private String ranger;

    public static User toUser(UserEntity entity){
        var user = User.builder().name(entity.getName()).age(entity.getAge())
                .email(entity.getEmail()).personality(entity.getPersonality()).build();

        return user;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPersonality() {
        return personality;
    }

    public void setPersonality(String personality) {
        this.personality = personality;
    }

    public String getRanger() {
        return ranger;
    }

    public void setRanger(String ranger) {
        this.ranger = ranger;
    }

}
Enter fullscreen mode Exit fullscreen mode

O JDBC Template pede para implementarmos uma interface RowMapper que nos auxilia no mapeamento do objeto que retorna do banco para o objeto UserEntity:

package com.gogo.powerrangers.mapper;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.gogo.powerrangers.entity.UserEntity;

public class UserRowMapper implements RowMapper<UserEntity> {

    @Override
    public UserEntity mapRow(ResultSet resultSet, int i) throws SQLException {

        UserEntity entity = new UserEntity();

        entity.setId(resultSet.getString("ID"));
        entity.setAge(resultSet.getInt("AGE"));
        entity.setEmail(resultSet.getString("EMAIL"));
        entity.setRanger(resultSet.getString("RANGER"));
        entity.setName(resultSet.getString("NAME"));
        entity.setPersonality(resultSet.getString("PERSONALITY"));

        return entity;
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora vamos implementar a nossa UserRepository numa classe chamada SpringJdbcUserRepository:

package com.gogo.powerrangers;

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

import javax.sql.DataSource;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

import com.gogo.powerrangers.entity.User;
import com.gogo.powerrangers.entity.UserEntity;
import com.gogo.powerrangers.mapper.UserRowMapper;
import com.gogo.powerrangers.usecase.port.UserRepository;

public class SpringJdbcUserRepository implements UserRepository {

    private JdbcTemplate jdbcTemplate;

    public DataSource dataSource(){
        return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:schema.sql").build();
    }

    public JdbcTemplate jdbcTemplate(){
        return new JdbcTemplate(this.dataSource());
    }

    public SpringJdbcUserRepository() {
        this.jdbcTemplate = this.jdbcTemplate();
    }

    @Override
    public User create(User user) {

        //@formatter:off
        String sql = new StringBuilder().append("INSERT INTO  ")
                                        .append(" USER(id, name, age, email, personality, ranger) ")
                                        .append(" VALUES(?, ?, ?, ?, ?, ?)").toString();
        //@formatter:on

        jdbcTemplate.update(sql, UUID.randomUUID().toString(), user.getName(), user.getAge(), user.getEmail(), user.getPersonality().getPersonality(), user.getRanger());

        return user;
    }

    @Override
    public Optional<User> findByEmail(String email) {

        String sql = "SELECT id, name, age, email, personality, ranger FROM USER WHERE email = ?";

        try {
            UserEntity userEntity = jdbcTemplate.queryForObject(sql, new UserRowMapper(), email);

            User user = UserEntity.toUser(userEntity);

            return Optional.of(user);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }

    @Override
    public Optional<List<User>> findAllUsers() {

        String sql = "SELECT id, name, age, email, personality, ranger FROM USER";

        List<UserEntity> userEntityList = jdbcTemplate.query(sql, new UserRowMapper());

        List<User> userList = userEntityList.stream().map(entity -> {
            return UserEntity.toUser(entity);
        }).collect(Collectors.toList());

        return Optional.of(userList);
    }
}
Enter fullscreen mode Exit fullscreen mode

Temos a nossa implementação da parte de persistência de dados e agora precisamos criar a aplicação web com Spring Boot.

No diretório application criamos outro diretório chamado spring-boot e dentro dele um arquivo pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>clean-architecture-example</artifactId>
        <groupId>com.gogo.powerrangers</groupId>
        <version>1.0</version>
        <relativePath>../../../clean-architecture-example/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot</artifactId>
    <version>${revision}</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>entity</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>usecase</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>controller</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Unit Test -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-runner</artifactId>
        </dependency>
    </dependencies>
</project>
Enter fullscreen mode Exit fullscreen mode

Aqui temos as dependências das camadas da nossa aplicação e as dependências do framework.

Quando usamos Spring Boot precisamos de uma classe principal que chamaremos de Application:

package com.gogo.powerrangers;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args){
        SpringApplication.run(Application.class, args);
    }
}
Enter fullscreen mode Exit fullscreen mode

Essa classe possui a annotation @SpringBootApplication e tudo o que é necessário para uma aplicação Spring Boot ser iniciada.

Mas agora precisamos fazer a nossa configuração de injeção de dependências e nisso o SPring Boot nos ajuda através dos Beans, então vamos criar uma classe de configuração chamada SpringBootConfig:

package com.gogo.powerrangers.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.gogo.powerrangers.SpringJdbcUserRepository;
import com.gogo.powerrangers.UserController;
import com.gogo.powerrangers.usecase.CreateUser;
import com.gogo.powerrangers.usecase.port.UserRepository;

@Configuration
public class SpringBootConfig {

    @Bean
    public UserRepository dataBase(){
        return new SpringJdbcUserRepository();
    }

    @Bean
    public CreateUser createUser(){
        return new CreateUser(this.dataBase());
    }

    @Bean
    public UserController userController(){
        return new UserController(this.createUser();
    }
}
Enter fullscreen mode Exit fullscreen mode

Aqui temos a annotation @Configuration que nos auxilia e indica ao Spring que aqui temos os nossos Beans que serão processados pelo container do Spring e deixarão esses Beans disponíveis para serem injetados na aplicação. Também temos os nosso Beans própriamente ditos e prontos para serem usados, então vamos a criação do nosso endpoint com a classe AddUserController:

package com.gogo.powerrangers.endpoint;

import com.gogo.powerrangers.UserController;
import com.gogo.powerrangers.model.UserModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/powerrangers")
public class AddUserController {

    @Autowired
    private UserController userController;

    @PostMapping("add")
    public ResponseEntity<UserModel> addUser(@RequestBody UserModel userModel){
        return ResponseEntity.ok(userController.createUser(userModel));
    }
}
Enter fullscreen mode Exit fullscreen mode

Aqui temos a nossa injeção através da annotation @Autowired da UserController e as declarações necessárias para a criação de um endpoint que recebe um UserModel através de um POST e faz a criação e persistência desse usuário.

VertX e Hibernate

Agora vamos criar uma aplicação com o framework VertX e com persistência de dados com o Hibernate. Para isso vamos começar com o Hibernate, criaremos um diretório dentro de repository com nome hibernate e nele criamos um arquivo pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>clean-architecture-example</artifactId>
        <groupId>com.gogo.powerrangers</groupId>
        <version>1.0</version>
        <relativePath>../../../../clean-architecture-example/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hibernate</artifactId>
    <version>${revision}</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>entity</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>usecase</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>

        <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-jpamodelgen</artifactId>
            </dependency>

            <!--Compile time JPA API -->
            <dependency>
                <groupId>javax.persistence</groupId>
                <artifactId>javax.persistence-api</artifactId>
            </dependency>

            <!--Runtime JPA implementation -->
            <dependency>
                <groupId>org.eclipse.persistence</groupId>
                <artifactId>eclipselink</artifactId>
            </dependency>
</project>
Enter fullscreen mode Exit fullscreen mode

No nosso pom temos nossas dependências e também adicionamos as dependências que são necessárias para o Hibernate funcionar. O Hibernate precisa de um arquivo de configuração dentro da pasta META-INF em resources chamado persistence.xml e dentro dele ficam as configurações das propriedades que o Hibernate usa:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
    version="2.2">

    <persistence-unit name="jpa-h2">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <class>com.gogo.powerrangers.entity.UserEntity</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <properties>
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
            <property name="javax.persistence.jdbc.driver"
                value="org.h2.Driver" />
            <property name="javax.persistence.jdbc.url"
                value="jdbc:h2:mem:test" />
            <property name="javax.persistence.jdbc.user" value="sa" />
            <property name="javax.persistence.jdbc.password" value="" />
            <property name="hibernate.dialect"
                value="org.hibernate.dialect.H2Dialect" />
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="show_sql" value="true" />
            <property name="hibernate.temp.use_jdbc_metadata_defaults"
                value="false" />
        </properties>
    </persistence-unit>

</persistence>
Enter fullscreen mode Exit fullscreen mode

Agora precisamos mapear o nosso objeto que vai representar a tabela no banco de dados:

package com.gogo.powerrangers.entity;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "USER")
public class UserEntity {

    @Id
    private String id;
    private String name;
    private String email;
    private int age;
    private String personality;
    private String ranger;

    public static User toUser(UserEntity entity) {
        var user = User.builder().name(entity.getName()).age(entity.getAge())
                .email(entity.getEmail()).personality(entity.getPersonality()).build();

        return user;
    }

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getPersonality() {
        return personality;
    }
    public void setPersonality(String personality) {
        this.personality = personality;
    }
    public String getRanger() {
        return ranger;
    }
    public void setRanger(String ranger) {
        this.ranger = ranger;
    }
}
Enter fullscreen mode Exit fullscreen mode

Aqui na UserEntity temos todas as anotações necessárias para o Hibernate. E agora vamos criar a nossa classe que irá implementar a UserRepository que chamaremos de HibernateUserRepository onde vamos criar a nossa instância do EntityManager para gerenciar as nossas transações com o banco de dados:

package com.gogo.powerrangers;

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

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

import com.gogo.powerrangers.entity.User;
import com.gogo.powerrangers.entity.UserEntity;
import com.gogo.powerrangers.usecase.port.UserRepository;

public class HibernateUserRepository implements UserRepository{

    private EntityManagerFactory emf = null;

    public HibernateUserRepository() {
        emf = Persistence.createEntityManagerFactory("jpa-h2");
    }

    @Override
    public User create(User user) {
        EntityManager entityManager = emf.createEntityManager();
        entityManager.getTransaction().begin();

        UserEntity entity = new UserEntity();
        entity.setId(UUID.randomUUID().toString());
        entity.setName(user.getName());
        entity.setEmail(user.getEmail());
        entity.setAge(user.getAge());
        entity.setPersonality(user.getPersonality().getPersonality());
        entity.setRanger(user.getRanger());

        entityManager.persist(entity);
        entityManager.getTransaction().commit();
        entityManager.close();

        return user;
    }

    @Override
    public Optional<User> findByEmail(String email) {
        EntityManager entityManager = emf.createEntityManager();

        //@formatter:off
        TypedQuery<UserEntity> query = entityManager.createQuery(new StringBuilder()
                .append("SELECT user ")
                .append("   FROM UserEntity user ")
                .append(" WHERE user.email = :email").toString(), UserEntity.class);
        // @formatter:on

        try {
            UserEntity userEntity = query.setParameter("email", email).getSingleResult();

            return Optional.of(UserEntity.toUser(userEntity));
        } catch (NoResultException e) {
            return Optional.empty();
        }
    }

    @Override
    public Optional<List<User>> findAllUsers() {
        EntityManager entityManager = emf.createEntityManager();

        List<UserEntity> userEntityList = entityManager.createQuery("SELECT user FROM UserEntity user", UserEntity.class).getResultList();

        List<User> userList = userEntityList.stream().map(UserEntity::toUser).collect(Collectors.toList());

        return Optional.of(userList);
    }

}
Enter fullscreen mode Exit fullscreen mode

Agora vamos criar a nossa aplicação com o framework VertX, vamos no diretório application e criar uma pasta chamada vertx e adicionar o pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>clean-architecture-example</artifactId>
        <groupId>com.gogo.powerrangers</groupId>
        <version>1.0</version>
        <relativePath>../../../clean-architecture-example/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>vertx</artifactId>
    <version>${revision}</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>entity</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>usecase</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>controller</artifactId>
            <version>${revision}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.vertx/vertx-web -->
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>

        <dependency>
            <groupId>com.gogo.powerrangers</groupId>
            <artifactId>hibernate</artifactId>
            <version>${revision}</version>
        </dependency>
</project>
Enter fullscreen mode Exit fullscreen mode

Temos as nossas dependências das camadas internas, as dependências do VertX e a dependência do jackson-core que nos ajuda com o nosso endpoint. E agora vamos criar a nossa classe de configuração onde teremos a injeção das nossas dependências:

package com.gogo.powerrangers.config;

import com.gogo.powerrangers.HibernateUserRepository;
import com.gogo.powerrangers.usecase.CreateUser;
import com.gogo.powerrangers.usecase.port.UserRepository;

public class VertxConfig {

    public final UserRepository repository() {
        return new HibernateUserRepository();
    }

    public final CreateUser createUser() {
        return new CreateUser(this.repository());
    }
}
Enter fullscreen mode Exit fullscreen mode

E criaremos agora o nosso controller que utiliza a instâncai que foi injetada do nosso UserController

package com.gogo.powerrangers.endpoint;

import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;

public abstract class Controller {

    public boolean isNull(final Buffer buffer) {
        return buffer == null || "".equals(buffer.toString());
    }

    public void sendError(int statusCode, HttpServerResponse response) {
        response
                .putHeader("content-type", "application/json")
                .setStatusCode(statusCode)
                .end();
    }

    public void sendSuccess(JsonObject body, HttpServerResponse response) {
        response
                .putHeader("content-type", "application/json")
                .end(body.encodePrettily());
    }

}
Enter fullscreen mode Exit fullscreen mode
package com.gogo.powerrangers.endpoint;

import com.gogo.powerrangers.UserController;
import com.gogo.powerrangers.model.UserModel;

import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;

public class AddUserController extends Controller{

    private final UserController controller;

    public AddUserController(UserController controller) {
        this.controller = controller;
    }

    public void createUser(final RoutingContext routingContext) {
        var response = routingContext.response();
        var body = routingContext.getBody();
        if (isNull(body)) {
            sendError(400, response);
        } else {
            var userModel = body.toJsonObject().mapTo(UserModel.class);
            var user = controller.createUser(userModel);
            var result = JsonObject.mapFrom(user);
            sendSuccess(result, response);
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

Temos aqui uma classe abstrata Controller que serve apenas como utilitário que outros controllers podem usar.

Agora precisamos o nosso ponto de entrada da aplicação e fazer as configurações do VertX:

package com.gogo.powerrangers;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.gogo.powerrangers.config.VertxConfig;
import com.gogo.powerrangers.endpoint.AddUserController;s

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Launcher;
import io.vertx.core.json.Json;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;

public class VertxApplication extends AbstractVerticle{

    private final VertxConfig config = new VertxConfig();
    private final UserController userController = new UserController(config.createUser(), config.searchUser());
    private final AddUserController addUserController = new AddUserController(userController);

    @Override
    public void start() {
        Json.mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        var router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        router.post("/add").handler(addUserController::createUser);

        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }

    public static void main(String[] args) {
        Launcher.executeCommand("run", VertxApplication.class.getName());
    }
}
Enter fullscreen mode Exit fullscreen mode

Aqui temos as configurações do VertX e rotas no método start e o main que é o ponto de entrada.

Conclusão

Podemos ver que com esse modelo de arquitetura temos uma aplicação plugável, quer dizer essa aplicação pode usar outras camadas sem que isso tenha impacto direto nas camadas mais internas. Vimos também que o foco está na regra de negócio e a facilidade em fazer testes é maior.

Prós

  • Independente de Framework
  • Altamente testável
  • Independente de UI
  • Independente de Banco de Dados
  • Independente de qualquer agente externo

Contras

  • Maior curva de aprendizado
  • Mais classes, pacotes e mais sub-projetos

link do projeto

Discussion (0)