CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

apavithraa/springboot-architecture

Skills for creating, testing, reviewing, migrating and guarding Spring Boot 4 projects with Java 25: project scaffolding with architecture patterns, Boot 4 test templates, code review, Boot 3 to 4 migration, and a team harness of build gates and agent hooks.

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

architecture-guide.mdskills/creating-springboot-projects/references/

Spring Boot Architecture Patterns Guide

Table of Contents

  1. Architecture Decision Matrix
  2. Package Structures
  3. Naming Conventions
  4. Anti-Patterns to Avoid
  5. Architecture Upgrade Path
  6. Best Practices

Architecture Decision Matrix

CriteriaLayeredPackage-by-ModuleModulithTomatoDDD+Hex
Team Size1-33-105-155-1510+
LifespanMonths1-2 yrs2-5 yrs3-5 yrs5+ yrs
Type SafetyLowLowLowHighHigh
Learning Curve⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Package Structures

Layered Architecture

com.example.app/
├── controller/
├── service/
├── repository/
└── domain/

When to use: Simple CRUD applications, prototypes, MVPs, single developer projects.

Package-by-Module

com.example.app/
├── products/
│   ├── domain/
│   └── rest/
├── orders/
│   ├── domain/
│   └── rest/
└── shared/

When to use: 3-5 distinct features, medium-sized applications, need feature isolation.

Modular Monolith (Spring Modulith)

com.example.app/
├── products/
│   ├── internal/      ← Package-private implementations
│   ├── ProductsAPI.java  ← Public module API
│   └── domain/
├── orders/
│   ├── internal/
│   ├── OrdersAPI.java
│   └── domain/
└── shared/

When to use: Need enforced module boundaries, want to test module independence, planning potential microservices extraction.

Tomato Architecture

com.example.app/
├── products/
│   ├── domain/
│   │   ├── vo/
│   │   │   ├── ProductSKU.java
│   │   │   ├── Price.java
│   │   │   └── Quantity.java
│   │   ├── ProductEntity.java
│   │   ├── ProductService.java
│   │   └── ProductQueryService.java
│   └── rest/
│       ├── ProductController.java
│       └── converters/
│           └── StringToProductSKUConverter.java
└── shared/
    └── vo/
        ├── Money.java
        └── Email.java

When to use: Rich domain models, need type safety, financial/healthcare domains, experiencing validation bugs from primitive types.

DDD+Hexagonal Architecture

com.example.app/
├── products/
│   ├── application/
│   │   ├── command/
│   │   │   ├── CreateProductHandler.java
│   │   │   └── UpdateProductHandler.java
│   │   └── query/
│   │       └── ProductQueryService.java
│   ├── domain/
│   │   ├── model/
│   │   │   ├── ProductAggregate.java
│   │   │   └── ProductFactory.java
│   │   ├── vo/
│   │   │   ├── ProductSKU.java
│   │   │   └── Price.java
│   │   └── ports/
│   │       └── ProductRepository.java (interface)
│   ├── infra/
│   │   └── persistence/
│   │       └── JpaProductRepository.java (impl)
│   └── interfaces/
│       └── rest/
│           └── ProductController.java
└── shared/
    └── domain/

When to use: Complex business domains, CQRS needed, infrastructure independence required, 10+ team members, 5+ year lifespan.

Naming Conventions

Tomato/DDD Patterns

TypePatternExamplePackage
Entity*EntityProductEntitydomain/model/
Aggregate Root*AggregateOrderAggregatedomain/model/
Value ObjectDomain nameProductSKU, Price, Emaildomain/vo/
Command*CmdCreateProductCmdapplication/command/
Command Handler*HandlerCreateProductHandlerapplication/command/
View Model*VMProductVMdomain/models/ or interfaces/rest/dto/
Write Service*ServiceProductServicedomain/
Read Service*QueryServiceProductQueryServiceapplication/query/
Repository Interface*RepositoryProductRepositorydomain/ports/ (DDD) or domain/
Repository ImplJpa*RepositoryJpaProductRepositoryinfra/persistence/
Module API*APIProductsAPIPackage root
ConverterStringTo*ConverterStringToProductSKUConverterrest/converters/

General Conventions

  • Use plural names for feature/module packages: products/ not product/
  • Value Objects are immutable and use of() factory method
  • Commands are immutable records
  • View Models are immutable records for API responses
  • Aggregates encapsulate business logic and invariants

Anti-Patterns to Avoid

Don'tDoWhy
Jump to implementationAsk assessment questions firstPrevents over-engineering or under-engineering
Use DDD for simple CRUDUse Layered or Package-by-ModuleDDD adds complexity without benefit for simple cases
Use Layered for complex domainUse Tomato or DDD+HexagonalLayered architecture leads to anemic domain models
Skip infrastructureAlways include Flyway, Testcontainers, DockerProduction readiness requires proper infrastructure
Copy templates blindlyRead template comments, adapt to domainTemplates are starting points, not solutions
Primitive obsessionUse Value Objects for domain conceptsType safety prevents bugs, makes intent explicit
Anemic domain modelPut business logic in entities/aggregatesDomain-Driven Design principle
God servicesSplit by command/query responsibilityCQRS improves maintainability
Shared mutable stateUse immutable Value ObjectsThread-safety and predictability

Architecture Upgrade Path

When to Upgrade

FromToTriggerEffort
LayeredPackage-by-Module3+ features, team grows to 3+Low
Package-by-ModuleModular MonolithNeed enforced boundaries, testing independenceMedium
Modular MonolithTomatoType confusion bugs, validation scattered across layersMedium
TomatoDDD+HexagonalNeed infrastructure independence, CQRS, complex business rulesHigh

Migration Strategies

Layered → Package-by-Module:

  1. Create module packages (products/, orders/)
  2. Move related controllers, services, repos into modules
  3. Extract shared utilities to shared/
  4. Refactor cross-module dependencies

Package-by-Module → Modular Monolith:

  1. Add Spring Modulith dependency
  2. Create *API.java interfaces for each module
  3. Make internal classes package-private
  4. Add @ApplicationModuleTest for module verification
  5. Use modularity-test.java template

Modular Monolith → Tomato:

  1. Identify domain concepts currently using primitives
  2. Create Value Objects for each concept (SKU, Price, Email, etc.)
  3. Replace primitives in entities with VOs
  4. Add Spring Converters for @PathVariable binding
  5. Update tests to use VOs

Tomato → DDD+Hexagonal:

  1. Separate commands from queries (CQRS)
  2. Create application layer with handlers
  3. Define domain ports (repository interfaces in domain/)
  4. Move infrastructure implementations to infra/
  5. Extract aggregates from entities
  6. Add ArchUnit tests for hexagonal verification

Signs You've Outgrown Your Architecture

Layered:

  • Features span multiple unrelated domains
  • Service classes exceed 500 lines
  • Difficult to understand feature scope
  • Changes affect multiple unrelated features

Package-by-Module:

  • Modules have circular dependencies
  • Difficult to test modules in isolation
  • Unclear module boundaries
  • Need to extract microservices

Modular Monolith:

  • Type confusion bugs (mixing IDs, codes, values)
  • Validation logic scattered everywhere
  • Primitive parameters causing bugs
  • Financial/healthcare domain (needs type safety)

Tomato:

  • Complex business rules in multiple aggregates
  • Need to swap infrastructure (database, message queue)
  • Multiple teams working on same codebase
  • Planning microservices extraction

Best Practices

Start Simple

  • Begin with Layered or Package-by-Module
  • Don't prematurely optimize for scale
  • Upgrade architecture when complexity demands it
  • Measure complexity by team size, lifespan, and domain complexity

Type Safety

  • Use Value Objects for domain concepts (not just primitives)
  • Leverage Java records for immutability
  • Enable JSpecify null-safety (@NullMarked)
  • Use sealed classes for states/enums when appropriate

Module Boundaries

  • Each module should have clear responsibility
  • Minimize dependencies between modules
  • Use events for cross-module communication
  • Test modules independently

Infrastructure

  • Always use migrations (Flyway/Liquibase)
  • Always use Testcontainers for integration tests
  • Always use Docker Compose for local development
  • Always use ProblemDetail for REST error responses

tile.json