Exercício Abrangente da Fase 1: Construindo o Framework Básico do OrderFlow
Colocando o Conhecimento em Prática—Esta aula reúne todos os conceitos abordados nas primeiras cinco aulas para construir o framework fundamental completo para o sistema de gerenciamento de pedidos de e-commerce OrderFlow.
1. O Que Você Vai Aprender
- Criar um projeto OrderFlow e configurar os ambientes dev e prod
- Implementar a API REST básica para Produtos e Pedidos
- Usar
@ConfigurationPropertiespara gerenciar parâmetros de banco de dados e negócio - Eliminar auto-configurações desnecessárias e personalizar um Starter simples
- Organizar todos os casos de teste de API usando uma Postman Collection
2. Uma História Real de Trabalho em Equipe
(1) Ponto de Dor: Conhecimento fragmentado e não pode ser colocado em prática
Alice havia terminado de aprender sobre auto-configuração do Spring Boot, APIs REST, gerenciamento de configuração e outros tópicos, mas quando chegou a hora de realmente construir o projeto OrderFlow, ela não sabia por onde começar. Cada tópico foi ensinado isoladamente, então ela não tinha certeza de como alternar entre H2 para o ambiente dev e MySQL para o ambiente prod, ou como integrar APIs REST com gerenciamento de configuração.
(2) Soluções para os Exercícios Abrangentes
Esta aula une todos os conceitos em um projeto completo—desde criar o projeto, configurar o ambiente e escrever APIs até personalizar o Starter—para simular um fluxo de trabalho de desenvolvimento do mundo real.
(3) Resultado
Depois que Alice completou os exercícios abrangentes, o esqueleto do projeto OrderFlow pode servir diretamente como base para cursos subsequentes, impulsionando a eficiência de desenvolvimento em três vezes.
3. Criando um Projeto e Configurando o Ambiente
(1) Inicialização do Projeto
(1) ▶ Exemplo: Criando um Projeto Usando o Spring Initializr
curl https://start.spring.io/starter.zip \
-d type=maven-project \
-d language=java \
-d bootVersion=3.2.5 \
-d groupId=com.orderflow \
-d artifactId=orderflow-service \
-d packageName=com.orderflow \
-d javaVersion=17 \
-d dependencies=web,data-jpa,h2,mysql,validation,lombok \
-o orderflow-service.zip && unzip orderflow-service.zip
Saída:
{"status":"ok","data":{}}
(2) Configuração de Múltiplos Ambientes
graph TD
A["application.yml<br/>Comum"] --> B["application-dev.yml<br/>H2 + Debug"]
A --> C["application-prod.yml<br/>MySQL + WARN"]
B --> D["Mesclado: profile dev"]
C --> E["Mesclado: profile prod"]
| Item de Configuração | dev | prod |
|---|---|---|
| Banco de Dados | H2 Em Memória | MySQL |
| ddl-auto | create-drop | validate |
| Nível de Log | DEBUG | WARN |
| Porta | 8080 | 8080 |
4. API REST de Produtos e Pedidos
(1) Estrutura de Pacotes do Projeto
com.orderflow/
├── OrderFlowApplication.java
├── config/
│ └── OrderFlowProperties.java
├── controller/
│ ├── ProductController.java
│ └── OrderController.java
├── model/
│ ├── Product.java
│ └── Order.java
└── repository/
├── ProductRepository.java
└── OrderRepository.java
(1) ▶ Exemplo: DTO de Produto e Controller
// model/Product.java
package com.orderflow.model;
public record Product(
Long id,
String name,
BigDecimal price,
Integer stock
) {}
// controller/ProductController.java
package com.orderflow.controller;
import com.orderflow.model.Product;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
private final Map<Long, Product> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
@PostMapping
public ResponseEntity<Product> create(@RequestBody Product product) {
Long id = seq.getAndIncrement();
Product saved = new Product(id, product.name(), product.price(), product.stock());
store.put(id, saved);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
@GetMapping
public Collection<Product> list() {
return store.values();
}
@GetMapping("/{id}")
public ResponseEntity<Product> get(@PathVariable Long id) {
Product p = store.get(id);
return p != null ? ResponseEntity.ok(p) : ResponseEntity.notFound().build();
}
}
Saída:
// Execução bem-sucedida
(2) ▶ Exemplo: DTO de Pedido e Controller
// model/Order.java
package com.orderflow.model;
import java.time.Instant;
public record Order(
Long id,
Long productId,
Integer quantity,
String status,
Instant createdAt
) {}
// controller/OrderController.java
package com.orderflow.controller;
import com.orderflow.model.Order;
import com.orderflow.config.OrderFlowProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final Map<Long, Order> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
private final OrderFlowProperties props;
public OrderController(OrderFlowProperties props) {
this.props = props;
}
@PostMapping
public ResponseEntity<Order> create(@RequestBody OrderRequest req) {
if (req.quantity() > props.maxItemsPerOrder()) {
return ResponseEntity.badRequest().build();
}
Long id = seq.getAndIncrement();
Order order = new Order(id, req.productId(), req.quantity(), "PENDING", Instant.now());
store.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping("/{id}")
public ResponseEntity<Order> get(@PathVariable Long id) {
Order o = store.get(id);
return o != null ? ResponseEntity.ok(o) : ResponseEntity.notFound().build();
}
public record OrderRequest(Long productId, Integer quantity) {}
}
Saída:
// Execução bem-sucedida
5. Configuração de Negócio com ConfigurationProperties
(1) ▶ Exemplo: OrderFlowProperties
// config/OrderFlowProperties.java
package com.orderflow.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(prefix = "orderflow")
public record OrderFlowProperties(
int maxItemsPerOrder,
Duration orderTimeout,
String defaultCurrency
) {}
Saída:
// Execução bem-sucedida
# application.yml
spring:
application:
name: orderflow-service
profiles:
active: dev
orderflow:
max-items-per-order: 100
order-timeout: 30m
default-currency: USD
6. Excluindo Auto-Configuração e Starters Personalizados
(1) ▶ Exemplo: Excluindo Auto-Configurações Indesejadas
@SpringBootApplication(exclude = {
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class
})
@EnableConfigurationProperties(OrderFlowProperties.class)
public class OrderFlowApplication {
public static void main(String[] args) {
SpringApplication.run(OrderFlowApplication.class, args);
}
}
Saída:
// Execução bem-sucedida
7. Organizando Postman Collections
(1) ▶ Exemplo: Script de Teste Postman
# Testes da API de Produtos
curl -X POST http://localhost:8080/api/v1/products \
-H "Content-Type: application/json" \
-d '{"name":"Laptop","price":999.99,"stock":50}'
curl http://localhost:8080/api/v1/products
curl http://localhost:8080/api/v1/products/1
# Testes da API de Pedidos
curl -X POST http://localhost:8080/api/v1/orders \
-H "Content-Type: application/json" \
-d '{"productId":1,"quantity":3}'
curl http://localhost:8080/api/v1/orders/1
# Verificação de configuração
curl http://localhost:8080/api/v1/health
Saída:
{"status":"ok","data":{}}
| Cenário de Teste | API | Código de Status Esperado |
|---|---|---|
| Criar Produto | POST /api/v1/products | 201 |
| Consultar Lista de Produtos | GET /api/v1/products | 200 |
| Consultar um Único Produto | GET /api/v1/products/1 | 200 |
| Criar Pedido | POST /api/v1/orders | 201 |
| Pedido Excessivo | POST /api/v1/orders (qty=200) | 400 |
8. Exemplo Abrangente: Framework Básico Completo do OrderFlow
// OrderFlowApplication.java
package com.orderflow;
import com.orderflow.config.OrderFlowProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(OrderFlowProperties.class)
public class OrderFlowApplication {
public static void main(String[] args) {
SpringApplication.run(OrderFlowApplication.class, args);
}
}
// OrderFlowProperties.java
package com.orderflow.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(prefix = "orderflow")
public record OrderFlowProperties(
int maxItemsPerOrder,
Duration orderTimeout,
String defaultCurrency
) {}
# application.yml
spring:
application:
name: orderflow-service
profiles:
active: dev
orderflow:
max-items-per-order: 100
order-timeout: 30m
default-currency: USD
---
# application-dev.yml
spring:
datasource:
url: jdbc:h2:mem:orderflow_dev
username: sa
password:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
logging:
level:
com.orderflow: DEBUG
---
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/orderflow
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
show-sql: false
logging:
level:
com.orderflow: WARN
# Inicialização no modo dev
$ java -jar orderflow-service.jar --spring.profiles.active=dev
Started OrderFlowApplication in 3.2 seconds
# Inicialização no modo prod
$ java -jar orderflow-service.jar --spring.profiles.active=prod
Started OrderFlowApplication in 5.1 seconds
❓ Perguntas Frequentes
application.yml e configurações específicas do ambiente em application-{profile}.yml. Sempre injete informações sensíveis (senhas, chaves) usando variáveis de ambiente.The following 1 profile is active: "dev" será impresso no log de inicialização. Você também pode injetar @Value("${spring.profiles.active}") no Controller e retorná-lo.record como DTO?record é imutável e não é adequado para entidades JPA (que requerem construtor sem argumentos e campos mutáveis). No entanto, é muito adequado para uso como DTOs de requisição/resposta na camada de controller.📖 Resumo
- Use Spring Initializr, Profile e @ConfigurationProperties juntos para configurar o esqueleto do projeto
- As APIs REST para
ProducteOrderatualmente usam um mapa em memória para armazenamento; isso será substituído por JPA no futuro - Configurações de negócio estão centralizadas em
OrderFlowProperties, com suporte de múltiplos ambientes fornecido através deProfile - Exclua auto-configurações desnecessárias para evitar erros de inicialização
- Postman Collections são agrupadas por recurso, facilitando a execução de testes contínuos
📝 Exercícios
-
Exercício Básico (Dificuldade: ⭐): Complete todo o código desta aula, garanta que o ambiente dev inicie com sucesso e verifique que todos os comandos de teste curl passam.
-
Exercício Avançado (Dificuldade ⭐⭐): Adicione os endpoints
PUT /api/v1/products/{id}eDELETE /api/v1/products/{id}, adicione o endpoint de listaGET /api/v1/ordersao OrderController, e implemente filtragem usando o parâmetro de consultastatus. -
Desafio (Dificuldade: ⭐⭐⭐): Crie um
orderflow-spring-boot-starterque envolva o endpoint de health check do OrderFlow (que retorna o nome da aplicação, versão, profile e informações da JVM), integre-o no projeto OrderFlow e verifique que a auto-configuração entra em vigor.



