
Clean Architecture
Apply Java 21+ Clean Architecture patterns—records as value objects and sealed domain events—when structuring a maintainable backend with your coding agent.
Overview
Clean Architecture is an agent skill for the Build phase that supplies Java 21+ patterns for value objects as records and domain events as sealed types in layered backends.
Install
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill clean-architectureWhat is this skill?
- Java record patterns for validated value objects (Email, Address) with compact syntax and invariants in compact construc
- Sealed interfaces and records for controlled domain-event hierarchies with aggregate IDs and timestamps
- Aligns with Java 21+ idioms for immutability, equals/hashCode, and type-safe event modeling
- Copy-paste reference implementations agents can adapt into hexagonal or ports-and-adapters layouts
Adoption & trust: 1.2k installs on skills.sh; 271 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your agent keeps producing mutable POJOs and vague event classes that blur domain rules and make Clean Architecture hard to enforce in Java.
Who is it for?
Indie builders or small teams implementing Java 21+ services who want agent-generated domain layers to match Clean Architecture conventions.
Skip if: Greenfield scaffolding-only sessions, non-JVM stacks, or teams that already have a locked corporate architecture template and only need lint rules.
When should I use this skill?
You are structuring Java backend domain code with records, sealed events, or Clean Architecture layers.
What do I get? / Deliverables
Generated Java domain code follows record-based value objects and sealed event models you can wire into use cases and adapters without rework.
- Record-based value object implementations
- Sealed domain-event type definitions
Recommended Skills
Journey fit
Canonical shelf is Build because the skill teaches domain and application-layer structure during active product implementation, not discovery or launch work. Backend subphase fits layered architecture, entities, and persistence boundaries—the core of Clean Architecture in Java services.
How it compares
Reference pattern snippets for domain modeling—not a full project generator or Spring Boot starter.
Common Questions / FAQ
Who is clean-architecture for?
Solo and indie developers building Java 21+ backends with AI coding agents who need consistent value-object and domain-event shapes in a layered architecture.
When should I use clean-architecture?
During Build when modeling entities, value types, and domain events for APIs or monoliths—especially before committing repository and use-case packages.
Is clean-architecture safe to install?
Review the Security Audits panel on this Prism page and the upstream developer-kit repo before trusting agent-generated Java in production.
SKILL.md
READMESKILL.md - Clean Architecture
# Java Clean Architecture Patterns Specific patterns for implementing Clean Architecture in Java 21+ applications. ## Record Types for Value Objects Java records provide immutability and automatic equals/hashCode implementation. ```java // Value object with validation public record Email(String value) { private static final Pattern PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$"); public Email { if (value == null || !PATTERN.matcher(value).matches()) { throw new IllegalArgumentException("Invalid email: " + value); } } public String domain() { return value.substring(value.indexOf('@') + 1); } } // Complex value object with multiple fields public record Address( String street, String city, String postalCode, String country ) { public Address { Objects.requireNonNull(street, "Street is required"); Objects.requireNonNull(city, "City is required"); } public String formatted() { return String.format("%s, %s %s, %s", street, city, postalCode, country); } } ``` ## Sealed Classes for Domain Events Use sealed classes to control event inheritance. ```java public sealed interface DomainEvent { Instant occurredAt(); String aggregateId(); } public record OrderCreatedEvent( OrderId orderId, Money total, Instant occurredAt ) implements DomainEvent { public OrderCreatedEvent { Objects.requireNonNull(orderId); Objects.requireNonNull(total); Objects.requireNonNull(occurredAt); } @Override public String aggregateId() { return orderId.value().toString(); } } public record OrderConfirmedEvent( OrderId orderId, Instant confirmedAt ) implements DomainEvent {} ``` ## Strongly-Typed IDs Prevent ID confusion with type-safe wrappers. ```java public record OrderId(UUID value) { public OrderId { Objects.requireNonNull(value, "OrderId cannot be null"); } public static OrderId generate() { return new OrderId(UUID.randomUUID()); } public static OrderId fromString(String id) { return new OrderId(UUID.fromString(id)); } } public record CustomerId(UUID value) { public CustomerId { Objects.requireNonNull(value); } } ``` ## Factory Methods in Entities Centralize creation logic and enforce invariants. ```java public class Product { private final ProductId id; private String name; private String description; private Money price; private Stock stock; // Private constructor - use factory methods private Product(ProductId id, String name, Money price) { this.id = id; this.name = name; this.price = price; this.stock = Stock.zero(); } public static Product create(String name, Money price) { validateName(name); validatePrice(price); return new Product(ProductId.generate(), name, price); } public static Product reconstitute(ProductId id, String name, Money price, Stock stock) { Product product = new Product(id, name, price); product.stock = stock; return product; } private static void validateName(String name) { if (name == null || name.isBlank() || name.length() > 100) { throw new DomainException("Product name must be 1-100 characters"); } } public void updatePrice(Money newPrice) { if (newPrice.amount().compareTo(BigDecimal.ZERO) <= 0) { throw new DomainException("Price must be positive"); } this.price = newPrice; } } ``` ## Result Type for Operations Explicit handling of success/failure without exceptions. ```java public sealed interface Result<T, E> { record Success<T, E>(T value) implements Result<T, E> {} record Failure<T, E>(E error) implements Result<T, E> {} default boolean isSuccess() { return this instanceof Success; } default Optional<T> getV