Camada que atua sob framework Spring, simplificando-o e modernizando-o, seguindo padrão MVC. Spring implementa padrão IoC (Inversion of Control), onde os objetos (Spring Beans / Beans) são instanciados automaticamente pelo container do Spring, dispensando criação manual.
JDK (Java Development Kit) é kit de ferramentas e bibliotecas para desenvolver aplicações Java. Gradle é automatizador de build moderno que usa linguagem baseada em Groovy ou Kotlin para configurar projetos.
Em 'start.spring.io', selecione:
Estrutura tradicional/standard (Structure/Package by Layer), onde cada camada (Controller, Service, Repository) é organizada em pacotes separados. Melhor cenário: sistemas mais simples, bem estruturados, com responsabilidades claras (como em CRUDs). Pior cenário: pouca flexibilidade para mudanças, acoplamento entre camadas e dificuldade em evoluir o domínio:
com.company.project
├── ProjectApplication.java
├── config
│ ├── SecurityConfig.java
│ ├── DatabaseConfig.java
│ └── SwaggerConfig.java
├── controller
│ ├── UserController.java
│ └── ProductController.java
├── service
│ ├── UserService.java
│ ├── UserServiceImpl.java
│ ├── ProductService.java
│ └── ProductServiceImpl.java
├── repository
│ ├── UserRepository.java
│ └── ProductRepository.java
├── model
│ ├── entity
│ │ ├── User.java
│ │ └── Product.java
│ └── dto
│ ├── UserDTO.java
│ ├── CreateUserRequest.java
│ └── ProductDTO.java
├── exception
│ ├── GlobalExceptionHandler.java
│ ├── ResourceNotFoundException.java
│ └── ValidationException.java
├── mapper
│ ├── UserMapper.java
│ └── ProductMapper.java
├── security
│ ├── JwtTokenProvider.java
│ └── CustomUserDetailsService.java
└── util
├── DateUtil.java
└── ValidationUtil.java
src/main/resources:
resources
├── application.yml
├── application-dev.yml
├── application-prod.yml
├── static
│ └── (static files)
├── templates
│ └── (template files)
└── db
└── migration
├── V1__Create_users_table.sql
└── V2__Add_products_table.sql
src/test:
src/test/java/com/company/project
├── controller
│ └── UserControllerTest.java
├── service
│ └── UserServiceTest.java
└── repository
└── UserRepositoryTest.java
Arquitetura anterior, onde projeto é organizado em módulos via pacotes, que seguem a estrutura de camadas tradicional (controller/seevice/repository/model):
com.whateverer.config
├── SwaggerConfig.kt
├── SecurityConfig.kt
├── JwtConfig.kt
├── OpenApiConfig.kt
├── JacksonConfig.kt
├── CorsConfig.kt
├── DatabaseConfig.kt
├── CacheConfig.kt
└── WebMvcConfig.kt
com.whateverer.feature.account
├── controller
│ ├── AccountController.kt
│ ├── AccountAdminController.kt
│ └── AccountAuthController.kt
│
├── model
│ ├── dto
│ │ ├── CreateAccountRequest.kt
│ │ ├── UpdateAccountRequest.kt
│ │ ├── AccountResponse.kt
│ │ ├── AccountSummaryResponse.kt
│ │ └── AccountLoginRequest.kt
│ │
│ └── entity
│ ├── AccountEntity.kt
│ ├── AccountProfileEntity.kt
│ ├── AccountRoleEntity.kt
│ └── AccountStatus.kt
│
├── repository
│ ├── AccountRepository.kt
│ ├── AccountQueryRepository.kt
│ └── AccountCustomRepository.kt
│
├── service
│ ├── AccountService.kt
│ ├── CreateAccountService.kt
│ ├── UpdateAccountService.kt
│ ├── DeleteAccountService.kt
│ ├── GetAccountService.kt
│ ├── ListAccountService.kt
│ ├── AccountValidationService.kt
│ └── AccountAuthService.kt
│
└── utils
├── AccountMapper.kt
├── AccountUtils.kt
├── AccountValidator.kt
├── AccountConstants.kt
└── AccountPermissions.kt
com.whateverer.feature.payment
├── controller
│ ├── PaymentController.kt
│ ├── RefundController.kt
│ └── PaymentWebhookController.kt
│
├── model
│ ├── dto
│ │ ├── CreatePaymentRequest.kt
│ │ ├── PaymentResponse.kt
│ │ ├── RefundRequest.kt
│ │ ├── PaymentStatusResponse.kt
│ │ └── InstallmentSimulationResponse.kt
│ │
│ └── entity
│ ├── PaymentEntity.kt
│ ├── RefundEntity.kt
│ ├── PaymentMethodEntity.kt
│ └── PaymentStatus.kt
│
├── repository
│ ├── PaymentRepository.kt
│ ├── RefundRepository.kt
│ └── PaymentQueryRepository.kt
│
├── service
│ ├── PaymentService.kt
│ ├── RefundService.kt
│ ├── PaymentGatewayService.kt
│ ├── PaymentValidationService.kt
│ ├── InstallmentService.kt
│ └── PaymentWebhookService.kt
│
└── utils
├── PaymentMapper.kt
├── PaymentUtils.kt
├── PaymentValidator.kt
├── PaymentConstants.kt
└── InstallmentCalculator.kt
Estrutura moderna (Structure/Package by Feature), onde cada feature/recurso é organizada em pacotes separados, sendo ideal para projetos complexos, escaláveis e microservices. Melhor cenário: sistemas grandes, com várias funcionalidades, onde cada módulo agrupa lógica, testes e regras relacionadas. Pior cenário: pode gerar duplicação de código se não for bem estruturado, e o time precisa se organizar para manter o alinhamento entre features:
com.myapp.project
├── Application.java
├── user/
│ ├── UserController.java
│ ├── UserService.java
│ ├── UserRepository.java
│ ├── User.java (entity)
│ ├── UserDto.java
├── product/
│ ├── ProductController.java
│ ├── ProductService.java
│ ├── ProductRepository.java
│ ├── Product.java
│ ├── ProductDto.java
├── auth/
│ ├── AuthController.java
│ ├── AuthService.java
│ ├── JwtService.java
│ ├── JwtAuthenticationFilter.java
│ ├── CustomUserDetailsService.java
│ ├── LoginRequest.java
│ ├── RegisterRequest.java
│ ├── AuthResponse.java
│ └── JwtUtil.java
├── config/
│ ├── AppConfig.java
│ ├── SecurityConfig.java
│ ├── DatabaseConfig.java
│ ├── SwaggerConfig.java
│ └── JwtConfig.java
├── exception/
│ ├── GlobalExceptionHandler.java
│ ├── ResourceNotFoundException.java
│ ├── ValidationException.java
│ ├── UnauthorizedException.java
│ ├── InvalidTokenException.java
│ └── TokenExpiredException.java
├── mapper/
│ ├── UserMapper.java
│ └── ProductMapper.java
└── util/
├── DateUtil.java
├── ValidationUtil.java
└── PasswordUtil.java
src/main/resources:
resources
├── application.yml
├── application-dev.yml
├── application-prod.yml
├── static
│ └── (static files, css, js, images)
├── templates
│ └── (template files, html, thymeleaf)
├── config
│ ├── security.yml
│ ├── swagger.yml
│ ├── e.g. application.yml
│ ├── logging.yml
│ └── database.ym
└── db
└── migration
├── V1__Create_users_table.sql
└── V2__Add_products_table.sql
src/test:
src/test/java/com/myapp/project
├── user/
│ ├── UserControllerTest.java
│ ├── UserServiceTest.java
│ └── UserRepositoryTest.java
├── product/
│ ├── ProductControllerTest.java
│ ├── ProductServiceTest.java
│ └── ProductRepositoryTest.java
└── config/
└── SecurityConfigTest.java
Arquitetura moderna (Hexagonal Architecture / Ports and Adapters), onde a aplicação é organizada em camadas concêntricas, com o domínio central isolado de detalhes de infraestrutura, sendo ideal para projetos complexos, escaláveis e microservices. Melhor cenário: sistemas complexos, com alto desacoplamento, onde o domínio é protegido de detalhes externos, permitindo fácil troca de tecnologia e alta testabilidade. Pior cenário: curva de aprendizado mais alta, abstrações adicionais, exigindo mais esforço para implementar no início:
src/main/java/com/myapp/project
├── application/
│ ├── port/
│ │ ├── input/
│ │ │ ├── UserUseCase.java
│ │ │ └── ProductUseCase.java
│ │ └── output/
│ │ ├── UserPersistencePort.java
│ │ └── ProductPersistencePort.java
│ ├── service/
│ │ ├── UserServiceImpl.java
│ │ └── ProductServiceImpl.java
│ ├── dto/
│ │ ├── UserDto.java
│ │ └── ProductDto.java
│ └── mapper/
│ ├── UserMapper.java
│ └── ProductMapper.java
├── domain/
│ ├── model/
│ │ ├── User.java
│ │ └── Product.java
│ ├── exception/
│ │ ├── ResourceNotFoundException.java
│ │ ├── ValidationException.java
│ │ ├── UnauthorizedException.java
│ │ ├── InvalidTokenException.java
│ │ └── TokenExpiredException.java
│ └── vo/
│ └── (Value Objects opcionais)
├── infrastructure/
│ ├── input/
│ │ ├── rest/
│ │ │ ├── UserController.java
│ │ │ └── ProductController.java
│ │ └── config/
│ │ ├── SwaggerConfig.java
│ │ └── SecurityConfig.java
│ └── output/
│ ├── persistence/
│ │ ├── UserRepositoryImpl.java
│ │ └── ProductRepositoryImpl.java
│ ├── security/
│ │ ├── JwtTokenProvider.java
│ │ └── CustomUserDetailsService.java
│ └── config/
│ └── DatabaseConfig.java
├── shared/
│ └── exception/
│ └── GlobalExceptionHandler.java
└── Application.java
Arquitetura profissional moderna (Modular Monolith + Clean Architecture + DDD), onde aplicação é organizada em módulos independentes, seguindo princípios de Clean Architecture e Domain-Driven Design, sendo ideal para projetos complexos, escaláveis e microservices. Melhor cenário: sistemas complexos, com alto desacoplamento, onde o domínio é protegido de detalhes externos, permitindo fácil evolução do domínio e alta testabilidade. Pior cenário: curva de aprendizado mais alta, abstrações adicionais, exigindo mais esforço para implementar no início:
my-erp-system/
├── build.gradle.kts
├── settings.gradle.kts
├── gradle.properties
├── docker-compose.yml
├── Dockerfile
├── README.md
├── .gitignore
├── .editorconfig
├── .env
│
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
│
├── docs/
│ ├── architecture/
│ │ ├── context-diagram.md
│ │ ├── containers-diagram.md
│ │ ├── components-diagram.md
│ │ ├── domain-overview.md
│ │ ├── bounded-contexts.md
│ │ └── decisions/
│ │ ├── adr-001-modular-monolith.md
│ │ ├── adr-002-clean-architecture.md
│ │ └── adr-003-ddd.md
│ │
│ ├── api/
│ │ ├── openapi.yaml
│ │ └── postman-collection.json
│ │
│ └── database/
│ ├── schema.drawio
│ └── migrations.md
│
├── scripts/
│ ├── start-local.sh
│ ├── build.sh
│ ├── deploy.sh
│ └── test.sh
│
├── src/
│ ├── main/
│ │ ├── kotlin/
│ │ │ └── com/
│ │ │ └── company/
│ │ │ └── erp/
│ │ │ │
│ │ │ ├── ErpApplication.kt
│ │ │ │
│ │ │ ├── shared/
│ │ │ │ │
│ │ │ │ ├── kernel/
│ │ │ │ │ ├── domain/
│ │ │ │ │ │ ├── AggregateRoot.kt
│ │ │ │ │ │ ├── Entity.kt
│ │ │ │ │ │ ├── ValueObject.kt
│ │ │ │ │ │ ├── DomainEvent.kt
│ │ │ │ │ │ ├── Identifier.kt
│ │ │ │ │ │ └── AuditableEntity.kt
│ │ │ │ │ │
│ │ │ │ │ ├── application/
│ │ │ │ │ │ ├── UseCase.kt
│ │ │ │ │ │ ├── Command.kt
│ │ │ │ │ │ ├── Query.kt
│ │ │ │ │ │ ├── CommandHandler.kt
│ │ │ │ │ │ ├── QueryHandler.kt
│ │ │ │ │ │ └── EventPublisher.kt
│ │ │ │ │ │
│ │ │ │ │ ├── infrastructure/
│ │ │ │ │ │ ├── BaseEntity.kt
│ │ │ │ │ │ ├── BaseRepository.kt
│ │ │ │ │ │ ├── JpaConfig.kt
│ │ │ │ │ │ └── TransactionConfig.kt
│ │ │ │ │ │
│ │ │ │ │ └── exception/
│ │ │ │ │ ├── BusinessException.kt
│ │ │ │ │ ├── ValidationException.kt
│ │ │ │ │ ├── NotFoundException.kt
│ │ │ │ │ └── DomainException.kt
│ │ │ │ │
│ │ │ │ ├── config/
│ │ │ │ │ ├── JacksonConfig.kt
│ │ │ │ │ ├── SecurityConfig.kt
│ │ │ │ │ ├── OpenApiConfig.kt
│ │ │ │ │ ├── CacheConfig.kt
│ │ │ │ │ ├── KafkaConfig.kt
│ │ │ │ │ └── ClockConfig.kt
│ │ │ │ │
│ │ │ │ ├── security/
│ │ │ │ │ ├── JwtService.kt
│ │ │ │ │ ├── JwtAuthenticationFilter.kt
│ │ │ │ │ ├── UserPrincipal.kt
│ │ │ │ │ └── AuthenticatedUser.kt
│ │ │ │ │
│ │ │ │ ├── observability/
│ │ │ │ │ ├── LoggingInterceptor.kt
│ │ │ │ │ ├── MetricsConfig.kt
│ │ │ │ │ ├── TracingConfig.kt
│ │ │ │ │ └── CorrelationIdFilter.kt
│ │ │ │ │
│ │ │ │ ├── messaging/
│ │ │ │ │ ├── InternalEventBus.kt
│ │ │ │ │ ├── KafkaEventPublisher.kt
│ │ │ │ │ └── EventSerializer.kt
│ │ │ │ │
│ │ │ │ └── web/
│ │ │ │ ├── ApiErrorResponse.kt
│ │ │ │ ├── GlobalExceptionHandler.kt
│ │ │ │ ├── PaginationRequest.kt
│ │ │ │ └── PaginationResponse.kt
│ │ │ │
│ │ │ ├── modules/
│ │ │ │
│ │ │ │ ├── customer/
│ │ │ │ │ │
│ │ │ │ │ ├── domain/
│ │ │ │ │ │ ├── model/
│ │ │ │ │ │ │ ├── Customer.kt
│ │ │ │ │ │ │ ├── CustomerAddress.kt
│ │ │ │ │ │ │ ├── CustomerDocument.kt
│ │ │ │ │ │ │ ├── CustomerStatus.kt
│ │ │ │ │ │ │ └── CustomerId.kt
│ │ │ │ │ │ │
│ │ │ │ │ │ ├── event/
│ │ │ │ │ │ │ ├── CustomerCreatedEvent.kt
│ │ │ │ │ │ │ ├── CustomerUpdatedEvent.kt
│ │ │ │ │ │ │ └── CustomerDeletedEvent.kt
│ │ │ │ │ │ │
│ │ │ │ │ │ ├── service/
│ │ │ │ │ │ │ ├── CustomerDomainService.kt
│ │ │ │ │ │ │ └── CustomerValidationService.kt
│ │ │ │ │ │ │
│ │ │ │ │ │ └── repository/
│ │ │ │ │ │ └── CustomerRepository.kt
│ │ │ │ │ │
│ │ │ │ │ ├── application/
│ │ │ │ │ │ ├── usecase/
│ │ │ │ │ │ │ ├── createcustomer/
│ │ │ │ │ │ │ │ ├── CreateCustomerCommand.kt
│ │ │ │ │ │ │ │ ├── CreateCustomerUseCase.kt
│ │ │ │ │ │ │ │ ├── CreateCustomerResponse.kt
│ │ │ │ │ │ │ │ └── CreateCustomerValidator.kt
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ ├── updatecustomer/
│ │ │ │ │ │ │ │ ├── UpdateCustomerCommand.kt
│ │ │ │ │ │ │ │ ├── UpdateCustomerUseCase.kt
│ │ │ │ │ │ │ │ └── UpdateCustomerValidator.kt
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ ├── getcustomer/
│ │ │ │ │ │ │ │ ├── GetCustomerQuery.kt
│ │ │ │ │ │ │ │ ├── GetCustomerUseCase.kt
│ │ │ │ │ │ │ │ └── CustomerDetailsResponse.kt
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ ├── listcustomers/
│ │ │ │ │ │ │ │ ├── ListCustomersQuery.kt
│ │ │ │ │ │ │ │ ├── ListCustomersUseCase.kt
│ │ │ │ │ │ │ │ └── CustomerSummaryResponse.kt
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └── deletecustomer/
│ │ │ │ │ │ │ ├── DeleteCustomerCommand.kt
│ │ │ │ │ │ │ └── DeleteCustomerUseCase.kt
│ │ │ │ │ │ │
│ │ │ │ │ │ ├── port/
│ │ │ │ │ │ │ ├── input/
│ │ │ │ │ │ │ │ ├── CreateCustomerInputPort.kt
│ │ │ │ │ │ │ │ ├── UpdateCustomerInputPort.kt
│ │ │ │ │ │ │ │ └── GetCustomerInputPort.kt
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └── output/
│ │ │ │ │ │ │ ├── LoadCustomerPort.kt
│ │ │ │ │ │ │ ├── SaveCustomerPort.kt
│ │ │ │ │ │ │ └── DeleteCustomerPort.kt
│ │ │ │ │ │ │
│ │ │ │ │ │ ├── mapper/
│ │ │ │ │ │ │ ├── CustomerMapper.kt
│ │ │ │ │ │ │ └── CustomerResponseMapper.kt
│ │ │ │ │ │ │
│ │ │ │ │ │ └── eventhandler/
│ │ │ │ │ │ └── CustomerCreatedEventHandler.kt
│ │ │ │ │ │
│ │ │ │ │ ├── infrastructure/
│ │ │ │ │ │ ├── persistence/
│ │ │ │ │ │ │ ├── entity/
│ │ │ │ │ │ │ │ ├── CustomerJpaEntity.kt
│ │ │ │ │ │ │ │ └── CustomerAddressEmbeddable.kt
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ ├── repository/
│ │ │ │ │ │ │ │ ├── JpaCustomerRepository.kt
│ │ │ │ │ │ │ │ └── SpringDataCustomerRepository.kt
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └── mapper/
│ │ │ │ │ │ │ ├── CustomerJpaMapper.kt
│ │ │ │ │ │ │ └── CustomerEntityMapper.kt
│ │ │ │ │ │ │
│ │ │ │ │ │ ├── web/
│ │ │ │ │ │ │ ├── CustomerController.kt
│ │ │ │ │ │ │ ├── request/
│ │ │ │ │ │ │ │ ├── CreateCustomerRequest.kt
│ │ │ │ │ │ │ │ └── UpdateCustomerRequest.kt
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └── response/
│ │ │ │ │ │ │ ├── CustomerResponse.kt
│ │ │ │ │ │ │ └── CustomerListResponse.kt
│ │ │ │ │ │ │
│ │ │ │ │ │ ├── messaging/
│ │ │ │ │ │ │ ├── consumer/
│ │ │ │ │ │ │ │ └── CustomerEventConsumer.kt
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └── producer/
│ │ │ │ │ │ │ └── CustomerEventProducer.kt
│ │ │ │ │ │ │
│ │ │ │ │ │ └── config/
│ │ │ │ │ │ └── CustomerModuleConfig.kt
│ │ │ │ │ │
│ │ │ │ │ └── CustomerModule.kt
│ │ │ │ │
│ │ │ │ ├── order/
│ │ │ │ │ ├── domain/
│ │ │ │ │ ├── application/
│ │ │ │ │ ├── infrastructure/
│ │ │ │ │ └── OrderModule.kt
│ │ │ │ │
│ │ │ │ ├── payment/
│ │ │ │ │ ├── domain/
│ │ │ │ │ ├── application/
│ │ │ │ │ ├── infrastructure/
│ │ │ │ │ └── PaymentModule.kt
│ │ │ │ │
│ │ │ │ ├── inventory/
│ │ │ │ │ ├── domain/
│ │ │ │ │ ├── application/
│ │ │ │ │ ├── infrastructure/
│ │ │ │ │ └── InventoryModule.kt
│ │ │ │ │
│ │ │ │ └── auth/
│ │ │ │ ├── domain/
│ │ │ │ ├── application/
│ │ │ │ ├── infrastructure/
│ │ │ │ └── AuthModule.kt
│ │ │ │
│ │ │ └── bootstrap/
│ │ │ ├── DatabaseSeeder.kt
│ │ │ ├── ModuleRegistrar.kt
│ │ │ └── StartupRunner.kt
│ │ │
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── application-dev.yml
│ │ ├── application-test.yml
│ │ ├── application-prod.yml
│ │ │
│ │ ├── db/
│ │ │ └── migration/
│ │ │ ├── V1__create_customer_table.sql
│ │ │ ├── V2__create_order_table.sql
│ │ │ ├── V3__create_payment_table.sql
│ │ │ └── V4__create_inventory_table.sql
│ │ │
│ │ ├── banner.txt
│ │ ├── logback-spring.xml
│ │ └── messages/
│ │ ├── messages.properties
│ │ └── validation.properties
│ │
│ └── test/
│ └── kotlin/
│ └── com/
│ └── company/
│ └── erp/
│ ├── integration/
│ │ ├── CustomerIntegrationTest.kt
│ │ ├── OrderIntegrationTest.kt
│ │ └── PaymentIntegrationTest.kt
│ │
│ ├── unit/
│ │ ├── customer/
│ │ │ ├── domain/
│ │ │ ├── application/
│ │ │ └── infrastructure/
│ │ │
│ │ ├── order/
│ │ └── payment/
│ │
│ ├── architecture/
│ │ ├── ModularizationTest.kt
│ │ ├── LayerDependencyTest.kt
│ │ ├── HexagonalRulesTest.kt
│ │ └── DddConventionsTest.kt
│ │
│ └── e2e/
│ ├── CustomerFlowE2ETest.kt
│ └── OrderFlowE2ETest.kt
│
└── .github/
└── workflows/
├── ci.yml
├── cd.yml
└── code-quality.yml
Baseado na arquitetura Layer-based Structure com módulos.
resources
├── application.yml # Configurações gerais da aplicação, como porta, datasource, logging, etc.
├── application-dev.yml # Configurações específicas para ambiente de desenvolvimento, como banco de dados local, logging detalhado, etc.
├── application-prod.yml # Configurações específicas para ambiente de produção, como banco de dados em nuvem, logging otimizado, etc.
├── static # Diretório com arquivos estáticos, como CSS, JavaScript e imagens, usados para servir conteúdo estático (se aplicável)
├── templates # Diretório com arquivos de template, como HTML ou Thymeleaf, usados para renderizar páginas (se aplicável)
└── db # Scripts de banco de dados, como migrações SQL ou arquivos de configuração do Flyway/Liquibase
└── migration # Scripts de migração do banco de dados, nomeados seguindo convenção (V1__create_users_table.sql, V2__add_products_table.sql, etc.)
├── V1__create_users_table.sql # Exemplo de script de migração para criar tabela de usuários
└── V2__add_products_table.sql # Exemplo de script de migração para adicionar tabela de produtos
com.whateverer.config # Configurações globais
├── SwaggerConfig.kt # Configuração do Swagger para documentação da API
├── SecurityConfig.kt # Configuração de segurança, incluindo autenticação, autorização, filtros de segurança e rotas públicas/privadas
├── JwtConfig.kt # Configuração específica para JWT (JSON Web Token), como geração e validação de tokens, além de expiração e assinatura
├── OpenApiConfig.kt # Configuração da documentação OpenAPI, contendo título da API, versão, descrição e autenticação JWT no Swagger
├── JacksonConfig.kt # Configuração do serializer/deserializer JSON do Jackson, que converte Kotlin objects para JSON e vice-versa, como formatos de datas, null handling
├── CorsConfig.kt # Configuração de CORS (Cross-Origin Resource Sharing), definindo quais domínios podem acessar a API (como frontend), métodos HTTP permitidos e cabeçalhos
├── DatabaseConfig.kt # Configuração do banco de dados, incluindo conexão, datasource, JPA/Hibernate, pool de conexões, transações e demais propriedades de acesso ao banco
├── CacheConfig.kt # Configuração de cache, definindo quais dados devem ser armazenados em cache, tempo de expiração e estratégia de cache (como Redis, cache local ou Caffeine) para otimização de desempenho e redução de queries
└── WebMvcConfig.kt # Configuração do Spring MVC (Model-View-Controller), incluindo resolvers, interceptors, formatadores, conversores, manipuladores de exceção e outras customizações relacionadas ao comportamento da camada web geral
com.whateverer.feature.account # Funcionalidades relacionadas ao domínio de gestão de contas e usuários
├── controller # Camada de controle, responsável por receber requisições HTTP REST, validar dados de entrada, chamar os serviços e retornar respostas adequadas
│ ├── AccountController.kt # Controlador para endpoints principais de conta (criar, obter, atualizar, excluir)
│ ├── AccountAdminController.kt # Controlador para endpoints administrativos de conta (listar contas, alterar status, atribuir papéis, bloquear contas)
│ └── AccountAuthController.kt # Controlador para endpoints de autenticação e autorização (login, logout, refresh token)
│
├── model # Camada de modelo, contendo definições de dados usados na aplicação, como DTOs (Data Transfer Objects) para comunicação entre camadas e entidades JPA para persistência no banco de dados
│ ├── dto # Pacote de DTOs, objetos usados para transferir dados entre camadas em comunicação HTTP, como requisições e respostas da API
│ │ ├── CreateAccountRequest.kt # Dados recebidos ao criar conta
│ │ ├── UpdateAccountRequest.kt # Dados recebidos ao atualizar conta
│ │ ├── AccountResponse.kt # Dados retornados/resposta ao obter detalhes de conta
│ │ ├── AccountSummaryResponse.kt # Dados retornados/resumo ao listar contas
│ │ └── AccountLoginRequest.kt # Payload, dados recebidos ao realizar login
│ │
│ └── entity # Pacote de entidades, classes que representam tabelas do banco de dados usando JPA/Hibernate, contendo anotações de mapeamento e relacionamentos
│ ├── AccountEntity.kt # Entidade principal de conta, representando tabela principal de contas no banco de dados
│ ├── AccountProfileEntity.kt # Entidade de perfil de conta, representando informações adicionais do usuário, como nome, email, telefone
│ ├── AccountRoleEntity.kt # Entidade de papel de conta, representando os papéis/roles/permissões atribuídos a cada conta para controle de acesso (admin, user)
│ └── AccountStatus.kt # Enumeração de status de conta, representando possíveis estados de conta (ativa, inativa, bloqueada, etc.)
│
├── repository # Camada de repositório, responsável por acessar banco de dados e realizar operações de CRUD (Create, Read, Update, Delete) via JPA/Hibernate, além de consultas personalizadas
│ ├── AccountRepository.kt # Repositório principal de conta, estendendo JpaRepository para operações básicas de CRUD
│ ├── AccountQueryRepository.kt # Repositório de consultas personalizadas, contendo métodos para consultas/queries complexas ou específicas que não se encaixam no padrão do JpaRepository
│ └── AccountCustomRepository.kt # Repositório customizado, contendo implementações personalizadas de métodos de acesso a dados que exigem lógica específica/personalizada ou otimizações
│
├── service # Camada de serviço, responsável por implementar lógica de negócio da aplicação, orquestrando operações entre repositórios, validando regras de negócio e garantindo a integridade dos dados
│ ├── AccountService.kt # Serviço principal de conta, contendo regras/métodos para criar, atualizar, excluir e obter contas, além de aplicar regras de negócio relacionadas a contas
│ ├── CreateAccountService.kt # Serviço específico para criação de contas, contendo regras/lógica de negócio relacionada à criação, como validação de dados, verificação de duplicidade, atribuição de papéis padrão, etc
│ ├── UpdateAccountService.kt # Serviço específico para atualização de contas, contendo regras/lógica de negócio relacionada à atualização, como validação de dados, controle de alterações, etc
│ ├── DeleteAccountService.kt # Serviço específico para exclusão de contas, contendo regras/lógica de negócio relacionada à exclusão, como verificação de dependências, controle de status, etc
│ ├── GetAccountService.kt # Serviço específico para obtenção de detalhes de conta individual, contendo regras/lógica de negócio relacionada à obtenção, como controle de acesso, formatação de dados, etc
│ ├── ListAccountService.kt # Serviço específico para listagem de contas, contendo regras/lógica de negócio relacionada à listagem, como paginação, filtragem, ordenação, etc
│ ├── AccountValidationService.kt # Serviço de validação de conta, contendo regras/lógica de negócio relacionada à validação de dados de conta, como validação de email, senha, formato de dados, etc
│ └── AccountAuthService.kt # Serviço de autenticação e autorização de conta, contendo regras/lógica de negócio relacionada à autenticação (login, logout, refresh token) e controle de acesso (verificação de papéis/permissões)
│
└── utils # Camada de utilitários, contendo classes e métodos auxiliares para mapeamento de objetos (DTOs para entidades e vice-versa), validação de dados, constantes e outras funções utilitárias relacionadas ao domínio de conta
├── AccountMapper.kt # Mapeador para conversão entre entidades em DTOs, e vice-versa, de conta
├── AccountUtils.kt # Utilitários/funções diversas relacionados a conta, como formatação de dados, geração de senhas, etc
├── AccountValidator.kt # Validador para validações reutilizáveis de dados relacionados a conta, como validação de email, senha, formato de dados, etc
├── AccountConstants.kt # Constantes relacionadas a conta, como mensagens de erro, papéis padrão, etc
└── AccountPermissions.kt # Definição/centralização de permissões e roles/papéis relacionados a conta, para controle de acesso
com.whateverer.feature.payment
├── controller
│ ├── PaymentController.kt
│ ├── RefundController.kt
│ └── PaymentWebhookController.kt
│
├── model
│ ├── dto
│ │ ├── CreatePaymentRequest.kt
│ │ ├── PaymentResponse.kt
│ │ ├── RefundRequest.kt
│ │ ├── PaymentStatusResponse.kt
│ │ └── InstallmentSimulationResponse.kt
│ │
│ └── entity
│ ├── PaymentEntity.kt
│ ├── RefundEntity.kt
│ ├── PaymentMethodEntity.kt
│ └── PaymentStatus.kt
│
├── repository
│ ├── PaymentRepository.kt
│ ├── RefundRepository.kt
│ └── PaymentQueryRepository.kt
│
├── service
│ ├── PaymentService.kt
│ ├── RefundService.kt
│ ├── PaymentGatewayService.kt
│ ├── PaymentValidationService.kt
│ ├── InstallmentService.kt
│ └── PaymentWebhookService.kt
│
└── utils
├── PaymentMapper.kt
├── PaymentUtils.kt
├── PaymentValidator.kt
├── PaymentConstants.kt
└── InstallmentCalculator.kt
src.test.kotlin.com.whateverer # Pacote de testes, organizado por tipo de teste (integração, unitário, arquitetura, e2e) e por domínio/feature (account, payment, order)
├── integration # Testes de integração, que verificam interação entre diferentes módulos ou camadas do sistema
│ ├── AccountIntegrationTest.kt # Teste de integração para funcionalidades relacionadas a conta, verificando interação entre camadas (controller, service, repository) e comportamento geral da funcionalidade de conta
│ ├── PaymentIntegrationTest.kt # Teste de integração para funcionalidades relacionadas a pagamento, verificando interação entre camadas (controller, service, repository) e comportamento geral da funcionalidade de pagamento
│ └── OrderIntegrationTest.kt # Teste de integração para funcionalidades relacionadas a pedido, verificando interação entre camadas (controller, service, repository) e comportamento geral da funcionalidade de pedido
├── unit # Testes unitários, que verificam comportamento de unidades isoladas de código, como métodos ou classes específicas, usando mocks para dependências externas
│ ├── account # Testes unitários para funcionalidades relacionadas a conta, organizados por camada (controller, service, repository)
│ │ ├── controller # Testes unitários para camada de controle de conta, verificando comportamento dos endpoints REST, validação de dados de entrada e respostas adequadas
│ │ │ └── AccountControllerTest.kt # Teste unitário para AccountController, verificando comportamento dos endpoints de conta
│ │ ├── service # Testes unitários para camada de serviço de conta, verificando lógica de negócio, regras de validação e interação com repositórios
│ │ │ ├── AccountServiceTest.kt # Teste unitário para AccountService, verificando regras de negócio relacionadas a conta
│ │ │ └── AccountValidationServiceTest.kt # Teste unitário para AccountValidationService, verificando regras de validação de dados relacionados a conta
│ │ └── repository # Testes unitários para camada de repositório de conta, verificando operações de acesso a dados e consultas personalizadas
│ │ ├── AccountRepositoryTest.kt # Teste unitário para AccountRepository, verificando operações básicas de CRUD e consultas simples
│ │ └── AccountQueryRepositoryTest.kt # Teste unitário para AccountQueryRepository, verificando consultas personalizadas e complexas relacionadas a conta
│ ├── payment
│ └── order
├── architecture # Testes de arquitetura, que verificam conformidade do código com regras de arquitetura definidas, como modularização, dependências entre camadas, regras hexagonais e convenções de DDD
│ ├── ModularizationTest.kt # Teste de arquitetura para verificar modularização do código, garantindo que funcionalidades estejam organizadas em módulos coesos e com baixo acoplamento
│ ├── LayerDependencyTest.kt # Teste de arquitetura para verificar dependências entre camadas, garantindo que camadas superiores (controller) dependam apenas das camadas inferiores (service, repository) e não o contrário
│ ├── HexagonalRulesTest.kt # Teste de arquitetura para verificar regras hexagonais, garantindo que camada de domínio não dependa de detalhes de infraestrutura e que portas e adaptadores estejam corretamente implementados
│ └── DddConventionsTest.kt # Teste de arquitetura para verificar convenções de DDD (Domain-Driven Design), garantindo que entidades, agregados, repositórios e serviços estejam organizados de acordo com práticas recomendadas de DDD
└── e2e # Testes end-to-end, que verificam comportamento do sistema como todo, simulando cenários reais de uso e verificando integração entre todas camadas e módulos
├── CustomerFlowE2ETest.kt # Teste end-to-end para fluxo de cliente, simulando cenários reais de uso relacionados a clientes, como criação, atualização, obtenção e exclusão de clientes, verificando integração entre todas as camadas e módulos relacionados a cliente
└── OrderFlowE2ETest.kt # Teste end-to-end para fluxo de pedido, simulando cenários reais de uso relacionados a pedidos, como criação, atualização, obtenção e exclusão de pedidos, verificando integração entre todas camadas e módulos relacionados a pedido
Modelo de API REST Spring Boot e Kotlin, sem utilização de JWT para autenticação, nem criação de testes. Pré-requisitos são Java JDK 25, Kotlin compiler 2.4, Spring Boot 4.1, Gradle 9.6, VS Code (extensões Java, Spring Boot, Kotlin JetBrains, Postman, DBCode), conta no Postman, e Docker. Hierarquia de diretórios Feature-Based Architecture (Package by Feature) básica:
cat_api_simples
├── build.gradle.kts
├── settings.gradle.kts
└── src
└── main
├── kotlin
│ └── com
│ └── ubsocial
│ └── cat_api_simples
│ ├── Application.kt
│ ├── config
│ │ └── CorsConfig.kt
│ ├── exception
│ │ ├── BusinessException.kt
│ │ ├── ResourceNotFoundException.kt
│ │ ├── ErrorResponse.kt
│ │ └── GlobalExceptionHandler.kt
│ └── feature
│ └── cat
│ ├── controller
│ │ ├── CatController.kt
│ │ └── CatAdoptionController.kt
│ ├── mapper
│ │ └── CatMapper.kt
│ ├── model
│ │ ├── dto
│ │ │ ├── CreateCatRequest.kt
│ │ │ ├── UpdateCatRequest.kt
│ │ │ ├── AdoptCatRequest.kt
│ │ │ ├── RemoveAdoptionRequest.kt
│ │ │ ├── CatResponse.kt
│ │ │ └── CatSummaryResponse.kt
│ │ ├── entity
│ │ │ └── CatEntity.kt
│ │ └── enums
│ │ └── CatStatus.kt
│ ├── repository
│ │ └── CatRepository.kt
│ ├── service
│ │ ├── CatService.kt
│ │ ├── CreateCatService.kt
│ │ ├── UpdateCatService.kt
│ │ ├── DeleteCatService.kt
│ │ ├── GetCatService.kt
│ │ ├── ListCatService.kt
│ │ ├── AdoptCatService.kt
│ │ └── RemoveAdoptionService.kt
│ ├── validator
│ │ └── CatValidator.kt
│ └── utils
│ └── CatUtils.kt
└── resources
└── application.properties
docker pull container-registry.oracle.com/database/free:latest
docker run -d --name oracle-db -p 1521:1521 -p 5500:5500 -e ORACLE_PWD=SenhaForte123 container-registry.oracle.com/database/free:latest
CREATE USER catapi IDENTIFIED BY "CatApi@123";
GRANT CONNECT TO catapi;
GRANT RESOURCE TO catapi;
GRANT CREATE SESSION TO catapi;
GRANT CREATE TABLE TO catapi;
GRANT CREATE VIEW TO catapi;
GRANT CREATE SEQUENCE TO catapi;
GRANT CREATE TRIGGER TO catapi;
GRANT CREATE PROCEDURE TO catapi;
GRANT UNLIMITED TABLESPACE TO catapi;
spring.application.name=cat_api_simples
spring.datasource.url=jdbc:oracle:thin:@//localhost:1521/FREEPDB1
spring.datasource.username=catapi
spring.datasource.password=CatApi@123
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
spring.web.error.include-message=always
spring.web.error.include-binding-errors=always
spring.web.error.include-stacktrace=never
spring.web.error.include-exception=false
package com.ubsocial.cat_api_simples.exception
class BusinessException(
message: String
) : RuntimeException(message)
package com.ubsocial.cat_api_simples.exception
class ResourceNotFoundException(
message: String
) : RuntimeException(message)
package com.ubsocial.cat_api_simples.exception
import java.time.LocalDateTime
data class ErrorResponse(
val timestamp: LocalDateTime,
val status: Int,
val error: String,
val message: String,
val path: String
)
package com.ubsocial.cat_api_simples.exception
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import java.time.LocalDateTime
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException::class)
fun handleResourceNotFoundException(
exception: ResourceNotFoundException,
request: HttpServletRequest
): ResponseEntity<ErrorResponse> {
val errorResponse = ErrorResponse(
timestamp = LocalDateTime.now(),
status = HttpStatus.NOT_FOUND.value(),
error = HttpStatus.NOT_FOUND.reasonPhrase,
message = exception.message ?: "Resource not found.",
path = request.requestURI
)
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse)
}
@ExceptionHandler(BusinessException::class)
fun handleBusinessException(
exception: BusinessException,
request: HttpServletRequest
): ResponseEntity<ErrorResponse> {
val errorResponse = ErrorResponse(
timestamp = LocalDateTime.now(),
status = HttpStatus.BAD_REQUEST.value(),
error = HttpStatus.BAD_REQUEST.reasonPhrase,
message = exception.message ?: "Business rule violation.",
path = request.requestURI
)
return ResponseEntity.badRequest().body(errorResponse)
}
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationException(
exception: MethodArgumentNotValidException,
request: HttpServletRequest
): ResponseEntity<ErrorResponse> {
val message = exception.bindingResult
.fieldErrors
.joinToString(", ") { "${it.field}: ${it.defaultMessage}" }
val errorResponse = ErrorResponse(
timestamp = LocalDateTime.now(),
status = HttpStatus.BAD_REQUEST.value(),
error = HttpStatus.BAD_REQUEST.reasonPhrase,
message = message,
path = request.requestURI
)
return ResponseEntity.badRequest().body(errorResponse)
}
@ExceptionHandler(Exception::class)
fun handleException(
exception: Exception,
request: HttpServletRequest
): ResponseEntity<ErrorResponse> {
val errorResponse = ErrorResponse(
timestamp = LocalDateTime.now(),
status = HttpStatus.INTERNAL_SERVER_ERROR.value(),
error = HttpStatus.INTERNAL_SERVER_ERROR.reasonPhrase,
message = "An unexpected error has occurred.",
path = request.requestURI
)
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(errorResponse)
}
}
package com.ubsocial.cat_api_simples.config
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
import org.springframework.web.filter.CorsFilter
@Configuration
class CorsConfig {
@Bean
fun corsFilter(): CorsFilter {
val configuration = CorsConfiguration().apply {
allowedOriginPatterns = listOf("*")
allowedMethods = listOf("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
allowedHeaders = listOf("*")
allowCredentials = false
maxAge = 3600
}
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", configuration)
return CorsFilter(source)
}
}
package com.ubsocial.cat_api_simples.feature.cat.model.enums
enum class CatStatus {
AVAILABLE,
ADOPTED
}
package com.ubsocial.cat_api_simples.feature.cat.model.entity
import com.ubsocial.cat_api_simples.feature.cat.model.enums.CatStatus
import jakarta.persistence.*
@Entity
@Table(name = "cats")
class CatEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
var id: Long? = null,
@Column(name = "name", nullable = false)
var name: String,
@Column(name = "breed", nullable = false)
var breed: String,
@Column(name = "age", nullable = false)
var age: Int,
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false)
var status: CatStatus = CatStatus.AVAILABLE,
@Column(name = "adopter_name")
var adopterName: String? = null
)
package com.ubsocial.cat_api_simples.feature.cat.model.dto
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
data class CreateCatRequest(
@field:NotBlank(message = "Nome do gato é obrigatório")
val name: String,
@field:NotBlank(message = "Raça do gato é obrigatória")
val breed: String,
@field:Min(value = 0, message = "Idade do gato deve ser maior ou igual a zero")
val age: Int
)
package com.ubsocial.cat_api_simples.feature.cat.model.dto
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
data class UpdateCatRequest(
@field:NotBlank(message = "Nome do gato é obrigatório")
val name: String,
@field:NotBlank(message = "Raça do gato é obrigatória")
val breed: String,
@field:Min(value = 0, message = "Idade do gato deve ser maior ou igual a zero")
val age: Int
)
package com.ubsocial.cat_api_simples.feature.cat.model.dto
import jakarta.validation.constraints.NotBlank
data class AdoptCatRequest(
@field:NotBlank(message = "Nome do adotante não pode ser nulo ou vazio")
val adopterName: String
)
package com.ubsocial.cat_api_simples.feature.cat.model.dto
data class RemoveAdoptionRequest(
val reason: String? = null
)
package com.ubsocial.cat_api_simples.feature.cat.model.dto
import com.ubsocial.cat_api_simples.feature.cat.model.enums.CatStatus
data class CatResponse(
val id: Long,
val name: String,
val breed: String,
val age: Int,
val status: CatStatus,
val adopterName: String?
)
package com.ubsocial.cat_api_simples.feature.cat.model.dto
import com.ubsocial.cat_api_simples.feature.cat.model.enums.CatStatus
data class CatSummaryResponse(
val id: Long,
val name: String,
val status: CatStatus
)
package com.ubsocial.cat_api_simples.feature.cat.repository
import com.ubsocial.cat_api_simples.feature.cat.model.entity.CatEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface CatRepository : JpaRepository<CatEntity, Long>
package com.ubsocial.cat_api_simples.feature.cat.mapper
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatResponse
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatSummaryResponse
import com.ubsocial.cat_api_simples.feature.cat.model.entity.CatEntity
import org.springframework.stereotype.Component
@Component
class CatMapper {
fun toResponse(entity: CatEntity): CatResponse =
CatResponse(
id = entity.id!!,
name = entity.name,
breed = entity.breed,
age = entity.age,
status = entity.status,
adopterName = entity.adopterName
)
fun toSummaryResponse(entity: CatEntity): CatSummaryResponse =
CatSummaryResponse(
id = entity.id!!,
name = entity.name,
status = entity.status
)
}
package com.ubsocial.cat_api_simples.feature.cat.validator
import com.ubsocial.cat_api_simples.exception.BusinessException
import com.ubsocial.cat_api_simples.feature.cat.model.entity.CatEntity
import com.ubsocial.cat_api_simples.feature.cat.model.enums.CatStatus
import org.springframework.stereotype.Component
@Component
class CatValidator {
fun validateAvailableForAdoption(cat: CatEntity) {
if (cat.status == CatStatus.ADOPTED) {
throw BusinessException("Gato já foi adotado")
}
}
fun validateAdopted(cat: CatEntity) {
if (cat.status != CatStatus.ADOPTED) {
throw BusinessException("Gato não foi adotado")
}
}
}
package com.ubsocial.cat_api_simples.feature.cat.utils
import org.springframework.stereotype.Component
@Component
class CatUtils {
fun normalizeName(name: String): String =
name.trim()
.lowercase()
.replaceFirstChar { it.uppercase() }
}
package com.ubsocial.cat_api_simples.feature.cat.service
import com.ubsocial.cat_api_simples.feature.cat.model.dto.*
interface CatService {
fun create(request: CreateCatRequest): CatResponse
fun update(id: Long, request: UpdateCatRequest): CatResponse
fun delete(id: Long)
fun findById(id: Long): CatResponse
fun findAll(): List<CatSummaryResponse>
fun adopt(id: Long, request: AdoptCatRequest): CatResponse
fun removeAdoption(id: Long, request: RemoveAdoptionRequest): CatResponse
}
package com.ubsocial.cat_api_simples.feature.cat.service
import com.ubsocial.cat_api_simples.feature.cat.mapper.CatMapper
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatResponse
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CreateCatRequest
import com.ubsocial.cat_api_simples.feature.cat.model.entity.CatEntity
import com.ubsocial.cat_api_simples.feature.cat.repository.CatRepository
import com.ubsocial.cat_api_simples.feature.cat.utils.CatUtils
import org.springframework.stereotype.Service
@Service
class CreateCatService(
private val catRepository: CatRepository,
private val catMapper: CatMapper,
private val catUtils: CatUtils
) {
fun execute(request: CreateCatRequest): CatResponse {
val cat = CatEntity(
name = catUtils.normalizeName(request.name),
breed = request.breed,
age = request.age
)
val savedCat = catRepository.save(cat)
return catMapper.toResponse(savedCat)
}
}
package com.ubsocial.cat_api_simples.feature.cat.service
import com.ubsocial.cat_api_simples.exception.ResourceNotFoundException
import com.ubsocial.cat_api_simples.feature.cat.mapper.CatMapper
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatResponse
import com.ubsocial.cat_api_simples.feature.cat.model.dto.UpdateCatRequest
import com.ubsocial.cat_api_simples.feature.cat.repository.CatRepository
import com.ubsocial.cat_api_simples.feature.cat.utils.CatUtils
import org.springframework.stereotype.Service
@Service
class UpdateCatService(
private val catRepository: CatRepository,
private val catMapper: CatMapper,
private val catUtils: CatUtils
) {
fun execute(
id: Long,
request: UpdateCatRequest
): CatResponse {
val cat = catRepository.findById(id)
.orElseThrow {
ResourceNotFoundException("Gato não encontrado com id: $id")
}
cat.name = catUtils.normalizeName(request.name)
cat.breed = request.breed
cat.age = request.age
val updatedCat = catRepository.save(cat)
return catMapper.toResponse(updatedCat)
}
}
package com.ubsocial.cat_api_simples.feature.cat.service
import com.ubsocial.cat_api_simples.exception.ResourceNotFoundException
import com.ubsocial.cat_api_simples.feature.cat.repository.CatRepository
import org.springframework.stereotype.Service
@Service
class DeleteCatService(
private val catRepository: CatRepository
) {
fun execute(id: Long) {
val cat = catRepository.findById(id)
.orElseThrow {
ResourceNotFoundException("Gato não encontrado com id: $id")
}
catRepository.delete(cat)
}
}
package com.ubsocial.cat_api_simples.feature.cat.service
import com.ubsocial.cat_api_simples.exception.ResourceNotFoundException
import com.ubsocial.cat_api_simples.feature.cat.mapper.CatMapper
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatResponse
import com.ubsocial.cat_api_simples.feature.cat.repository.CatRepository
import org.springframework.stereotype.Service
@Service
class GetCatService(
private val catRepository: CatRepository,
private val catMapper: CatMapper
) {
fun execute(id: Long): CatResponse {
val cat = catRepository.findById(id)
.orElseThrow {
ResourceNotFoundException("Gato não encontrado com id: $id")
}
return catMapper.toResponse(cat)
}
}
package com.ubsocial.cat_api_simples.feature.cat.service
import com.ubsocial.cat_api_simples.feature.cat.mapper.CatMapper
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatSummaryResponse
import com.ubsocial.cat_api_simples.feature.cat.repository.CatRepository
import org.springframework.stereotype.Service
@Service
class ListCatService(
private val catRepository: CatRepository,
private val catMapper: CatMapper
) {
fun execute(): List<CatSummaryResponse> {
return catRepository.findAll()
.map(catMapper::toSummaryResponse)
}
}
package com.ubsocial.cat_api_simples.feature.cat.service
import com.ubsocial.cat_api_simples.exception.ResourceNotFoundException
import com.ubsocial.cat_api_simples.feature.cat.mapper.CatMapper
import com.ubsocial.cat_api_simples.feature.cat.model.dto.AdoptCatRequest
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatResponse
import com.ubsocial.cat_api_simples.feature.cat.repository.CatRepository
import com.ubsocial.cat_api_simples.feature.cat.validator.CatValidator
import org.springframework.stereotype.Service
@Service
class AdoptCatService(
private val catRepository: CatRepository,
private val catMapper: CatMapper,
private val catValidator: CatValidator
) {
fun execute(
id: Long,
request: AdoptCatRequest
): CatResponse {
val cat = catRepository.findById(id)
.orElseThrow {
ResourceNotFoundException("Gato não encontrado com id: $id")
}
catValidator.validateAvailableForAdoption(cat)
cat.adopterName = request.adopterName
cat.status = com.ubsocial.cat_api_simples.feature.cat.model.enums.CatStatus.ADOPTED
val adoptedCat = catRepository.save(cat)
return catMapper.toResponse(adoptedCat)
}
}
package com.ubsocial.cat_api_simples.feature.cat.service
import com.ubsocial.cat_api_simples.exception.ResourceNotFoundException
import com.ubsocial.cat_api_simples.feature.cat.mapper.CatMapper
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatResponse
import com.ubsocial.cat_api_simples.feature.cat.model.dto.RemoveAdoptionRequest
import com.ubsocial.cat_api_simples.feature.cat.model.enums.CatStatus
import com.ubsocial.cat_api_simples.feature.cat.repository.CatRepository
import com.ubsocial.cat_api_simples.feature.cat.validator.CatValidator
import org.springframework.stereotype.Service
@Service
class RemoveAdoptionService(
private val catRepository: CatRepository,
private val catMapper: CatMapper,
private val catValidator: CatValidator
) {
fun execute(
id: Long,
request: RemoveAdoptionRequest
): CatResponse {
val cat = catRepository.findById(id)
.orElseThrow {
ResourceNotFoundException("Gato não encontrado com id: $id")
}
catValidator.validateAdopted(cat)
cat.adopterName = null
cat.status = CatStatus.AVAILABLE
val updatedCat = catRepository.save(cat)
return catMapper.toResponse(updatedCat)
}
}
package com.ubsocial.cat_api_simples.feature.cat.controller
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatResponse
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CreateCatRequest
import com.ubsocial.cat_api_simples.feature.cat.model.dto.UpdateCatRequest
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatSummaryResponse
import com.ubsocial.cat_api_simples.feature.cat.service.CreateCatService
import com.ubsocial.cat_api_simples.feature.cat.service.DeleteCatService
import com.ubsocial.cat_api_simples.feature.cat.service.GetCatService
import com.ubsocial.cat_api_simples.feature.cat.service.ListCatService
import com.ubsocial.cat_api_simples.feature.cat.service.UpdateCatService
import jakarta.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/cats")
class CatController(
private val createCatService: CreateCatService,
private val updateCatService: UpdateCatService,
private val deleteCatService: DeleteCatService,
private val getCatService: GetCatService,
private val listCatService: ListCatService
) {
@PostMapping
fun create(
@Valid @RequestBody request: CreateCatRequest
): ResponseEntity<CatResponse> {
return ResponseEntity
.status(HttpStatus.CREATED)
.body(createCatService.execute(request))
}
@PutMapping("/{id}")
fun update(
@PathVariable id: Long,
@Valid @RequestBody request: UpdateCatRequest
): ResponseEntity<CatResponse> {
return ResponseEntity.ok(
updateCatService.execute(id, request)
)
}
@DeleteMapping("/{id}")
fun delete(
@PathVariable id: Long
): ResponseEntity<Void> {
deleteCatService.execute(id)
return ResponseEntity.noContent().build()
}
@GetMapping("/{id}")
fun findById(
@PathVariable id: Long
): ResponseEntity<CatResponse> {
return ResponseEntity.ok(
getCatService.execute(id)
)
}
@GetMapping
fun findAll(): ResponseEntity<List<CatSummaryResponse>> {
return ResponseEntity.ok(
listCatService.execute()
)
}
}
package com.ubsocial.cat_api_simples.feature.cat.controller
import com.ubsocial.cat_api_simples.feature.cat.model.dto.AdoptCatRequest
import com.ubsocial.cat_api_simples.feature.cat.model.dto.CatResponse
import com.ubsocial.cat_api_simples.feature.cat.model.dto.RemoveAdoptionRequest
import com.ubsocial.cat_api_simples.feature.cat.service.AdoptCatService
import com.ubsocial.cat_api_simples.feature.cat.service.RemoveAdoptionService
import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/cats")
class CatAdoptionController(
private val adoptCatService: AdoptCatService,
private val removeAdoptionService: RemoveAdoptionService
) {
@PatchMapping("/{id}/adoption")
fun adopt(
@PathVariable id: Long,
@Valid @RequestBody request: AdoptCatRequest
): ResponseEntity<CatResponse> {
return ResponseEntity.ok(
adoptCatService.execute(id, request)
)
}
@DeleteMapping("/{id}/adoption")
fun removeAdoption(
@PathVariable id: Long,
@RequestBody request: RemoveAdoptionRequest
): ResponseEntity<CatResponse> {
return ResponseEntity.ok(
removeAdoptionService.execute(id, request)
)
}
}
Body (raw JSON):
{
"name": "Mingau",
"breed": "Persa",
"age": 3
}
Body (raw JSON):
{
"name": "Mingau ATUALIZADO",
"breed": "Siamês",
"age": 4
}
Body (raw JSON):
{
"adopterName": "Mateus"
}
Body (raw JSON):
{
"reason": "Adotante desistiu da adoção"
}
Elaborado por Mateus Schwede
ubsocial.github.io