Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Nesse artigo vamos te ensinar a configurar o Maven Multi-Module em um projeto Spring Boot, essa configuração vai te permitir criar bibliotecas internas dentro do mesmo repositório possibilitando o compartilhamento de código comuns entre aplicações.
Quando trabalhamos com projetos que requerem o uso de microsserviços em quase todos os cenários possíveis será necessário interação entre esses serviços, seja por meio de uma chamada REST ou por meios assíncronos como o uso de filas, quando esse tipo de interação acontece é comum se deparar com o reuso das mesmas classes que são usadas para a comunicação, ao invés de rescrever a mesma classe ou copiar para o repositório é possível escreve-la apenas uma vez e reutilizá-la em vários dos seus microsserviços, para isso existem alguns tipos de abordagem diferentes.
Para esse compartilhamento de código pode-se utilizar a estratégia de criar uma biblioteca interna e hospedá-la para que seja instalada via gerenciador de pacotes no código, esse tipo de abordagem traz bastante benefícios, pois é possível reutilizá-la em praticamente qualquer tipo de projeto até os que não estão no mesmo contexto da sua aplicação, mas um grande desafio é gerenciar as releases de modo a não impactar o desenvolvimento dos outros projetos.
Outro desafio é encontrar o repositório ideal que irá atender suas expectativas nesse caso, lembre-se você não precisa de um repositório de código e sim um repositório de biblioteca ele deve armazenar a sua lib depois do build, existem inúmeros no mercado e para ambientes clouds elas possuem os seus próprios.
Mas também existe uma outra estratégia bem interessante que não exige tanta infraestrutura e o controle de versionamento pode ficar um pouco mais simplificado, nesse caso estamos falando de criar um monorepo um repositório que irá armazenar não só o código de sua aplicação mas também das bibliotecas que ajudaram a compartilhar código entre suas diferentes aplicações, para o uso dessa abordagem podemos usar um feature nativa do Maven o Multi-Module.
A seguir vamos abordar a criação de um projeto usando o Multi-Module do Maven em um projeto Spring Boot, pois é o framework mais utilizado no mercado nos dias atuais, e vamos ajudar a solucionar alguns desafios que podem aparecer quando se utiliza essa abordagem.
Para dar inicio a configuração do nosso projeto, vamos criar um primeiro projeto Maven através da linha de comando:
mvn archetype:generate -DgroupId=com.artefatox -DartifactId=microservices
TerminalNo exemplo acima estamos criando o projeto com o groupId “com.artefatox” e o artifactId como “microservices”, é importante ajustar esses nomes para o contexto do seu projeto.
No decorrer da configuração serão feitas algumas perguntas no terminal, como versão do projeto entre outras, geralmente ele já lhe dá a melhor opção pré-preenchida então na maioria dos casos a menos que você necessite de uma opção mais personalizada é só confirmar com Enter.
Após o termino da configuração podemos navegar até do projeto criada e analisar o seu arquivo pom.xml, note que é um projeto Maven comum.
Vamos precisar adicionar uma informação no pom.xml para que o maven passe a entender que o projeto terá um pais e os modulos que podemos criar sejam eles serviços ou bibliotecas serão os filhos, vamos adicionar a informação de packaging, como você pode conferir abaixo na linha 12.
<?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.artefatox</groupId>
<artifactId>microservices</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Microservices</name>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
pom.xmlAgora já estamos habilitados a adicionar sub-módulos, para isso vamos utilizar o terminal:
mvn archetype:generate -DgroupId=com.artefatox -DartifactId=core
mvn archetype:generate -DgroupId=com.artefatox -DartifactId=repository
mvn archetype:generate -DgroupId=com.artefatox -DartifactId=worker
mvn archetype:generate -DgroupId=com.artefatox -DartifactId=api
TerminalPara o nosso exemplo vamos criar 4 sub módulos: core, repository, worker e api, estamos abordando a criação dessa forma pois a nossa necessidade é que dois projetos acessem os mesmos dados de um banco, e para reutilização das classes de conexão com o banco e dos nossos modelos de entidade vamos separá-los no pacote repository.
Após adicionar os sub-módulos note que no arquivo pom.xml tem a referência para esses pacotes internos:
<modules>
<module>core</module>
<module>repository</module>
<module>worker</module>
<module>api</module>
</modules>
pom.xmlE quando fazemos um comando de build na raiz do nosso projeto todos os projetos são compilados:
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ api ---
[INFO] Building jar: /home/mauricio/workspace/microservices/api/target/api-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Microservices 1.0-SNAPSHOT:
[INFO]
[INFO] Core ............................................... SUCCESS [ 0.389 s]
[INFO] Microservices ...................................... SUCCESS [ 0.002 s]
[INFO] Repository ......................................... SUCCESS [ 0.720 s]
[INFO] Worker ............................................. SUCCESS [ 0.460 s]
[INFO] Api ................................................ SUCCESS [ 0.449 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.073 s
[INFO] Finished at: 2024-03-29T11:57:51-03:00
[INFO] ------------------------------------------------------------------------
TerminalImportante que a cada adição de sub-módulo você adicione em seu pom.xml o packaging como jar, e conferir se o sub-módulo está apontando para o projeto pai, como demonstramos no exemplo a seguir:
<parent>
<groupId>com.artefatox</groupId>
<artifactId>microservices</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.artefatox</groupId>
<artifactId>core</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Core</name>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
pom.xmlAgora nossa estrutura está basicamente pronta, precisamos agora adicionar as dependências do nosso framework no caso do Spring Boot.
Como primeiro passo precisamos transformar o nosso projeto pai como um filho do projeto Spring assim o gerenciamento de dependências ficará mais simples, para o atual momento vamos utilizar a versão 3.2.4 do Spring que é a versão estável mais atual.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
pom.xmlTambém será necessário adicionar as configurações de build do projeto e essas configurações serão herdadas por todos os projetos filho.
Como um opcional vamos aproveitar para incluir algumas bibliotecas que nós utilizamos bastante e que facilita o desenvolvimento de aplicações complexas, que é o Lombok e MapStruct, temos artigo para configurar essas bibliotecas em projetos comuns, caso se interesse acesse aqui.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.artefatox</groupId>
<artifactId>microservices</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Microservices</name>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
<org.projectlombok.version>1.18.24</org.projectlombok.version>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
</properties>
<modules>
<module>core</module>
<module>repository</module>
<module>worker</module>
<module>api</module>
</modules>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.1.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<compilerArg>
-Amapstruct.defaultComponentModel=spring</compilerArg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
pom.xmlVeja como ficou a configuração dos nosso pom.xml, depois da adição das bibliotecas que serão compartilhadas entre os projetos filhos e também a configuração de build do projeto aproveitando o MapStruct e o Lombok.
Agora precisamos adicionar as bibliotecas dos módulos do Spring nos sub-módulos que fazem sentido, exemplo vamos adicionar o Spring JPA no módulo de repositório e o Spring Web no módulo Api.
Agora que temos todas as dependências externas configuradas vamos utilizar os módulos que nós criamos entre os nossos projetos, como premissa vamos adotar que todos os módulos precisaram ter como dependência o módulo core, pois nele devemos colocar as principais classes mais comuns do nosso projeto, e conforme a necessidade vamos adicionando os outros módulos.
No nosso exemplo vamos fazer o módulo Api utilizar o módulo Repository para se utilizar das classes de entidade do banco de dados e da interface Repository que nos é entregue pelo próprio framework do Spring.
Repository 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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>microservices</artifactId>
<groupId>com.artefatox</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>repository</artifactId>
<name>Repository</name>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.artefatox</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
pom.xmlApi 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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>microservices</artifactId>
<groupId>com.artefatox</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>api</artifactId>
<name>Api</name>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.artefatox</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.artefatox</groupId>
<artifactId>repository</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
pom.xmlPara testar se a nossa configuração ocorreu de forma correta vamos exemplificar criando dois endpoints bem simples, um que registra uma informação no banco de dados e o outro que lê as informações registradas.
Vamos começar nossa implementação criando a classe entidade do banco de dados e também a interface que conterá os métodos necessários para as operações, para isso vamos criar no nosso módulo Repository:
package com.artefatox.repository.demo;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Data
@Entity
public class Demo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String text;
}
ApiApplication.javapackage com.artefatox.repository.demo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface DemoRepository extends JpaRepository<Demo, Long> {
}
ApiApplication.javaImplementações corriqueiras para quem já está habituado a utilizar o framework.
Agora vamos nos concentrar no nosso módulo Api, pois ele será a nossa aplicação em si, o módulo repository server para nós como uma biblioteca compartilhada.
Para que se comporte como uma aplicação Spring boot temos que configurar a classe main no padrão do framework:
package com.artefatox;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
ApiApplication.javaE a nossa classe controller para receber as requisições REST:
package com.artefatox.api.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
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;
import com.artefatox.repository.demo.Demo;
import com.artefatox.repository.demo.DemoRepository;
import jakarta.transaction.Transactional;
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private DemoRepository repository;
@GetMapping
public List<Demo> list() {
return repository.findAll();
}
@PostMapping
@Transactional
public Demo create(@RequestBody Demo demo) {
return repository.save(demo);
}
}
ApiApplication.javaNa nossa classe controller já estamos utilizando as implementações da dependência repository como pode ser observado nas linhas 12 e 13.
Como o módulo Api é quem de fato será executado, precisamos fazer a configuração de conexão com o banco de dados nesse módulo, mas como estamos utilizando o framework Spring isso fica muito simples basta adicionar algumas informações ao arquivo application.properties que pode ser criado na pasta /src/main/resources
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=drop-and-create
application.propertiesNo nosso caso executamos uma instância do banco Postgres em um contêiner Docker para testar a aplicação.
Agora que as informações de conexão estão configuradas basta tentar executar a nossa aplicação normalmente, pode ser através dos recursos da sua IDE ou da linha de comando.
Quando você tentar executar pode acontecer um erro na injeção de dependência da nossa interface de repositório, para solucionar isso precisamos indicar para o Spring que ele precisa escanear os pacotes para identificar as classes de entidade e de repositório, essa configuração é bem simples, basta adicionar algumas anotações na classe principal da aplicação:
package com.artefatox;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EntityScan(basePackages = "com.artefatox")
@EnableJpaRepositories(basePackages = "com.artefatox")
@SpringBootApplication(scanBasePackages = "com.artefatox")
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
ApiApplication.javaAgora sim podemos executar nossa aplicação e realizar testes para ver se tudo está funcionando corretamente.
Vamos criar uma entrada simples utilizando o endpoint do tipo POST:
curl --request POST \
--url http://localhost:8080/demo \
--header 'Content-Type: application/json' \
--data '{"text": "Olá mundo"}'
TerminalE em seguida ler a informação no endpoint to tipo GET:
curl --request GET --url http://localhost:8080/demo
TerminalEsse tipo de abordagem traz muitos benefícios principalmente quando se identifica a necessidade de ter muito código compartilhado, isso quase sempre acontece quando se trabalha com microsserviços e eles precisam se comunicar entre si.
Esse tipo de abordagem também traz alguns desafios principalmente relacionados a maturidade da equipe que está trabalhando com ele, pois uma alteração nos módulos que servem como dependência pode ocasionar bugs indesejados, então é necessário que a equipe a trabalhar esteja bem cientes das dependências entre os vários módulos que o projeto pode ter, e ter uma organização muito boa para manter o projeto, além de tentar utilizar padrões já conhecidos para facilitar o ingresso de novos colaboradores.