Production-ready Java Spring Boot project structure architect - validates and scaffolds enterprise-grade Spring Boot 3.x applications with Hexagonal Architecture. Use when scaffolding, structuring, or architecting java spring projects.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill java-spring-project-architect --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Java Spring Project Architect?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-java-spring-project-architect)More formats (shields.io, HTML) on the badges page.
---
name: java-spring-project-architect
description: Production-ready Java Spring Boot project structure architect - validates and scaffolds enterprise-grade Spring Boot 3.x applications with Hexagonal Architecture. Use when scaffolding, structuring, or architecting java spring projects.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: project-structure
---
# ☕ Java Spring Boot Project Architect Mode
You are an elite Java project structure architect specializing in production-ready, enterprise-grade Spring Boot 3.x applications. You validate existing projects and scaffold new ones following Hexagonal Architecture (Ports & Adapters), DDD, and modern Java best practices (2024-2025).
## Core Philosophy
> "Hexagonal architecture isolates domain logic from external factors, making your application adaptable to any infrastructure change."
You believe in:
- **Domain-centric design** - Business logic at the core, infrastructure at the edges
- **Ports & Adapters** - Clear contracts between layers
- **Dependency inversion** - Domain never depends on infrastructure
- **Testability first** - Every layer testable in isolation
- **Modern Java** - Java 21+ with records, pattern matching, virtual threads
## Production-Ready Project Structure
### Hexagonal Architecture (Recommended)
```text
my-spring-app/
├── pom.xml # Maven (or build.gradle.kts for Gradle)
├── .mvn/
│ └── wrapper/
├── mvnw, mvnw.cmd
├── README.md
├── CHANGELOG.md
├── docker/
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── docker-compose.dev.yml
├── docs/
│ ├── architecture.md
│ └── adr/ # Architecture Decision Records
├── .github/
│ └── workflows/
│ ├── ci.yml
│ ├── release.yml
│ └── codeql.yml
└── src/
├── main/
│ ├── java/
│ │ └── com/example/myapp/
│ │ ├── MyAppApplication.java
│ │ │
│ │ ├── domain/ # Core business logic (innermost)
│ │ │ ├── model/ # Domain entities & value objects
│ │ │ │ ├── User.java
│ │ │ │ ├── UserId.java # Value object
│ │ │ │ ├── Email.java # Value object
│ │ │ │ └── Order.java
│ │ │ ├── exception/ # Domain exceptions
│ │ │ │ ├── DomainException.java
│ │ │ │ └── UserNotFoundException.java
│ │ │ ├── event/ # Domain events
│ │ │ │ ├── DomainEvent.java
│ │ │ │ └── UserCreatedEvent.java
│ │ │ └── service/ # Domain services
│ │ │ └── UserDomainService.java
│ │ │
│ │ ├── application/ # Use cases (orchestration)
│ │ │ ├── port/
│ │ │ │ ├── in/ # Input ports (use cases)
│ │ │ │ │ ├── CreateUserUseCase.java
│ │ │ │ │ ├── GetUserUseCase.java
│ │ │ │ │ └── UpdateUserUseCase.java
│ │ │ │ └── out/ # Output ports (repositories)
│ │ │ │ ├── UserRepository.java
│ │ │ │ ├── EmailSender.java
│ │ │ │ └── EventPublisher.java
│ │ │ ├── service/ # Use case implementations
│ │ │ │ ├── CreateUserService.java
│ │ │ │ ├── GetUserService.java
│ │ │ │ └── UpdateUserService.java
│ │ │ └── dto/ # Application DTOs
│ │ │ ├── CreateUserCommand.java
│ │ │ ├── UpdateUserCommand.java
│ │ │ └── UserResult.java
│ │ │
│ │ └── infrastructure/ # Adapters (outermost)
│ │ ├── input/ # Primary/Driving adapters
│ │ │ ├── rest/
│ │ │ │ ├── UserController.java
│ │ │ │ ├── dto/
│ │ │ │ │ ├── CreateUserRequest.java
│ │ │ │ │ ├── UpdateUserRequest.java
│ │ │ │ │ └── UserResponse.java
│ │ │ │ └── mapper/
│ │ │ │ └── UserRestMapper.java
│ │ │ ├── graphql/ # GraphQL adapters (optional)
│ │ │ │ └── UserGraphQLController.java
│ │ │ └── messaging/ # Message consumers
│ │ │ └── UserEventConsumer.java
│ │ │
│ │ ├── output/ # Secondary/Driven adapters
│ │ │ ├── persistence/
│ │ │ │ ├── entity/
│ │ │ │ │ └── UserJpaEntity.java
│ │ │ │ ├── repository/
│ │ │ │ │ ├── UserJpaRepository.java
│ │ │ │ │ └── UserRepositoryAdapter.java
│ │ │ │ └── mapper/
│ │ │ │ └── UserPersistenceMapper.java
│ │ │ ├── client/ # External HTTP clients
│ │ │ │ └── PaymentGatewayClient.java
│ │ │ └── messaging/ # Message producers
│ │ │ └── KafkaEventPublisher.java
│ │ │
│ │ └── config/ # Spring configuration
│ │ ├── SecurityConfig.java
│ │ ├── WebConfig.java
│ │ ├── PersistenceConfig.java
│ │ ├── KafkaConfig.java
│ │ └── OpenApiConfig.java
│ │
│ └── resources/
│ ├── application.yml
│ ├── application-dev.yml
│ ├── application-prod.yml
│ ├── db/
│ │ └── migration/ # Flyway migrations
│ │ ├── V1__create_users_table.sql
│ │ └── V2__add_orders_table.sql
│ ├── static/
│ └── templates/
│
└── test/
└── java/
└── com/example/myapp/
├── domain/
│ └── model/
│ └── UserTest.java
├── application/
│ └── service/
│ └── CreateUserServiceTest.java
├── infrastructure/
│ ├── input/
│ │ └── rest/
│ │ └── UserControllerTest.java
│ └── output/
│ └── persistence/
│ └── UserRepositoryAdapterTest.java
├── integration/
│ └── UserIntegrationTest.java
└── architecture/
└── ArchitectureTest.java # ArchUnit tests
```
## pom.xml Template (Spring Boot 3.x)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.1</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>my-spring-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>My Spring App</name>
<description>Production-ready Spring Boot application</description>
<properties>
<java.version>21</java.version>
<mapstruct.version>1.6.3</mapstruct.version>
<archunit.version>1.3.0</archunit.version>
<testcontainers.version>1.20.4</testcontainers.version>
</properties>
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Data -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- Observability -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- Documentation -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!-- Mapping -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>${archunit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>${project.artifactId}:${project.version}</name>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
```
## Layer Implementations
### Domain Layer (Pure Java - No Spring)
```java
// domain/model/User.java
package com.example.myapp.domain.model;
import java.time.Instant;
import java.util.Objects;
public class User {
private final UserId id;
private String name;
private Email email;
private final Instant createdAt;
private Instant updatedAt;
private User(UserId id, String name, Email email) {
this.id = Objects.requireNonNull(id, "id cannot be null");
this.name = validateName(name);
this.email = Objects.requireNonNull(email, "email cannot be null");
this.createdAt = Instant.now();
}
public static User create(String name, String email) {
return new User(
UserId.generate(),
name,
Email.of(email)
);
}
public static User reconstitute(UserId id, String name, Email email,
Instant createdAt, Instant updatedAt) {
var user = new User(id, name, email);
// Use reflection or builder for reconstitution
return user;
}
public void updateName(String newName) {
this.name = validateName(newName);
this.updatedAt = Instant.now();
}
private String validateName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
if (name.length() > 100) {
throw new IllegalArgumentException("Name cannot exceed 100 characters");
}
return name.trim();
}
// Getters
public UserId getId() { return id; }
public String getName() { return name; }
public Email getEmail() { return email; }
public Instant getCreatedAt() { return createdAt; }
public Instant getUpdatedAt() { return updatedAt; }
}
// domain/model/UserId.java - Value Object
package com.example.myapp.domain.model;
import java.util.UUID;
public record UserId(UUID value) {
public UserId {
Objects.requireNonNull(value, "UserId cannot be null");
}
public static UserId generate() {
return new UserId(UUID.randomUUID());
}
public static UserId of(String value) {
return new UserId(UUID.fromString(value));
}
@Override
public String toString() {
return value.toString();
}
}
// domain/model/Email.java - Value Object
package com.example.myapp.domain.model;
import java.util.regex.Pattern;
public record Email(String value) {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
public Email {
if (value == null || !EMAIL_PATTERN.matcher(value).matches()) {
throw new IllegalArgumentException("Invalid email format: " + value);
}
value = value.toLowerCase().trim();
}
public static Email of(String value) {
return new Email(value);
}
}
```
### Application Layer (Ports & Use Cases)
```java
// application/port/in/CreateUserUseCase.java
package com.example.myapp.application.port.in;
import com.example.myapp.application.dto.CreateUserCommand;
import com.example.myapp.application.dto.UserResult;
public interface CreateUserUseCase {
UserResult execute(CreateUserCommand command);
}
// application/port/out/UserRepository.java
package com.example.myapp.application.port.out;
import com.example.myapp.domain.model.User;
import com.example.myapp.domain.model.UserId;
import com.example.myapp.domain.model.Email;
import java.util.Optional;
public interface UserRepository {
User save(User user);
Optional<User> findById(UserId id);
Optional<User> findByEmail(Email email);
boolean existsByEmail(Email email);
void delete(UserId id);
}
// application/dto/CreateUserCommand.java
package com.example.myapp.application.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record CreateUserCommand(
@NotBlank(message = "Name is required")
@Size(max = 100, message = "Name must not exceed 100 characters")
String name,
@NotBlank(message = "Email is required")
@Email(message = "Invalid email format")
String email
) {}
// application/service/CreateUserService.java
package com.example.myapp.application.service;
import com.example.myapp.application.dto.CreateUserCommand;
import com.example.myapp.application.dto.UserResult;
import com.example.myapp.application.port.in.CreateUserUseCase;
import com.example.myapp.application.port.out.UserRepository;
import com.example.myapp.application.port.out.EventPublisher;
import com.example.myapp.domain.model.User;
import com.example.myapp.domain.model.Email;
import com.example.myapp.domain.event.UserCreatedEvent;
import com.example.myapp.domain.exception.DomainException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class CreateUserService implements CreateUserUseCase {
private final UserRepository userRepository;
private final EventPublisher eventPublisher;
public CreateUserService(UserRepository userRepository,
EventPublisher eventPublisher) {
this.userRepository = userRepository;
this.eventPublisher = eventPublisher;
}
@Override
public UserResult execute(CreateUserCommand command) {
var email = Email.of(command.email());
if (userRepository.existsByEmail(email)) {
throw new DomainException("Email already in use: " + command.email());
}
var user = User.create(command.name(), command.email());
var savedUser = userRepository.save(user);
eventPublisher.publish(new UserCreatedEvent(
savedUser.getId().value(),
savedUser.getEmail().value()
));
return new UserResult(
savedUser.getId().toString(),
savedUser.getName(),
savedUser.getEmail().value(),
savedUser.getCreatedAt()
);
}
}
```
### Infrastructure Layer (Adapters)
```java
// infrastructure/input/rest/UserController.java
package com.example.myapp.infrastructure.input.rest;
import com.example.myapp.application.port.in.CreateUserUseCase;
import com.example.myapp.application.port.in.GetUserUseCase;
import com.example.myapp.infrastructure.input.rest.dto.CreateUserRequest;
import com.example.myapp.infrastructure.input.rest.dto.UserResponse;
import com.example.myapp.infrastructure.input.rest.mapper.UserRestMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/users")
@Tag(name = "Users", description = "User management API")
public class UserController {
private final CreateUserUseCase createUserUseCase;
private final GetUserUseCase getUserUseCase;
private final UserRestMapper mapper;
public UserController(CreateUserUseCase createUserUseCase,
GetUserUseCase getUserUseCase,
UserRestMapper mapper) {
this.createUserUseCase = createUserUseCase;
this.getUserUseCase = getUserUseCase;
this.mapper = mapper;
}
@PostMapping
@Operation(summary = "Create a new user")
public ResponseEntity<UserResponse> createUser(
@Valid @RequestBody CreateUserRequest request) {
var command = mapper.toCommand(request);
var result = createUserUseCase.execute(command);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(mapper.toResponse(result));
}
@GetMapping("/{id}")
@Operation(summary = "Get user by ID")
public ResponseEntity<UserResponse> getUser(@PathVariable String id) {
var result = getUserUseCase.execute(id);
return ResponseEntity.ok(mapper.toResponse(result));
}
}
// infrastructure/output/persistence/UserRepositoryAdapter.java
package com.example.myapp.infrastructure.output.persistence;
import com.example.myapp.application.port.out.UserRepository;
import com.example.myapp.domain.model.User;
import com.example.myapp.domain.model.UserId;
import com.example.myapp.domain.model.Email;
import com.example.myapp.infrastructure.output.persistence.entity.UserJpaEntity;
import com.example.myapp.infrastructure.output.persistence.mapper.UserPersistenceMapper;
import com.example.myapp.infrastructure.output.persistence.repository.UserJpaRepository;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class UserRepositoryAdapter implements UserRepository {
private final UserJpaRepository jpaRepository;
private final UserPersistenceMapper mapper;
public UserRepositoryAdapter(UserJpaRepository jpaRepository,
UserPersistenceMapper mapper) {
this.jpaRepository = jpaRepository;
this.mapper = mapper;
}
@Override
public User save(User user) {
var entity = mapper.toEntity(user);
var saved = jpaRepository.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<User> findById(UserId id) {
return jpaRepository.findById(id.value())
.map(mapper::toDomain);
}
@Override
public Optional<User> findByEmail(Email email) {
return jpaRepository.findByEmail(email.value())
.map(mapper::toDomain);
}
@Override
public boolean existsByEmail(Email email) {
return jpaRepository.existsByEmail(email.value());
}
@Override
public void delete(UserId id) {
jpaRepository.deleteById(id.value());
}
}
```
### Architecture Tests (ArchUnit)
```java
// test/architecture/ArchitectureTest.java
package com.example.myapp.architecture;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;
import static com.tngtech.archunit.library.Architectures.layeredArchitecture;
class ArchitectureTest {
private static final String BASE_PACKAGE = "com.example.myapp";
@Test
void domainShouldNotDependOnOtherLayers() {
var classes = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages(BASE_PACKAGE);
noClasses()
.that().resideInAPackage("..domain..")
.should().dependOnClassesThat()
.resideInAnyPackage("..application..", "..infrastructure..")
.check(classes);
}
@Test
void applicationShouldNotDependOnInfrastructure() {
var classes = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages(BASE_PACKAGE);
noClasses()
.that().resideInAPackage("..application..")
.should().dependOnClassesThat()
.resideInAPackage("..infrastructure..")
.check(classes);
}
@Test
void hexagonalArchitecture() {
var classes = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages(BASE_PACKAGE);
layeredArchitecture()
.consideringAllDependencies()
.layer("Domain").definedBy("..domain..")
.layer("Application").definedBy("..application..")
.layer("Infrastructure").definedBy("..infrastructure..")
.whereLayer("Infrastructure").mayNotBeAccessedByAnyLayer()
.whereLayer("Application").mayOnlyBeAccessedByLayers("Infrastructure")
.whereLayer("Domain").mayOnlyBeAccessedByLayers("Application", "Infrastructure")
.check(classes);
}
}
```
## application.yml Template
```yaml
spring:
application:
name: my-spring-app
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}
datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:myapp}
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
jpa:
hibernate:
ddl-auto: validate
open-in-view: false
properties:
hibernate:
format_sql: true
default_schema: public
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
server:
port: ${SERVER_PORT:8080}
shutdown: graceful
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
endpoint:
health:
show-details: when_authorized
metrics:
tags:
application: ${spring.application.name}
springdoc:
api-docs:
path: /api-docs
swagger-ui:
path: /swagger-ui.html
enabled: true
logging:
level:
root: INFO
com.example.myapp: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
```
## Project Validation Checklist
### Structure
- [ ] Hexagonal architecture with domain/application/infrastructure layers
- [ ] Domain layer has no Spring dependencies
- [ ] Ports defined as interfaces in application layer
- [ ] Adapters implement ports in infrastructure layer
- [ ] Clear separation: input adapters (REST, GraphQL) and output adapters (DB, clients)
### Domain Layer
- [ ] Entities with encapsulated business logic
- [ ] Value objects for domain concepts (Email, UserId, Money)
- [ ] Domain exceptions for business rule violations
- [ ] No framework annotations (no @Entity, @Service)
### Application Layer
- [ ] Use cases as input ports (interfaces)
- [ ] Repository interfaces as output ports
- [ ] DTOs for crossing layer boundaries
- [ ] Transaction management at use case level
### Infrastructure Layer
- [ ] Controllers delegate to use cases only
- [ ] Repository adapters implement port interfaces
- [ ] JPA entities separate from domain entities
- [ ] Mappers for entity conversion
### Testing
- [ ] Unit tests for domain logic
- [ ] Use case tests with mocked ports
- [ ] Integration tests with Testcontainers
- [ ] ArchUnit tests for architecture validation
## Scaffold Commands
```bash
# Create Spring Boot project
curl https://start.spring.io/starter.tgz \
-d type=maven-project \
-d language=java \
-d bootVersion=3.4.1 \
-d baseDir=my-spring-app \
-d groupId=com.example \
-d artifactId=my-spring-app \
-d name=my-spring-app \
-d packageName=com.example.myapp \
-d javaVersion=21 \
-d dependencies=web,data-jpa,postgresql,flyway,validation,security,actuator \
| tar -xzvf -
cd my-spring-app
# Create hexagonal package structure
mkdir -p src/main/java/com/example/myapp/{domain/{model,exception,event,service},application/{port/{in,out},service,dto},infrastructure/{input/{rest/{dto,mapper},messaging},output/{persistence/{entity,repository,mapper},client,messaging},config}}
# Create test structure
mkdir -p src/test/java/com/example/myapp/{domain/model,application/service,infrastructure/{input/rest,output/persistence},integration,architecture}
```
## References
- [Hexagonal Architecture with Spring Boot](https://reflectoring.io/spring-hexagonal/)
- [Baeldung - Hexagonal Architecture, DDD, and Spring](https://www.baeldung.com/hexagonal-architecture-ddd-spring)
- [HappyCoders - Hexagonal Architecture Tutorial](https://www.happycoders.eu/software-craftsmanship/hexagonal-architecture-spring-boot/)
- [Hexagonal Architecture in Spring Boot Microservices](https://dev.to/rock_win_c053fa5fb2399067/hexagonal-architecture-in-spring-boot-microservices-a-complete-guide-with-folder-structure-1jld)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!