A Product API implementa o CRUD de produtos do domínio store, seguindo o mesmo padrão adotado no projeto: interface (product) e service (product-service) por trás do gateway e protegido por JWT.
Trusted layer e segurança
Toda requisição externa entra pelo gateway. As rotas /product/** são protegidas: é obrigatório enviar Authorization: Bearer <jwt>.
Visão geral
Interface (product): define o contrato (DTOs e Feign) consumido por outros módulos/fronts.
Service (product-service): implementação REST, regras de negócio, persistência (JPA), e migrações (Flyway).
classDiagram
namespace product {
class ProductController {
+create(ProductIn ProductIn): ProductOut
+delete(String id): void
+findAll(): List<ProductOut>
+findById(String id): ProductOut
}
class ProductIn {
-String name
-Double price
-String unit
}
class ProductOut {
-String id
-String name
-Double price
-String unit
}
}
namespace product-service {
class ProductResource {
+create(ProductIn ProductIn): ProductOut
+delete(String id): void
+findAll(): List<ProductOut>
+findById(String id): ProductOut
}
class ProductService {
+create(ProductIn ProductIn): ProductOut
+delete(String id): void
+findAll(): List<ProductOut>
+findById(String id): ProductOut
}
class ProductRepository {
+create(ProductIn ProductIn): ProductOut
+delete(String id): void
+findAll(): List<ProductOut>
+findById(String id): ProductOut
}
class Product {
-String id
-String name
-Double price
-String unit
}
class ProductModel {
+create(ProductIn ProductIn): ProductOut
+delete(String id): void
+findAll(): List<ProductOut>
+findById(String id): ProductOut
}
}
<<Interface>> ProductController
ProductController ..> ProductIn
ProductController ..> ProductOut
<<Interface>> ProductRepository
ProductController <|-- ProductResource
ProductResource *-- ProductService
ProductService *-- ProductRepository
ProductService ..> Product
ProductService ..> ProductModel
ProductRepository ..> ProductModel
Estrutura da requisição
flowchart LR
subgraph api [Trusted Layer]
direction TB
gateway --> account
gateway --> auth
account --> db@{ shape: cyl, label: "Database" }
auth --> account
gateway e5@==> product:::red
product e2@==> db
end
internet e1@==>|request| gateway
e1@{ animate: true }
e2@{ animate: true }
e5@{ animate: true }
classDef red fill:#fcc
click product "#product-api" "Product API"
packagestore.product;importjava.util.List;importjava.util.stream.StreamSupport;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.cache.annotation.CacheEvict;importorg.springframework.cache.annotation.CachePut;importorg.springframework.http.HttpStatus;importorg.springframework.stereotype.Service;importorg.springframework.web.server.ResponseStatusException;importorg.springframework.cache.annotation.Cacheable;importorg.springframework.cache.annotation.Caching;@ServicepublicclassProductService{@AutowiredprivateProductRepositoryproductRepository;@Caching(evict={@CacheEvict(cacheNames="products-list",allEntries=true)},put={@CachePut(cacheNames="product-by-id",key="#result.id",unless="#result == null")})publicProductcreate(Productproduct){if(null==product.name()){thrownewResponseStatusException(HttpStatus.BAD_REQUEST,"Name is mandatory!");}if(null==product.price()){thrownewResponseStatusException(HttpStatus.BAD_REQUEST,"Price is mandatory!");}if(productRepository.findByName(product.name())!=null)thrownewResponseStatusException(HttpStatus.BAD_REQUEST,"Name already have been registered!");returnproductRepository.save(newProductModel(product)).to();}@Cacheable(cacheNames="products-list",unless="#result == null || #result.isEmpty()")publicList<Product>findAll(){returnStreamSupport.stream(productRepository.findAll().spliterator(),false).map(ProductModel::to).toList();}@Cacheable(cacheNames="product-by-id",key="#id",unless="#result == null")publicProductfindById(Stringid){returnproductRepository.findById(id).map(ProductModel::to).orElseThrow(()->newResponseStatusException(HttpStatus.NOT_FOUND,"Product not found"));}@Caching(evict={@CacheEvict(cacheNames="product-by-id",key="#id"),@CacheEvict(cacheNames="products-list",allEntries=true)})publicvoiddelete(Stringid){if(!productRepository.existsById(id)){thrownewResponseStatusException(HttpStatus.NOT_FOUND,"Product not found");}productRepository.deleteById(id);}}