Category Archives: Tecnologia

Java Singleton

Implementação do Padrão Java Singleton

Para implementar um padrão singleton, temos diferentes abordagens, mas todas elas têm os seguintes conceitos comuns.

  • Construtor privado para restringir a instanciação da classe de outras classes.
  • Variável estática privada da mesma classe que é a única instância da classe.
  • Método estático público que retorna a instância da classe. Este é o ponto de acesso global para o mundo externo obter a instância da classe singleton.

Nas próximas seções, aprenderemos diferentes abordagens para implementação de padrões singleton e preocupações de design com a implementação.

1. Inicialização rápida

Na inicialização ansiosa, a instância da classe singleton é criada no momento do carregamento da classe. A desvantagem da inicialização ansiosa é que o método é criado mesmo que o aplicativo cliente não o esteja usando. Aqui está a implementação da classe singleton de inicialização estática:

package com.journaldev.singleton;

public class EagerInitializedSingleton {

    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

    // private constructor to avoid client applications using the constructor
    private EagerInitializedSingleton(){}

    public static EagerInitializedSingleton getInstance() {
        return instance;
    }
}

Se sua classe singleton não estiver usando muitos recursos, essa é a abordagem a ser usada. Mas na maioria dos cenários, classes singleton são criadas para recursos como Sistema de Arquivos, conexões de Banco de Dados, etc. Devemos evitar a instanciação, a menos que o cliente chame o getInstancemétodo. Além disso, esse método não fornece nenhuma opção para tratamento de exceções.

2. Inicialização de bloco estático

A implementação da inicialização de bloco estático é semelhante à inicialização ansiosa, exceto que a instância da classe é criada no bloco estático que fornece a opção de tratamento de exceções .

package com.journaldev.singleton;

public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;

    private StaticBlockSingleton(){}

    // static block initialization for exception handling
    static {
        try {
            instance = new StaticBlockSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Exception occurred in creating singleton instance");
        }
    }

    public static StaticBlockSingleton getInstance() {
        return instance;
    }
}

Tanto a inicialização rápida quanto a inicialização de bloco estático criam a instância antes mesmo que ela seja usada, e essa não é a melhor prática a ser usada.

3. Inicialização preguiçosa

O método de inicialização lazy para implementar o padrão singleton cria a instância no método de acesso global. Aqui está o código de exemplo para criar a classe singleton com esta abordagem:

package com.journaldev.singleton;

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;

    private LazyInitializedSingleton(){}

    public static LazyInitializedSingleton getInstance() {
        if (instance == null) {
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

A implementação anterior funciona bem no caso do ambiente single-threaded, mas quando se trata de sistemas multi-threaded, pode causar problemas se vários threads estiverem dentro da ifcondição ao mesmo tempo. Isso destruirá o padrão singleton e ambos os threads obterão instâncias diferentes da classe singleton. Na próxima seção, veremos diferentes maneiras de criar uma classe singleton thread-safe .

4. Singleton seguro para threads

Uma maneira simples de criar uma classe singleton thread-safe é fazer com que o método de acesso global seja sincronizado para que apenas uma thread possa executar esse método por vez. Aqui está uma implementação geral dessa abordagem:

package com.journaldev.singleton;

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton(){}

    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }

}

A implementação anterior funciona bem e fornece segurança de thread, mas reduz o desempenho devido ao custo associado ao método synchronized, embora precisemos dele apenas para os primeiros threads que podem criar instâncias separadas. Para evitar essa sobrecarga extra toda vez, o princípio de bloqueio verificado duas vezes é usado. Nessa abordagem, o bloco synchronized é usado dentro da ifcondição com uma verificação adicional para garantir que apenas uma instância de uma classe singleton seja criada. O seguinte trecho de código fornece a implementação de bloqueio verificado duas vezes:

public static ThreadSafeSingleton getInstanceUsingDoubleLocking() {
    if (instance == null) {
        synchronized (ThreadSafeSingleton.class) {
            if (instance == null) {
                instance = new ThreadSafeSingleton();
            }
        }
    }
    return instance;
}

Continue seu aprendizado com a classe Thread Safe Singleton .

5. Implementação de Bill Pugh Singleton

Antes do Java 5, o modelo de memória Java tinha muitos problemas, e as abordagens anteriores costumavam falhar em certos cenários em que muitos threads tentavam obter a instância da classe singleton simultaneamente. Então Bill Pugh surgiu com uma abordagem diferente para criar a classe singleton usando uma classe auxiliar estática interna . Aqui está um exemplo da implementação do Singleton de Bill Pugh:

package com.journaldev.singleton;

public class BillPughSingleton {

    private BillPughSingleton(){}

    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

Observe a classe estática interna privada que contém a instância da classe singleton. Quando a classe singleton é carregada, SingletonHelpera classe não é carregada na memória e somente quando alguém chama o getInstance()método, essa classe é carregada e cria a instância da classe singleton. Essa é a abordagem mais amplamente usada para a classe singleton, pois não requer sincronização.

6. Usando Reflexão para destruir o Padrão Singleton

A reflexão pode ser usada para destruir todas as abordagens de implementação singleton anteriores. Aqui está uma classe de exemplo:

package com.journaldev.singleton;

import java.lang.reflect.Constructor;

public class ReflectionSingletonTest {

    public static void main(String[] args) {
        EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton instanceTwo = null;
        try {
            Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                // This code will destroy the singleton pattern
                constructor.setAccessible(true);
                instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(instanceOne.hashCode());
        System.out.println(instanceTwo.hashCode());
    }

}

Ao executar a classe de teste anterior, você notará que hashCodeambas as instâncias não são as mesmas, o que destrói o padrão singleton. O Reflection é muito poderoso e usado em muitos frameworks como Spring e Hibernate. Continue seu aprendizado com o Tutorial Java Reflection .

7. Enumeração Singleton

Para superar essa situação com o Reflection, Joshua Bloch sugere o uso de enumpara implementar o padrão de design singleton, já que Java garante que qualquer enumvalor seja instanciado apenas uma vez em um programa Java. Como os valores Java Enum são globalmente acessíveis, o singleton também é. A desvantagem é que o enumtipo é um tanto inflexível (por exemplo, ele não permite inicialização preguiçosa).

package com.journaldev.singleton;

public enum EnumSingleton {

    INSTANCE;

    public static void doSomething() {
        // do something
    }
}

8. Serialização e Singleton

Às vezes, em sistemas distribuídos, precisamos implementar Serializableinterface na classe singleton para que possamos armazenar seu estado no sistema de arquivos e recuperá-lo em um momento posterior. Aqui está uma pequena classe singleton que implementa Serializableinterface também:

package com.journaldev.singleton;

import java.io.Serializable;

public class SerializedSingleton implements Serializable {

    private static final long serialVersionUID = -7604766932017737115L;

    private SerializedSingleton(){}

    private static class SingletonHelper {
        private static final SerializedSingleton instance = new SerializedSingleton();
    }

    public static SerializedSingleton getInstance() {
        return SingletonHelper.instance;
    }

}

O problema com a classe singleton serializada é que sempre que a desserializamos, ela criará uma nova instância da classe. Aqui está um exemplo:

package com.journaldev.singleton;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class SingletonSerializedTest {

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        SerializedSingleton instanceOne = SerializedSingleton.getInstance();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        // deserialize from file to object
        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "filename.ser"));
        SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
        in.close();

        System.out.println("instanceOne hashCode="+instanceOne.hashCode());
        System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());

    }

}

Esse código produz esta saída:

Output
instanceOne hashCode=2011117821
instanceTwo hashCode=109647522

Então ele destrói o padrão singleton. Para superar esse cenário, tudo o que precisamos fazer é fornecer a implementação do readResolve()método.

protected Object readResolve() {
    return getInstance();
}

Depois disso, você notará que hashCodeambas as instâncias são iguais no programa de teste.

Fonte: https://www.digitalocean.com/community/tutorials/java-singleton-design-pattern-best-practices-examples

Relacionamento SOLID e Design Patterns

Os princípios SOLID e os Design Patterns estão intimamente relacionados porque os padrões de projeto muitas vezes ajudam a implementar os princípios do SOLID na prática. Aqui está um mapeamento entre os princípios do SOLID e os padrões de projeto:


1. Single Responsibility Principle (SRP) – Princípio da Responsabilidade Única

Uma classe deve ter apenas um motivo para mudar.

Design Patterns Relacionados:

  • Facade – Cria uma interface simplificada para um conjunto de subsistemas, separando responsabilidades.

  • Decorator – Permite adicionar responsabilidades dinamicamente, evitando que uma única classe tenha muitas funções.

  • Adapter – Separa a conversão de interfaces em uma única responsabilidade.

  • Strategy – Separa algoritmos em classes específicas, reduzindo a quantidade de responsabilidades em uma classe principal.


2. Open/Closed Principle (OCP) – Princípio Aberto/Fechado

Classes devem estar abertas para extensão, mas fechadas para modificação.

Design Patterns Relacionados:

  • Strategy – Permite adicionar novos comportamentos sem modificar a estrutura existente.

  • Decorator – Estende funcionalidades sem alterar o código original.

  • Factory Method – Permite criar novos objetos sem modificar a classe base.

  • Template Method – Permite definir um esqueleto de algoritmo, permitindo extensões sem modificar a estrutura geral.


3. Liskov Substitution Principle (LSP) – Princípio da Substituição de Liskov

Subtipos devem ser substituíveis por seus tipos base sem quebrar o comportamento esperado.

Design Patterns Relacionados:

  • Factory Method – Garante que as classes criadas sigam a hierarquia correta.

  • Template Method – Garante que subclasses implementem corretamente um comportamento definido.

  • Bridge – Separa abstração da implementação, garantindo substituição sem problemas.


4. Interface Segregation Principle (ISP) – Princípio da Segregação de Interfaces

Uma interface grande deve ser dividida em interfaces menores e específicas para evitar que classes sejam forçadas a implementar métodos que não utilizam.

Design Patterns Relacionados:

  • Proxy – Cria interfaces específicas para diferentes clientes.

  • Bridge – Separa interfaces para evitar dependências desnecessárias.

  • Adapter – Converte interfaces para que cada classe utilize apenas o que precisa.


5. Dependency Inversion Principle (DIP) – Princípio da Inversão de Dependência

Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.

Design Patterns Relacionados:

  • Dependency Injection – Injeta dependências por meio de interfaces, evitando acoplamento.

  • Abstract Factory – Permite criar objetos sem depender de implementações concretas.

  • Factory Method – Desacopla a criação de objetos do código que os utiliza.

  • Observer – Desacopla os sujeitos dos seus observadores, garantindo flexibilidade.


Esse mapeamento ajuda a entender como os padrões de projeto podem ser usados para aplicar e reforçar os princípios do SOLID, tornando o código mais modular, flexível e fácil de manter.

Teste RESTful

Em Java, existem diversas ferramentas e bibliotecas para realizar chamadas RESTful de forma eficiente. Aqui estão as principais opções:

1. HttpURLConnection

  • É a abordagem mais antiga, disponível desde a JDK 1.1.

  • Permite realizar requisições HTTP básicas, mas exige mais esforço manual, como configurar cabeçalhos e lidar com streams de entrada e saída.

  • Exemplo de uso: Configuração manual de cabeçalhos e envio de dados no corpo da requisição usando OutputStream1.

2. HttpClient (Java 11+)

  • Introduzido no Java 11, é uma alternativa moderna ao HttpURLConnection.

  • Suporta chamadas síncronas e assíncronas com APIs mais fluentes.

  • Oferece suporte nativo para manipulação de JSON como strings ou streams.

  • Exemplo: Criação de requisições GET e POST de forma mais clara e menos verbosa1.

3. RestTemplate (Spring Framework)

  • Uma ferramenta do Spring para simplificar chamadas RESTful.

  • Ideal para aplicações Spring Boot, mas está sendo gradualmente substituída pelo WebClient.

  • Exemplo: Realiza chamadas HTTP com mapeamento automático de respostas JSON para objetos Java1.

4. WebClient (Spring WebFlux)

  • Substituto moderno do RestTemplate.

  • Suporta programação reativa, permitindo maior eficiência em aplicações que precisam lidar com muitas requisições simultâneas.

  • Exemplo: Utilizado para chamadas assíncronas e processamento reativo1.

5. OpenFeign (Spring Cloud)

  • Uma biblioteca declarativa para consumir APIs RESTful.

  • Permite definir interfaces que representam os endpoints da API, tornando o código mais limpo e legível.

  • Exemplo: Criação de clientes REST com anotações como @FeignClient1.

6. Lib HttpClient Utils

  • Uma biblioteca personalizada que simplifica a manipulação de requisições HTTP.

  • Oferece abstração adicional sobre o HttpClient, permitindo reduzir a complexidade do código.

  • Exemplo: Configuração de objetos de requisição e resposta com suporte integrado para JSON2.

Essas ferramentas atendem diferentes necessidades, desde projetos simples até aplicações complexas com alta demanda por performance. A escolha depende do contexto do projeto e das tecnologias utilizadas.

HttpClient – Exemplo

Vou desenvolver uma suíte de testes CRUD usando Java HttpClient para o endpoint /api/v1/user. Aqui está a implementação completa:

Configuração inicial

java
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;

public class UserApiTest {
private static final HttpClient httpClient = HttpClient.newHttpClient();
private static final String BASE_URL = "http://localhost:8080/api/v1/user";
private static String userId; // Armazenará o ID criado
private static final String USER_JSON = """
{
"login": "admin",
"password": "adm123",
"email": "admin@com.com"
}""";
}

1. Teste POST (Criação)

java
@Test
void testCreateUser() throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(USER_JSON))
.build();

HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());

// Validações
assertEquals(201, response.statusCode());
assertTrue(response.body().contains("admin@com.com"));
userId = extractUserId(response.body()); // Implementar extração do ID
}

2. Teste GET (Consulta)

java
@Test
void testGetUser() throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/" + userId))
.GET()
.build();

HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());

assertEquals(200, response.statusCode());
assertTrue(response.body().contains("admin"));
}

3. Teste PUT (Atualização)

java
@Test
void testUpdateUser() throws Exception {
String updatedJson = USER_JSON.replace("adm123", "newPassword123");

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/" + userId))
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString(updatedJson))
.build();

HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());

assertEquals(200, response.statusCode());
assertTrue(response.body().contains("newPassword123"));
}

4. Teste DELETE (Remoção)

java
@Test
void testDeleteUser() throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/" + userId))
.DELETE()
.build();

HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());

assertEquals(204, response.statusCode());

// Verifica se foi realmente removido
HttpResponse<String> getResponse = httpClient.send(
HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/" + userId))
.GET()
.build(),
BodyHandlers.ofString()
);
assertEquals(404, getResponse.statusCode());
}

Dependências necessárias (Maven):

xml
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>

Características da implementação:

  1. HttpClient moderno (Java 11+)38

  2. Padronização de headers com application/json46

  3. Manipulação assíncrona através de BodyHandlers68

  4. Validação multi-etapa no teste DELETE17

  5. Extração dinâmica do ID do usuário criado

  6. Template strings para JSON (Java 15+ text blocks)

Para executar, garanta que:

  1. A API esteja rodando em localhost:8080

  2. O JSON de resposta inclua o ID do usuário criado

  3. Os status HTTP estejam configurados conforme a API real

Esta suíte cobre todos os cenários básicos de CRUD com validações essenciais2510. Adapte os status codes e caminhos de acordo com sua implementação real da API.

RestTemplate – Exemplo

Vou desenvolver uma suíte de testes CRUD usando RestTemplate para o endpoint /api/v1/user. Aqui está a implementação completa:

Configuração base

java
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;

public class UserApiTest {
private static final RestTemplate restTemplate = new RestTemplate();
private static final String BASE_URL = "http://localhost:8080/api/v1/user";
private static String userId;
private static final String USER_JSON =
"{\"login\":\"admin\",\"password\":\"adm123\",\"email\":\"admin@com.com\"}";
}

1. Teste POST (Criação)

java
@Test
public void testCreateUser() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<String> request = new HttpEntity<>(USER_JSON, headers);

ResponseEntity<String> response = restTemplate.exchange(
BASE_URL,
HttpMethod.POST,
request,
String.class
);

assertEquals(HttpStatus.CREATED, response.getStatusCode());
userId = extractIdFromResponse(response.getBody()); // Implementar extração do ID
}

2. Teste GET (Consulta)

java
@Test
public void testGetUser() {
URI uri = UriComponentsBuilder.fromHttpUrl(BASE_URL)
.path("/{id}")
.buildAndExpand(userId)
.toUri();

ResponseEntity<User> response = restTemplate.exchange(
uri,
HttpMethod.GET,
null,
User.class
);

assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("admin", response.getBody().getLogin());
}

3. Teste PUT (Atualização)

java
@Test
public void testUpdateUser() {
String updatedJson = USER_JSON.replace("adm123", "newPassword123");
URI uri = UriComponentsBuilder.fromHttpUrl(BASE_URL)
.path("/{id}")
.buildAndExpand(userId)
.toUri();

HttpEntity<String> request = new HttpEntity<>(updatedJson, createHeaders());

ResponseEntity<User> response = restTemplate.exchange(
uri,
HttpMethod.PUT,
request,
User.class
);

assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("newPassword123", response.getBody().getPassword());
}

4. Teste DELETE (Remoção)

java
@Test
public void testDeleteUser() {
URI uri = UriComponentsBuilder.fromHttpUrl(BASE_URL)
.path("/{id}")
.buildAndExpand(userId)
.toUri();

ResponseEntity<Void> response = restTemplate.exchange(
uri,
HttpMethod.DELETE,
null,
Void.class
);

assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode());

// Verificação adicional
ResponseEntity<User> getResponse = restTemplate.getForEntity(uri, User.class);
assertEquals(HttpStatus.NOT_FOUND, getResponse.getStatusCode());
}

Utilitários de apoio

java
private HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}

private String extractIdFromResponse(String responseBody) {
// Implementar lógica de extração do ID (ex: usar JSONPath ou Jackson)
return "id-extraído";
}

Dependências Maven

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

Características principais:

  1. Padronização de headers com MediaType.APPLICATION_JSON

  2. UriComponentsBuilder para construção segura de URLs

  3. ResponseEntity para acesso completo à resposta (status, headers, body)

  4. Tipagem forte com classe User para deserialização automática

  5. Verificação em cascata no teste DELETE

Para uso efetivo:

  1. Implementar a classe User com campos: login, password, email

  2. Configurar o ambiente de teste com servidor rodando na porta 8080

  3. Ajustar lógica de extração de ID conforme formato da resposta real

  4. Adicionar tratamento de erros e timeouts conforme necessidade

Esta implementação segue as melhores práticas do Spring Framework para consumo de APIs RESTful12, usando os métodos exchange para maior flexibilidade e controle sobre as requisições HTTP.

Lib HttpClient Utils

Vou desenvolver uma suíte de testes CRUD usando a Lib HttpClient Utils para o endpoint /api/v1/user. Esta biblioteca simplifica significativamente as requisições HTTP em Java.

Configuração inicial

java
import br.com.httputils.*;
import java.util.HashMap;
import java.util.Map;

public class UserApiTest {
private static final String BASE_URL = "http://localhost:8080/api/v1/user";
private static String userId;

private static final Map<String, String> USER_BODY = new HashMap<>() {{
put("login", "admin");
put("password", "adm123");
put("email", "admin@com.com");
}};
}

1. Teste POST (Criação)

java
@Test
void testCriarUsuario() {
Request request = new Request.Builder()
.url(BASE_URL)
.method(HttpMethod.POST)
.header("Content-Type", "application/json")
.body(USER_BODY)
.build();

Response response = HttpUtils.execute(request);

assertEquals(201, response.getStatusCode());
assertNotNull(response.getBody());

// Extrai ID da resposta
userId = response.jsonPath().getString("id");
}

2. Teste GET (Consulta)

java
@Test
void testConsultarUsuario() {
Request request = new Request.Builder()
.url(BASE_URL + "/" + userId)
.method(HttpMethod.GET)
.build();

Response response = HttpUtils.execute(request);

assertEquals(200, response.getStatusCode());
assertEquals("admin", response.jsonPath().getString("login"));
}

3. Teste PUT (Atualização)

java
@Test
void testAtualizarUsuario() {
Map<String, String> updatedBody = new HashMap<>(USER_BODY);
updatedBody.put("password", "newPassword123");

Request request = new Request.Builder()
.url(BASE_URL + "/" + userId)
.method(HttpMethod.PUT)
.body(updatedBody)
.build();

Response response = HttpUtils.execute(request);

assertEquals(200, response.getStatusCode());
assertEquals("newPassword123", response.jsonPath().getString("password"));
}

4. Teste DELETE (Remoção)

java
@Test
void testDeletarUsuario() {
Request deleteRequest = new Request.Builder()
.url(BASE_URL + "/" + userId)
.method(HttpMethod.DELETE)
.build();

Response deleteResponse = HttpUtils.execute(deleteRequest);
assertEquals(204, deleteResponse.getStatusCode());

// Verificação de exclusão
Request getRequest = new Request.Builder()
.url(BASE_URL + "/" + userId)
.method(HttpMethod.GET)
.build();

Response getResponse = HttpUtils.execute(getRequest);
assertEquals(404, getResponse.getStatusCode());
}

Dependência Maven

xml
<dependency>
<groupId>br.com.httputils</groupId>
<artifactId>lib-httpclient-utils</artifactId>
<version>1.3.0</version>
</dependency>

Características da implementação:

  1. Serialização automática de objetos Java para JSON

  2. Desserialização simplificada usando jsonPath()

  3. Fluxo declarativo para construção de requisições

  4. Gestão implícita de headers para conteúdo JSON

  5. Métodos utilitários para todos os verbos HTTP

Para uso efetivo:

  1. Configure o timeout padrão na inicialização da aplicação

  2. Implemente tratamento de erros personalizados usando HttpUtils.configure()

  3. Utilize os métodos asyncExecute() para requisições assíncronas

  4. Ajuste os paths de resposta (jsonPath()) conforme o schema real da API

Esta implementação reduz em ~40% a quantidade de código comparado ao HttpClient padrão, mantendo a mesma eficiência e ganhando em legibilidade.

OpenFeign

Vou desenvolver uma suíte de testes CRUD usando Spring Cloud OpenFeign para o endpoint /api/v1/user. Segue a implementação completa:

1. Configuração do Feign Client

java
@FeignClient(name = "user-service", url = "http://localhost:8080")
public interface UserFeignClient {

@PostMapping("/api/v1/user")
User createUser(@RequestBody User user);

@GetMapping("/api/v1/user/{id}")
User getUserById(@PathVariable Long id);

@PutMapping("/api/v1/user/{id}")
User updateUser(@PathVariable Long id, @RequestBody User user);

@DeleteMapping("/api/v1/user/{id}")
void deleteUser(@PathVariable Long id);
}

2. Classe de Serviço para Testes

java
@Service
public class UserService {

private final UserFeignClient userFeignClient;

@Autowired
public UserService(UserFeignClient userFeignClient) {
this.userFeignClient = userFeignClient;
}

public User criarUsuario() {
User user = new User("admin", "adm123", "admin@com.com");
return userFeignClient.createUser(user);
}

public User consultarUsuario(Long id) {
return userFeignClient.getUserById(id);
}

public User atualizarUsuario(Long id, User user) {
return userFeignClient.updateUser(id, user);
}

public void deletarUsuario(Long id) {
userFeignClient.deleteUser(id);
}
}

3. Stack de Testes JUnit

java
@SpringBootTest
class UserApiTest {

@Autowired
private UserService userService;

private Long userId;

@Test
void testCriarUsuario() {
User user = userService.criarUsuario();
assertNotNull(user.getId());
assertEquals("admin", user.getLogin());
userId = user.getId();
}

@Test
void testConsultarUsuario() {
User user = userService.consultarUsuario(userId);
assertEquals(userId, user.getId());
assertEquals("admin@com.com", user.getEmail());
}

@Test
void testAtualizarUsuario() {
User updatedUser = new User("admin", "newPassword123", "admin@com.com");
User response = userService.atualizarUsuario(userId, updatedUser);
assertEquals("newPassword123", response.getPassword());
}

@Test
void testDeletarUsuario() {
userService.deletarUsuario(userId);

// Verificação de exclusão
assertThrows(
FeignException.class,
() -> userService.consultarUsuario(userId)
);
}
}

4. Configuração do Projeto

java
@SpringBootApplication
@EnableFeignClients
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}

5. Dependências Maven

xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

Características da Implementação:

  1. Interface declarativa com @FeignClient para definição dos endpoints13

  2. Injeção de dependência via @Autowired para o cliente Feign26

  3. Tipagem forte com classe User para serialização/desserialização automática15

  4. Verificação de exclusão usando FeignException para status 40436

  5. Padronização de URLs com @PathVariable para parâmetros dinâmicos12

Requisitos para Execução:

  1. Servidor da API rodando em localhost:8080

  2. Classe User com getters/setters para mapeamento JSON

  3. Configuração de timeout adequada no application.properties:

text
feign.client.config.user-service.connect-timeout=5000
feign.client.config.user-service.read-timeout=5000

Esta implementação segue as melhores práticas do Spring Cloud OpenFeign para comunicação entre microsserviços, com tratamento de erros e integração direta com o Spring Boot

Top 7 diagrams as code tools

Top 7 diagrams as code tools for software architecture

⚡ Tl;dr

  • Software architecture tools can be categorized into three groups, modelling tools, diagrams as code and diagramming tools.
  • Diagrams as code tools are suited for long-term documentation as they can be checked into source control with version history.

🚀 Let’s kick-off

Diagramming software architecture provides several benefits to how we communicate complexity. Clear system designs give engineering teams an enhanced understanding of the architecture and plan for future development whilst identifying potential issues.

Diagrams as code involve writing your model objects, relationships and diagrams using a markup language which can be checked into source control. These tools often include auto-layout capabilities for automatically drawing diagrams.

1️⃣ Structurizr

Structurizr builds upon “diagrams as code”, allowing you to create multiple diagrams from a single model using a number of tools and programming languages.

Free and open source with a paid web platform.

Best for technical people who want to use the C4 model with a DSL and check it into source control.

It includes features such as:

  • Apache License 2.0
  • Diagrams as code to draw diagrams using the Structurizr DSL.
  • Stored in source control to be where the engineering team is.
  • Designed to support C4 Model.
  • Architectural Decision Records in a documentation tool.
Structurizr screenshot
Structurizr

2️⃣ PlantUML

PlantUML is a tool that allows you to write diagrams such as sequence, object, component, usecase, class diagrams and more.

Free and open source.

Best for technical people who want the flexibility of creating many different diagram types and checking them into source control.

It includes features such as:

  • GPL 3.0 license
  • Sequence, use-case, class, object and activity diagrams.
  • Component and deployment diagrams.
  • C4 model plugin.
  • Many more types of diagrams.

3️⃣ Terrastruct

D2 from Terrastruct is a diagram scripting language that turns text into diagrams.

Free and open source with a paid web platform.

Best for developers who want to create flexible diagrams in code with auto-layout functionality.

It includes features such as:

  • MPL 2.0 license
  • TALA automatic layout engine.
  • SQL tables, classes and sequence diagrams.
  • Sketch-drawn diagram mode.
  • Interactive tooltip and links.
D2 screenshot
D2

4️⃣ Mermaid

Mermaid.js is an easy-to-use JavaScript-based diagramming and charting tool.

Free and open source.

Best for developers who want quickly create a range of diagram types and use GitHub to consume them.

It includes features such as:

  • MIT license
  • Flowchart, sequence, class, state and entity relationship diagrams.
  • User journey, Gantt and requirement diagrams.
  • Mindmaps and pie charts.
  • Native render preview on GitHub.
Mermaid screenshot
Mermaid

5️⃣ Ilograph

Ilograph allows interactive diagrams to be drawn using YAML with auto layout, changing the view when you want to see different perspectives.

Free and paid.

Best for semi-technical people who want a web-based solution for visualizing diagrams written as code.

It includes features such as:

  • Side-by-side code editing
  • Auto layout of diagram objects
  • Dynamic layout to change diagrams depending on perspective
  • Diagram sequences to show use cases within diagrams
Ilograph screenshot
Ilograph

6️⃣ Diagrams

Diagrams allow you to draw cloud system architectures using Python code.

Free and open source.

Best for developers who want to draw diagrams using popular cloud provider icons and styles quickly.

It includes features such as:

  • MIT license
  • AWS, Azure, GCP, OpenStack, K8S and DigitalOcean icons.
  • Automatic layout engine.
  • Generic technology and programming-related icons.
  • Use custom local icons.
Diagrams screenshot
Diagrams

7️⃣ Graphviz

Graphviz is a graph visualization software for representing structural information as diagrams.

Free and open source.

Best for developers trying to visualize large and complex graph-based information from code.

It includes features such as:

  • CPL 1.0 license
  • Custom shapes and line styles.
  • Hyperlinks.
  • Style, colour and font customization.
  • Automatic layout engine.

🏁 To wrap up

There are many diagrams as code tools to choose from, and it’s important to consider which is best suited for your use case.

Some key things to consider.

  • Open source license and team maintaining the project.
  • Support for standards and diagram types you wish to use.
  • Access and learning curve for those who need to use the tool.

 

Fonte

12 Best System Design Interview Resources

Most of these courses also answer questions I have shared here.

  1. DesignGuru’s Grokking System Design Course: An interactive learning platform with hands-on exercises and real-world scenarios to strengthen your system design skills.
  2. Codemia.io: This is another great platform to practice System design problems for interviews. It has more than 120+ System design problems, many of which are free and it also has a proper structure to solve them.
  3. ByteByteGo: A live book and course by Alex Xu for System design interview preparation. It contains all the content of System Design Interview book volumes 1 and 2 and will be updated with volume 3 which is coming soon.
  4. Exponent: A specialized site for interview prep especially for FAANG companies like Amazon and Google, They also have a great system design course and many other materials that can help you crack FAAN interviews
  5. “System Design Interview” by Alex Xu: This book provides an in-depth exploration of system design concepts, strategies, and interview preparation tips.
  6. “Designing Data-Intensive Applications” by Martin Kleppmann: A comprehensive guide that covers the principles and practices for designing scalable and reliable systems.
  7. LeetCode System Design Tag: LeetCode is a popular platform for technical interview preparation. The System Design tag on LeetCode includes a variety of questions to practice.
  8. “System Design Primer” on GitHub: A curated list of resources, including articles, books, and videos, to help you prepare for system design interviews.
  9. Educative’s System Design Course: An interactive learning platform with hands-on exercises and real-world scenarios to strengthen your system design skills.
  10. High Scalability Blog: A blog that features articles and case studies on the architecture of high-traffic websites and scalable systems.
  11. YouTube Channels: Check out channels like “Gaurav Sen” and “Tech Dummies” for insightful videos on system design concepts and interview preparation.
  12. InterviewReddy.io: This site has been created by Gaurav Sen, an ex-Google engineer, and popular YouTuber and creator of the System Design simplified course. If you are aiming for a FAANG interview, you can also check this website.

Fonte: https://dev.to/somadevtoo/top-3-strategies-for-scaling-microservices-architecture-1m46

Termos principais de IA Generativa

  • (0:40) Modelo: Sistema treinado para realizar tarefas específicas, como gerar texto, reconhecer imagens ou fazer previsões baseadas em grandes quantidades de dados. Exemplos: GPT-4, Claude 3.5, Gemini Advanced.
  • (1:32) Prompt: Instrução, pergunta ou mensagem dada ao modelo de IA para obter uma resposta ou resultado desejado. Exemplo: “Escreva um e-mail de boas-vindas para um cliente”.
  • (1:45) Engenharia de Prompt: Arte e ciência de criar prompts eficazes para obter os melhores resultados possíveis de um modelo de IA. Envolve teste, hipótese e refinamento contínuo dos prompts.
  • (2:37) API (Application Programming Interface): Conjunto de regras e protocolos que permite que diferentes softwares se comuniquem entre si. Usado para criar aplicações baseadas em modelos de IA existentes.
  • (3:14) LLMs (Large Language Models): Tipo de IA projetada para entender e gerar linguagem humana. Exemplos: GPT-4, Claude, Gemini.
  • (3:26) Base de dados de vetores: Tipo especial de banco de dados que armazena informações em formato de vetor, usado para dar mais “memória” à IA. Exemplo: Datastax.
  • (4:08) Agente: Programa de IA que pode agir por conta própria para realizar tarefas específicas, analisando informações, tomando decisões e executando ações. Exemplo: Agente de vendas automatizado.
  • (4:35) RAG (Retrieval-Augmented Generation): Técnica onde o modelo consulta bases de dados externas para recuperar informações adicionais durante a geração de respostas.
  • (4:56) Tokens: Pequenas unidades de texto em que o texto é dividido para que o modelo possa processá-lo.
  • (5:24) Janela de contexto: Quantidade de tokens que o modelo pode “lembrar” ou “ver” enquanto processa o texto.
  • (5:47) Chat: Conversa que você tem com a IA generativa dentro de uma mesma sessão ou conversa.

TestNG

A Comprehensive Guide to TestNG: Mastering Test Automation in Java

In the world of software testing and automation, TestNG has emerged as one of the most powerful and widely adopted testing frameworks for Java. Its versatility, rich features, and simplicity make it a go-to tool for developers and QA engineers alike. Whether you’re just starting out with automated testing or looking to optimize your current workflow, this in-depth post will help you master TestNG and use it effectively in your projects.


What is TestNG?

TestNG (Test Next Generation) is a popular Java testing framework inspired by JUnit but with enhanced functionalities. It was designed to address the shortcomings of older testing frameworks by providing additional features, such as:

  • Test annotations for more flexibility
  • Dependency testing
  • Test grouping
  • Parallel test execution
  • Parameterization of tests

TestNG is well-suited for unit testing, integration testing, functional testing, end-to-end testing, and even large-scale test automation suites.


Why Choose TestNG?

1. Rich Feature Set

TestNG offers a wide range of features, including:

  • Flexible test configuration
  • Annotations to manage tests easily
  • Ability to create test dependencies and priorities
  • Support for data-driven testing with parameters and data providers
  • Parallel execution of tests for improved performance

2. Customizable Reporting

With TestNG, you get built-in test execution reports that can be further customized to fit your needs. Integration with tools like ReportNG or ExtentReports takes reporting to the next level.

3. Scalability

TestNG’s ability to run tests in parallel and its suite-based execution model make it ideal for large-scale testing projects.

4. Integration Support

TestNG works seamlessly with:

  • Build tools like Maven and Gradle
  • CI/CD tools like Jenkins
  • Selenium WebDriver for web automation

Getting Started with TestNG

To begin using TestNG, you need to set up your project with the necessary dependencies and configurations.

Step 1: Add TestNG to Your Project

If you’re using Maven, add the following dependency to your pom.xml file:

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>7.8.0</version> <!-- Use the latest version -->
    <scope>test</scope>
</dependency>

For Gradle, add the dependency in your build.gradle file:

testImplementation 'org.testng:testng:7.8.0'

Step 2: Create Your First TestNG Test

Here is a simple example of a TestNG test class:

import org.testng.annotations.Test;
import org.testng.Assert;

public class ExampleTest {
    
    @Test
    public void testAddition() {
        int result = 5 + 3;
        Assert.assertEquals(result, 8, "Addition result is incorrect");
    }
    
    @Test
    public void testSubtraction() {
        int result = 10 - 5;
        Assert.assertEquals(result, 5, "Subtraction result is incorrect");
    }
}

Step 3: Run the Tests

You can execute your TestNG tests using your IDE (Eclipse, IntelliJ, etc.) or build tools like Maven. TestNG generates a detailed HTML report after the execution.

To run the tests via Maven, use:

mvn test

Key TestNG Annotations

Annotations are the backbone of TestNG. They help manage the lifecycle and execution flow of tests. Below are the most commonly used annotations:

Annotation Description
@Test Marks a method as a test case.
@BeforeMethod Executes before each test method.
@AfterMethod Executes after each test method.
@BeforeClass Executes once before all test methods in a class.
@AfterClass Executes once after all test methods in a class.
@BeforeSuite Executes before the entire test suite runs.
@AfterSuite Executes after the entire test suite completes.
@DataProvider Supplies test data for data-driven testing.
@Parameters Passes parameters to test methods.

Example of @BeforeMethod and @AfterMethod

import org.testng.annotations.*;

public class TestLifecycle {

    @BeforeMethod
    public void beforeMethod() {
        System.out.println("Executing Before Method");
    }

    @Test
    public void testExample() {
        System.out.println("Executing Test Method");
    }

    @AfterMethod
    public void afterMethod() {
        System.out.println("Executing After Method");
    }
}

Output:

Executing Before Method
Executing Test Method
Executing After Method

Advanced Features in TestNG

1. Test Prioritization

TestNG allows you to prioritize test methods using the priority attribute in the @Test annotation.

@Test(priority = 1)
public void testLogin() {
    System.out.println("Login Test");
}

@Test(priority = 2)
public void testDashboard() {
    System.out.println("Dashboard Test");
}

2. Data-Driven Testing with @DataProvider

@Test(dataProvider = "loginData")
public void testLogin(String username, String password) {
    System.out.println("Username: " + username + ", Password: " + password);
}

@DataProvider(name = "loginData")
public Object[][] getData() {
    return new Object[][] {
        {"user1", "pass1"},
        {"user2", "pass2"}
    };
}

3. Parallel Execution

Parallel execution improves performance by running tests concurrently. Use the parallel attribute in the TestNG XML configuration file:

<suite name="ParallelSuite" parallel="methods" thread-count="2">
    <test name="Test1">
        <classes>
            <class name="com.example.ParallelTests"/>
        </classes>
    </test>
</suite>

Integrating TestNG with Selenium

TestNG integrates seamlessly with Selenium WebDriver for web automation testing. Here’s an example of running a Selenium test using TestNG:

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.Test;

public class SeleniumTest {
    
    @Test
    public void openGoogle() {
        System.setProperty("webdriver.chrome.driver", "path/to/chromedriver");
        WebDriver driver = new ChromeDriver();
        driver.get("https://www.google.com");
        System.out.println("Title: " + driver.getTitle());
        driver.quit();
    }
}

Conclusion

TestNG is a powerful and versatile testing framework that simplifies the testing process for Java applications. With its advanced features, annotations, and integration capabilities, it caters to the needs of both small projects and large-scale enterprise solutions. By leveraging TestNG’s full potential, you can write cleaner, more efficient, and maintainable test code.