Category Archives: Tecnologia

Architecture Review Board

Understanding the Architecture Review Board

Introduction

An Architecture Review Board (ARB) is a governance body within an organization responsible for overseeing and guiding the development, implementation, and maintenance of IT and enterprise architectures. The ARB ensures that architectural decisions align with the organization’s strategic goals, standards, and long-term vision. By fostering consistency, interoperability, and efficiency, the ARB plays a critical role in modern enterprises, particularly those managing complex IT ecosystems.

This article explores the purpose, composition, processes, benefits, challenges, and best practices of an Architecture Review Board, offering insights for organizations seeking to establish or optimize their ARB.

What is an Architecture Review Board?

An Architecture Review Board is a formalized group of stakeholders tasked with reviewing, approving, and governing architectural decisions across an organization’s IT and business systems. The ARB ensures that proposed architectures—whether for software, infrastructure, or enterprise-wide systems—adhere to established standards, policies, and best practices. It acts as a checkpoint to mitigate risks, reduce technical debt, and promote alignment with business objectives.

The ARB typically operates within the context of enterprise architecture frameworks like TOGAFZachman, or C4 Model, ensuring that architectural decisions support scalability, security, and maintainability. It is not a bureaucratic gatekeeper but rather a collaborative body that balances innovation with governance.

Purpose of an Architecture Review Board

The primary purposes of an ARB include:

  1. Alignment with Business Goals: Ensuring architectural decisions support the organization’s strategic objectives, such as cost reduction, scalability, or customer experience improvement.
  2. Standardization and Consistency: Promoting adherence to architectural standards, frameworks, and technologies to avoid fragmentation and ensure interoperability.
  3. Risk Mitigation: Identifying and addressing risks related to security, performance, scalability, or compliance in proposed architectures.
  4. Technical Debt Management: Preventing short-term, ad-hoc solutions that could lead to long-term maintenance challenges.
  5. Knowledge Sharing: Facilitating collaboration and communication among stakeholders to share best practices and lessons learned.
  6. Innovation Enablement: Balancing governance with the flexibility to adopt new technologies and approaches.

Composition of an Architecture Review Board

The composition of an ARB varies depending on the organization’s size, structure, and industry. However, a typical ARB includes:

  • Chief Architect or Enterprise Architect: Leads the ARB and provides strategic oversight.
  • Solution Architects: Offer expertise in specific domains, such as application, data, or infrastructure architecture.
  • Business Stakeholders: Represent business units to ensure alignment with organizational goals.
  • Security Specialists: Focus on compliance, risk management, and cybersecurity.
  • IT Operations Representatives: Provide insights into operational feasibility and infrastructure impacts.
  • Development Team Leads: Ensure that development practices align with architectural guidelines.
  • External Consultants (optional): Provide independent perspectives or specialized expertise.

The ARB should be diverse yet manageable in size (typically 5–10 members) to balance expertise with efficient decision-making.

ARB Processes and Workflow

The ARB operates through a structured process to review and govern architectural decisions. A typical workflow includes:

  1. Submission of Proposals:

    • Teams (e.g., project managers, architects, or developers) submit architecture proposals or designs for review. These may include diagrams (e.g., using the C4 Model), technical specifications, or business cases.
    • Proposals should outline the problem, proposed solution, technologies, risks, and alignment with organizational goals.
  2. Pre-Review Preparation:

    • ARB members review the submitted materials in advance, often using tools like Sparx Enterprise ArchitectArchi, or Structurizr for modeling and visualization.
    • Clarifications or additional details may be requested from the submitting team.
  3. Review Meeting:

    • The ARB convenes (in-person or virtually) to discuss the proposal. The submitting team may present their case and answer questions.
    • The board evaluates the proposal based on predefined criteria, such as alignment with standards, scalability, security, and cost-effectiveness.
  4. Decision and Feedback:

    • The ARB approves, rejects, or requests modifications to the proposal. Feedback is provided to guide improvements.
    • Approved architectures are documented and tracked for future reference.
  5. Ongoing Governance:

    • The ARB monitors the implementation of approved architectures to ensure compliance and address deviations.
    • Regular updates or audits may be conducted to assess the architecture’s performance.

Benefits of an Architecture Review Board

An effective ARB delivers significant value to an organization, including:

  • Improved Decision Quality: Ensures decisions are informed, consistent, and aligned with long-term goals.
  • Reduced Technical Debt: Prevents poorly designed systems that lead to costly rework.
  • Enhanced Collaboration: Fosters communication between business and IT stakeholders, reducing silos.
  • Risk Reduction: Identifies potential issues early, such as security vulnerabilities or scalability limitations.
  • Standardization: Promotes reusable components and consistent technology choices, reducing complexity.
  • Agility with Governance: Balances the need for innovation with adherence to standards, enabling faster delivery of reliable systems.

Challenges of an Architecture Review Board

While valuable, ARBs can face challenges that organizations must address:

  1. Bureaucracy Perception: If not managed well, the ARB can be seen as a bottleneck, slowing down projects.
  2. Resource Constraints: ARB members often have other responsibilities, leading to scheduling conflicts or limited bandwidth.
  3. Scope Creep: The ARB may become involved in low-level decisions, diluting its strategic focus.
  4. Resistance to Change: Teams may resist ARB oversight, especially if it’s perceived as overly rigid.
  5. Keeping Up with Technology: Rapidly evolving technologies (e.g., cloud, AI, microservices) require the ARB to stay current.
  6. Balancing Stakeholder Interests: Conflicting priorities between business and IT can complicate decision-making.

Best Practices for a Successful ARB

To maximize the effectiveness of an ARB, organizations should adopt the following best practices:

  1. Define Clear Objectives and Scope:

    • Establish the ARB’s purpose, authority, and scope to avoid overreach or ambiguity. Focus on strategic decisions rather than micromanagement.
    • Use frameworks like TOGAF or C4 Model to guide reviews and ensure consistency.
  2. Streamline Processes:

    • Implement lightweight submission and review processes to avoid delays. Tools like LucidchartPlantUML, or Structurizr can simplify documentation.
    • Use templates for proposals to standardize submissions.
  3. Foster Collaboration:

    • Encourage open dialogue between the ARB and project teams. Treat reviews as collaborative discussions, not adversarial evaluations.
    • Include diverse perspectives to ensure well-rounded decisions.
  4. Leverage Tools and Automation:

    • Use architecture tools (e.g., ArchiMateEnterprise Architect, or Draw.io) to visualize and analyze proposals.
    • Integrate with DevOps pipelines or repositories (e.g., via Structurizr) to automate documentation and tracking.
  5. Educate and Train:

    • Train ARB members on emerging technologies and frameworks to maintain relevance.
    • Educate teams on the ARB’s role to reduce resistance and improve submissions.
  6. Measure and Iterate:

    • Track the ARB’s impact through metrics like project success rates, reduced technical debt, or improved compliance.
    • Regularly review and refine ARB processes based on feedback and outcomes.

Case Study: ARB in Action

Consider a large financial institution implementing a new customer-facing application. The development team proposes a microservices architecture using Kubernetes and a cloud-native database. The ARB reviews the proposal, ensuring:

  • Alignment: The architecture supports the bank’s goal of improving customer experience.
  • Standards: The chosen technologies align with the organization’s cloud strategy.
  • Security: The proposal includes robust security measures, validated by the security specialist.
  • Scalability: The architecture can handle peak loads during high-traffic periods.

The ARB approves the proposal with minor adjustments, such as adopting a standardized API gateway. Post-implementation, the ARB monitors the system’s performance, ensuring it meets expectations.

Conclusion

An Architecture Review Board is a vital component of effective IT and enterprise governance. By aligning architectural decisions with business goals, enforcing standards, and mitigating risks, the ARB enables organizations to build robust, scalable, and innovative systems. While challenges like bureaucracy or resource constraints exist, adopting best practices—such as clear objectives, streamlined processes, and collaborative tools—can ensure the ARB’s success.

For organizations looking to establish or enhance their ARB, leveraging frameworks like TOGAFC4 Model, or ArchiMate, combined with modern tools like Structurizr or Enterprise Architect, can streamline the process. By fostering a culture of collaboration and continuous improvement, the ARB can drive long-term value in an increasingly complex technological landscape.

This article provides a detailed overview of the Architecture Review Board, tailored for readers seeking a comprehensive understanding. If you’d like me to generate a diagram (e.g., a C4 Model context diagram for an ARB process) or focus on a specific aspect (e.g., tools or case studies), let me know! For further details on tools or subscriptions mentioned.

System Design Blueprint

8 Best System design resources for Coding Interviews
1. ZTM – https://bit.ly/3YpWu4q
2. ByteByteGo – https://bit.ly/3P3eqMN
1. Grokking the System Design – https://bit.ly/3ckZlsl
3. Pragmatic Design – https://bit.ly/3vFNPid
4. Software Design – https://bit.ly/3BxMXzr
5. Software Architecture 101 – https://bit.ly/3pzJCJh
6. Modern design – https://bit.ly/3OQKX8B
7. DesignGuru – https://bit.ly/3pMiO8g
8. Meetapro – https://bit.ly/48UAXpJ

 

Arrays parallelSort

O método Arrays.sort é bastante eficiente para a maioria dos casos, mas quando lidamos com arrays muito grandes, sua versão sequencial pode se tornar um gargalo, principalmente ao aproveitar apenas um único núcleo da CPU. Para arrays de tipos primitivos, o Java utiliza uma implementação de Dual-Pivot Quicksort, que tem, em média, complexidade de O(n log n). Entretanto, mesmo essa implementação bem otimizada pode enfrentar limitações em contextos de alto volume de dados, sobretudo se a ordenação for um dos pontos críticos de desempenho da aplicação.

Uma solução bastante interessante nesses cenários é utilizar o Arrays.parallelSort. Introduzido a partir do Java 8, esse método parte o array em segmentos menores e os classifica de forma concorrente, aproveitando o framework Fork/Join para distribuir as tarefas entre múltiplos núcleos. Essa abordagem pode reduzir significativamente o tempo total de ordenação em sistemas com múltiplos threads, especialmente em arrays de tamanho enorme. Além disso, para conjuntos de dados cuja chave seja numérica e limitada, algoritmos não-baseados em comparação, como Radix Sort ou Counting Sort, podem ser explorados, já que esses algoritmos podem apresentar desempenho linear sob determinadas condições.

Portanto, ao avaliar a performance do Arrays.sort com arrays grandes, a recomendação é considerar o uso de algoritmos paralelos, como o Arrays.parallelSort, que se aproveitam da arquitetura moderna multi-core. Essa solução não só melhora o tempo de execução, mas também distribui de forma mais equilibrada a carga de processamento, resultando em um desempenho superior para aplicações que lidam com volumes massivos de dados.

Você também pode explorar algoritmos específicos para tipos de dados particulares. Por exemplo, para ordenar strings ou outros objetos com um padrão de comparação complexo, uma combinação de particionamento eficiente e técnicas de otimização local pode trazer ganhos importantes. Essa abordagem pode ser ajustada conforme o perfil dos dados e o ambiente de execução, permitindo um balanceamento ideal entre uso de memória e velocidade de processamento.

Principal Architect Interview Questions

Personal Introductions & Motivations
. “Tell me a bit about your career to date”
. “What motivated you to explore this opportunity?”
. “What are you looking for in your next role that perhaps you’re not getting in your current role?”

Team Structure & Maturity (Chief Architect outlines)
. “We currently have a hybrid architecture model; a small core EA team and domain architects embedded with product and engineering.”
. “We’re investing in raising the architectural bar across teams; you’d play a key role in maturing that culture.”

Architecture Challenges
. “One challenge we’re facing is fragmentation; different domains are solving similar problems in different ways.
. “There’s still a gap between business strategy and architecture outcomes; one of your core focuses would be bridging that.”

Architecture Specific Questions
• How do you define and drive technical direction across multiple domains or product lines?
• What methods do you use to ensure architectural alignment across distributed teams or department
• How do you establish and maintain architectural standards. patterns, and principles at scale?
• How do you grow and mentor other architects or senior engineers?
• Describe how you’ve worked with product and business leaders to shape a technology roadmap.
• Can you give an example of resolving conflict between architectural vision and product priorities?

Q&A
Opportunity for the Principal Architect to ask some questions:
• “What would success look like in the first 90 days?”
• “How is the architecture function currently perceived across engineering and business teams?”
• “How do you see me contributing to the cultural maturity and evolution of the architecture team?”

COC

Commercial Offer Catalog (Catálogo de Ofertas Comerciais) é um conceito amplamente utilizado em setores como telecomunicações, serviços financeiros, energia, e-commerce, e outros segmentos que envolvem a venda de produtos e serviços complexos. Ele funciona como uma base estruturada e centralizada onde todas as ofertas comerciais de uma empresa são definidas, organizadas, gerenciadas e disponibilizadas para os canais de venda.

A seguir, explico detalhadamente os principais conceitos relacionados ao Commercial Offer Catalog:


📚 O que é um Commercial Offer Catalog?

É um repositório centralizado de todas as ofertas comerciais de uma empresa. Nele são definidos os produtos e serviços que podem ser oferecidos aos clientes, bem como as regras de negócio, preços, elegibilidade, promoções, dependências técnicas e combinações possíveis entre os produtos.


  1. Produtos e Serviços

    • Itens individuais que compõem uma oferta, como internet banda larga, plano de telefonia, seguro, etc.
    • Pode conter atributos como: velocidade, franquia, tipo de cobertura, validade, entre outros.
  2. Ofertas

    • Conjunto de produtos/serviços agrupados com regras específicas.
    • Ex: “Combo Família” que inclui TV, internet e telefone fixo com desconto.
  3. Preços e Tarifas

    • Tabelas de preços, descontos aplicáveis, promoções temporárias e regras tributárias.
    • Pode incluir variações por região, canal de venda, perfil do cliente, etc.
  4. Regras de Elegibilidade

    • Define para quem a oferta está disponível (ex: apenas para novos clientes, ou em determinadas regiões).
  5. Validade e Ciclo de Vida

    • Período em que a oferta está ativa, expirando depois de uma data específica ou após condições de uso.
  6. Relacionamento entre Ofertas

    • Regras de compatibilidade, exclusividade ou dependência entre diferentes ofertas ou produtos.

🧠 Benefícios de um Commercial Offer Catalog bem estruturado

  • 🔄 Agilidade na criação e modificação de ofertas: Mudanças podem ser feitas rapidamente sem impacto no sistema como um todo.
  • 🛠 Reuso de componentes: Produtos e regras podem ser reutilizados em diferentes ofertas.
  • 🎯 Personalização: Permite ofertas personalizadas para diferentes segmentos ou perfis de cliente.
  • 📦 Unificação de canais: Garante que todos os canais de venda (loja, app, call center, site) ofereçam as mesmas informações e regras.
  • ✅ Governança e controle: Facilita a conformidade com regras regulatórias e políticas internas.

🧱 Arquitetura e Tecnologias Envolvidas

  • Pode ser implementado usando padrões como:
    • TM Forum SID e eTOM, no setor de telecomunicações.
    • APIs RESTful para expor o catálogo a sistemas de CRM, portais e apps.
    • Soluções COTS (Commercial Off-the-Shelf) como Oracle BRM, Amdocs Catalog, Ericsson e outros.
    • Microservices e Headless CMS, em implementações mais modernas.

🌍 Exemplos de uso

  • Uma operadora de telecom usa o catálogo para definir quais planos de internet estão disponíveis em cada cidade.
  • Um banco usa o catálogo para compor pacotes de serviços bancários com tarifas específicas para clientes universitários.
  • Um e-commerce usa o catálogo para definir combos promocionais e personalizáveis com base no histórico de compras do cliente.

Se quiser, posso te mostrar um exemplo prático em modelo JSON ou Java de como um sistema poderia representar uma oferta no catálogo. Também posso explorar como isso se integra com sistemas de CRM, Order Management ou Billing. Quer seguir por aí?

Legal! Vamos explorar um exemplo prático de como uma oferta comercial poderia ser representada em um modelo de dados (JSON) e depois podemos discutir como isso se integra com sistemas como CRM, Order Management e Billing.


📦 Exemplo de uma Oferta Comercial em JSON

{
  "offerId": "COMBO_FAMILIA_2025",
  "name": "Combo Família 2025",
  "description": "Internet + TV + Telefone fixo com desconto exclusivo",
  "status": "ACTIVE",
  "validFrom": "2025-01-01",
  "validTo": "2025-12-31",
  "channelsAvailable": ["ONLINE", "CALL_CENTER", "RETAIL_STORE"],
  "eligibilityRules": {
    "customerType": ["NEW", "EXISTING"],
    "region": ["SP", "RJ", "MG"]
  },
  "products": [
    {
      "productId": "INTERNET_500MB",
      "name": "Internet 500MB",
      "price": {
        "monthly": 99.9
      }
    },
    {
      "productId": "TV_HD_150CANAIS",
      "name": "TV HD com 150 canais",
      "price": {
        "monthly": 79.9
      }
    },
    {
      "productId": "TELEFONE_FIXO_ILIMITADO",
      "name": "Telefone Fixo Ilimitado",
      "price": {
        "monthly": 29.9
      }
    }
  ],
  "bundlePrice": {
    "monthly": 169.9,
    "discount": 39.8
  },
  "technicalDependencies": [
    "EQUIPAMENTO_MODEM",
    "EQUIPAMENTO_TV_BOX"
  ]
}

🔄 Integrações com Outros Sistemas

🧾 1. CRM (Customer Relationship Management)

  • O CRM consulta o catálogo para mostrar ofertas elegíveis no momento da venda.
  • Permite recomendar ofertas com base no perfil do cliente.
  • Utiliza as regras de elegibilidade do catálogo.

📦 2. Order Management (OM)

  • Recebe a oferta selecionada e quebra em componentes técnicos para orquestrar a ativação.
  • Garante que todas as dependências técnicas sejam respeitadas (ex: entrega de modem, ativação da linha).

💰 3. Billing (Faturamento)

  • Usa o catálogo para saber como calcular o valor da fatura do cliente.
  • Identifica preços promocionais, período de vigência, descontos, etc.

🛠 Arquitetura baseada em microservices (exemplo simplificado)

[Front-End Web/App]
      ↓
[API Gateway]
      ↓
[Commercial Catalog Service] ←→ [Product Catalog]
      ↓
[CRM] ←→ [Order Management System] ←→ [Billing System]

Cada componente consulta o Commercial Catalog Service para garantir consistência.


Se quiser, posso converter esse modelo JSON em um POJO Java com classes como OfferProductPrice, e até simular um endpoint REST para consumir o catálogo. Quer seguir para a parte Java?

NBO

O conceito de Next Best Offer (NBO), também conhecido como Próxima Melhor Oferta, é uma abordagem utilizada principalmente em marketing, vendas e atendimento ao cliente com o objetivo de identificar e oferecer, de forma personalizada, o produto, serviço ou ação mais relevante para um cliente em um dado momento.

🌟 O que é Next Best Offer?

Next Best Offer é uma estratégia orientada por dados que utiliza algoritmos de análise preditiva, inteligência artificial e machine learning para prever qual oferta tem maior probabilidade de ser aceita por um cliente. A ideia é aumentar a relevância da comunicação com o cliente e, assim, melhorar taxas de conversão, satisfação e retenção.


🎯 Objetivos principais

  • Personalizar a experiência do cliente
  • Aumentar o valor do cliente ao longo do tempo (Customer Lifetime Value)
  • Reduzir ofertas irrelevantes ou genéricas
  • Melhorar a eficiência das campanhas de marketing

⚙️ Como funciona?

O processo de NBO envolve várias etapas:

  1. Coleta de dados

    • Dados transacionais (compras anteriores, visitas ao site)
    • Dados demográficos
    • Comportamento digital (cliques, tempo de permanência, abandono de carrinho)
    • Dados contextuais (localização, hora, canal de acesso)
  2. Análise e segmentação

    • Machine learning é usado para identificar padrões e segmentar clientes com base em comportamento e preferências.
  3. Cálculo de probabilidade

    • Algoritmos avaliam a probabilidade de um cliente aceitar uma determinada oferta.
  4. Rankeamento das ofertas

    • As ofertas são priorizadas por relevância e valor para o cliente e para o negócio.
  5. Apresentação no canal apropriado

    • A oferta é feita no canal ideal (e-mail, app, call center, site etc.) no momento certo.

🧠 Técnicas e tecnologias envolvidas

  • Machine Learning / AI
  • Modelos preditivos e classificadores
  • Sistemas de Recomendação (Recommendation Engines)
  • Processamento de linguagem natural (NLP) para entender preferências a partir de interações

💼 Exemplos práticos

  • E-commerce: Após comprar um smartphone, o sistema recomenda capas ou fones de ouvido compatíveis.
  • Bancos: Cliente com padrão de gastos crescente recebe sugestão de upgrade de cartão de crédito.
  • Telecom: Oferta de pacote adicional quando o cliente se aproxima do limite de dados.
  • Streaming: Recomendação de filmes ou séries com base no histórico e no perfil de consumo.

🧩 Diferença entre NBO e NBE

Além do NBO, há também o conceito de Next Best Experience (NBE):

Conceito Foco
Next Best Offer Produto ou serviço certo para o cliente
Next Best Experience Melhor ação para melhorar a experiência do cliente, que pode não envolver venda

Exemplo de NBE: um atendimento proativo para resolver uma falha antes que o cliente reclame.


📈 Benefícios para o negócio

  • Aumento da taxa de conversão
  • Redução de churn (cancelamento)
  • Melhoria na experiência do cliente
  • Otimização de campanhas de marketing
  • Crescimento da fidelização

Se quiser, posso mostrar um exemplo técnico (em pseudocódigo ou com alguma linguagem) de como isso pode ser modelado, por exemplo, com um classificador de NBO. Quer seguir por esse caminho?

Legal! Vamos montar um exemplo técnico simplificado de como modelar o conceito de Next Best Offer (NBO) utilizando Machine Learning com classificação. Vou estruturar assim:


🧪 Exemplo Técnico de Next Best Offer com Classificação (em Python + scikit-learn)

🎯 Cenário

Imagine que somos uma empresa de telecom. Queremos prever qual o melhor produto/serviço para oferecer a um cliente com base no seu histórico de uso e perfil.

Temos 3 ofertas possíveis:

  • Plano Dados Premium
  • Upgrade para Fibra
  • Assinatura de Streaming

🧾 Dataset simplificado (exemplo)

idade tipo_cliente uso_dados (GB) visitas_app oferta_aceita
25 novo 10 3 Plano Dados Premium
40 regular 20 1 Upgrade para Fibra
30 novo 15 5 Assinatura de Streaming

🧠 Etapas do modelo

import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier

# 1. Dados simulados
dados = pd.DataFrame({
    'idade': [25, 40, 30, 22, 55],
    'tipo_cliente': ['novo', 'regular', 'novo', 'regular', 'novo'],
    'uso_dados': [10, 20, 15, 25, 5],
    'visitas_app': [3, 1, 5, 2, 4],
    'oferta_aceita': [
        'Plano Dados Premium',
        'Upgrade para Fibra',
        'Assinatura de Streaming',
        'Upgrade para Fibra',
        'Plano Dados Premium'
    ]
})

# 2. Pré-processamento
label_enc = LabelEncoder()
dados['tipo_cliente'] = label_enc.fit_transform(dados['tipo_cliente'])  # 'novo'=1, 'regular'=0
dados['oferta_aceita'] = label_enc.fit_transform(dados['oferta_aceita'])  # transforma em 0,1,2

X = dados.drop('oferta_aceita', axis=1)
y = dados['oferta_aceita']

# 3. Treinamento do modelo
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
modelo = RandomForestClassifier()
modelo.fit(X_train, y_train)

# 4. Previsão do NBO para novo cliente
novo_cliente = pd.DataFrame({
    'idade': [28],
    'tipo_cliente': [label_enc.transform(['novo'])[0]],
    'uso_dados': [18],
    'visitas_app': [4]
})

previsao = modelo.predict(novo_cliente)
oferta_prevista = label_enc.inverse_transform(previsao)[0]
print(f"👉 Próxima Melhor Oferta: {oferta_prevista}")

🧠 O que está acontecendo?

  • Coletamos dados do cliente (idade, uso, perfil).
  • Treinamos um modelo com exemplos reais.
  • Para um novo cliente, o modelo prediz a oferta com maior probabilidade de aceitação.
  • Usamos Random Forest como classificador, mas outros algoritmos (como XGBoost, redes neurais ou regressão logística) também podem ser usados, dependendo do volume e complexidade dos dados.

Se você quiser, posso mostrar também um fluxo mais realista com pesos por lucratividadecanal ideal e até abordagem multi-oferta. Quer explorar mais algum desses pontos?

Java Factory

O padrão de design de fábrica é usado quando temos uma superclasse com várias subclasses e, com base na entrada, precisamos retornar uma das subclasses. Esse padrão tira a responsabilidade da instanciação de uma classe do programa cliente para a classe de fábrica. Vamos primeiro aprender como implementar um padrão de design de fábrica em Java e, em seguida, veremos as vantagens do padrão de fábrica. Veremos alguns dos usos do padrão de design de fábrica no JDK. Observe que esse padrão também é conhecido como Padrão de Design de Método de Fábrica .

Padrão de Design de Fábrica Super Classe

A superclasse no padrão de design de fábrica pode ser uma interface, uma classe abstrata ou uma classe Java normal. Para nosso exemplo de padrão de design de fábrica, temos uma superclasse abstrata com um método sobrescrito toString() para fins de teste.

package com.journaldev.design.model;

public abstract class Computer {
	
	public abstract String getRAM();
	public abstract String getHDD();
	public abstract String getCPU();
	
	@Override
	public String toString(){
		return "RAM= "+this.getRAM()+", HDD="+this.getHDD()+", CPU="+this.getCPU();
	}
}

Subclasses de Padrão de Design de Fábrica

Digamos que temos duas subclasses, PC e Servidor, com a implementação abaixo.

package com.journaldev.design.model;

public class PC extends Computer {

	private String ram;
	private String hdd;
	private String cpu;
	
	public PC(String ram, String hdd, String cpu){
		this.ram=ram;
		this.hdd=hdd;
		this.cpu=cpu;
	}
	@Override
	public String getRAM() {
		return this.ram;
	}

	@Override
	public String getHDD() {
		return this.hdd;
	}

	@Override
	public String getCPU() {
		return this.cpu;
	}

}

Observe que ambas as classes estão estendendo Computera superclasse.

package com.journaldev.design.model;

public class Server extends Computer {

	private String ram;
	private String hdd;
	private String cpu;
	
	public Server(String ram, String hdd, String cpu){
		this.ram=ram;
		this.hdd=hdd;
		this.cpu=cpu;
	}
	@Override
	public String getRAM() {
		return this.ram;
	}

	@Override
	public String getHDD() {
		return this.hdd;
	}

	@Override
	public String getCPU() {
		return this.cpu;
	}

}

Classe de fábrica

Agora que temos superclasses e subclasses prontas, podemos escrever nossa classe de fábrica. Aqui está a implementação básica.

package com.journaldev.design.factory;

import com.journaldev.design.model.Computer;
import com.journaldev.design.model.PC;
import com.journaldev.design.model.Server;

public class ComputerFactory {

	public static Computer getComputer(String type, String ram, String hdd, String cpu){
		if("PC".equalsIgnoreCase(type)) return new PC(ram, hdd, cpu);
		else if("Server".equalsIgnoreCase(type)) return new Server(ram, hdd, cpu);
		
		return null;
	}
}

Alguns pontos importantes sobre o método Factory Design Pattern são:

  1. Podemos manter a classe Factory Singleton ou podemos manter o método que retorna a subclasse como estático .
  2. Observe que, com base no parâmetro de entrada, diferentes subclasses são criadas e retornadas. getComputeré o método de fábrica.

padrão de fábrica java, padrão de fábrica, padrão de projeto de fábrica, diagrama de classe de padrão de fábrica

Aqui está um programa cliente de teste simples que usa a implementação do padrão de design de fábrica acima.

package com.journaldev.design.test;

import com.journaldev.design.factory.ComputerFactory;
import com.journaldev.design.model.Computer;

public class TestFactory {

	public static void main(String[] args) {
		Computer pc = ComputerFactory.getComputer("pc","2 GB","500 GB","2.4 GHz");
		Computer server = ComputerFactory.getComputer("server","16 GB","1 TB","2.9 GHz");
		System.out.println("Factory PC Config::"+pc);
		System.out.println("Factory Server Config::"+server);
	}

}

A saída do programa acima é:

Factory PC Config::RAM= 2 GB, HDD=500 GB, CPU=2.4 GHz
Factory Server Config::RAM= 16 GB, HDD=1 TB, CPU=2.9 GHz

Vantagens do Padrão de Projeto de Fábrica

  1. O padrão de design de fábrica fornece uma abordagem ao código para interface em vez de implementação.
  2. O padrão Factory remove a instanciação de classes de implementação reais do código do cliente. O padrão Factory torna nosso código mais robusto, menos acoplado e fácil de estender. Por exemplo, podemos facilmente alterar a implementação da classe PC porque o programa cliente não tem conhecimento disso.
  3. O padrão de fábrica fornece abstração entre classes de implementação e cliente por meio de herança.

Exemplos de padrões de design de fábrica no JDK

  1. Os métodos java.util.Calendar, ResourceBundle e NumberFormat getInstance()usam o padrão Factory.
  2. valueOf()método em classes wrapper como Boolean, Integer etc.

Tutorial em vídeo do YouTube sobre o padrão de design de fábrica

Recentemente, carreguei um vídeo no YouTube para o padrão Factory Design, por favor, dê uma olhada. Por favor, curta e compartilhe o vídeo e inscreva-se no meu canal do YouTube. https://www.youtube.com/watch?v=J1QU\_R4MQQc

Você pode baixar o código de exemplo do meu Projeto GitHub .

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