diff --git a/jmolecules-architecture-guide/SKILL.md b/jmolecules-architecture-guide/SKILL.md new file mode 100644 index 0000000..f706a46 --- /dev/null +++ b/jmolecules-architecture-guide/SKILL.md @@ -0,0 +1,108 @@ +--- +name: jmolecules-arch-guide +description: jMolecules architecture guidance for AI coding. Provides intelligent support for recognizing, validating, and generating code with jMolecules architectural abstractions. Use this skill when working with DDD, layered, onion, hexagonal, or CQRS architecture styles in Java/Kotlin projects. +--- + +# jMolecules Architecture Guide + +## Overview + +This skill provides AI-powered guidance for working with jMolecules architectural abstractions. It helps developers recognize architecture concepts in code, validate architecture compliance using existing tools, suggest improvements, generate architecture skeletons, and visualize architectural structures. + +## Core Capabilities + +### 1. Architecture Concept Recognition + +Automatically recognize and identify jMolecules architectural abstractions in code: +- **DDD Building Blocks**: `@Entity`, `@AggregateRoot`, `@ValueObject`, `@Repository`, `@Service`, `@Factory` +- **Architecture Layers**: `@DomainLayer`, `@ApplicationLayer`, `@InfrastructureLayer`, `@InterfaceLayer` +- **Event-Driven Components**: `@DomainEvent`, `@DomainEventHandler`, `@DomainEventPublisher` +- **Architecture Styles**: Layered, onion, hexagonal, CQRS architecture annotations + +### 2. Architecture Validation Guidance + +Guide developers on using existing tools for architecture validation: + +#### jQAssistant +- Generate Cypher queries to find architecture patterns +- Interpret jQAssistant analysis results +- Help configure jQAssistant rules for jMolecules + +#### ArchUnit +- Generate ArchUnit rules for architecture violation detection +- Provide examples of common ArchUnit checks +- Explain how to integrate ArchUnit into build process + +#### Spring Modulith +- Guide on using Spring Modulith for module decomposition +- Explain how to create module documentation +- Show how to integrate with jMolecules + +### 3. Improvement Suggestions + +Provide intelligent suggestions for improving architecture: +- Recommend where to add jMolecules annotations +- Suggest refactorings to convert primitive fields to Value Objects +- Propose architecture style changes (e.g., from layered to hexagonal) +- Identify opportunities to better separate concerns + +### 4. Architecture Skeleton Generation + +Generate complete architecture skeletons from high-level descriptions: +- Create DDD aggregates with entities, value objects, and repositories +- Generate layered architecture structures (domain, application, infrastructure) +- Create event-driven components (events, handlers, publishers) +- Support multiple architecture styles (layered, onion, hexagonal, CQRS) + +### 5. Visualization and Documentation + +Visualize and document architecture: +- Generate Mermaid diagrams of architecture structures +- Create jMolecules configuration files (jmolecules-stereotypes.json) +- Produce markdown documentation from code annotations +- Export architecture information for reporting + +## Usage Scenarios + +### Scenario 1: Analyze Existing Code + +When analyzing existing code: +1. Read and analyze the codebase +2. Identify existing architecture patterns and annotations +3. Suggest appropriate jQAssistant, ArchUnit, or Spring Modulith checks +4. Interpret and explain tool output +5. Provide improvement suggestions + +### Scenario 2: Create New Architecture + +When creating a new architecture: +1. Get high-level requirements from user +2. Choose appropriate architecture style +3. Generate complete architecture skeleton +4. Add documentation and visualization +5. Configure architecture validation tools + +### Scenario 3: Refactor Architecture + +When refactoring existing architecture: +1. Analyze current architecture +2. Identify refactoring opportunities +3. Apply suggested refactorings +4. Validate the new architecture using tools +5. Update documentation and configuration + +## Resources + +This skill includes: + +### references/ +- [jmolecules-stereotypes.json](./references/jmolecules-stereotypes.json) - jMolecules stereotypes configuration +- [ddd-best-practices.md](./references/ddd-best-practices.md) - DDD implementation guidelines +- [architecture-styles.md](./references/architecture-styles.md) - Architecture style comparisons +- [jqassistant-examples.md](./references/jqassistant-examples.md) - jQAssistant query examples +- [archunit-examples.md](./references/archunit-examples.md) - ArchUnit rule examples +- [spring-modulith.md](./references/spring-modulith.md) - Spring Modulith integration guide + +### assets/ +- [templates/](./assets/templates/) - Architecture skeleton templates +- [mermaid/](./assets/mermaid/) - Mermaid diagram templates diff --git a/jmolecules-architecture-guide/assets/mermaid/architecture-layered.mmd b/jmolecules-architecture-guide/assets/mermaid/architecture-layered.mmd new file mode 100644 index 0000000..30281d4 --- /dev/null +++ b/jmolecules-architecture-guide/assets/mermaid/architecture-layered.mmd @@ -0,0 +1,32 @@ +graph TB + subgraph "Interface Layer" + Controller[Controller\n@InterfaceLayer] + end + + subgraph "Application Layer" + UseCase[Use Case\n@ApplicationLayer] + end + + subgraph "Domain Layer" + Aggregate[Aggregate Root\n@AggregateRoot] + Entity[Entity\n@Entity] + VO[Value Object\n@ValueObject] + Service[Domain Service\n@Service] + Repository[Repository\n@Repository] + end + + subgraph "Infrastructure Layer" + RepositoryImpl[Repository Implementation\n@InfrastructureLayer] + Database[(Database)] + ExternalAPI[External API\n@InfrastructureLayer] + end + + Controller --> UseCase + UseCase --> Aggregate + UseCase --> Repository + Aggregate --> Entity + Aggregate --> VO + Entity --> VO + Repository --> RepositoryImpl + RepositoryImpl --> Database + RepositoryImpl --> ExternalAPI \ No newline at end of file diff --git a/jmolecules-architecture-guide/assets/templates/aggregate-root.template b/jmolecules-architecture-guide/assets/templates/aggregate-root.template new file mode 100644 index 0000000..9035bcb --- /dev/null +++ b/jmolecules-architecture-guide/assets/templates/aggregate-root.template @@ -0,0 +1,43 @@ +// Aggregate Root template +import org.jmolecules.ddd.annotation.AggregateRoot; +import org.jmolecules.ddd.annotation.Entity; + +@AggregateRoot +@Entity +public class ${AggregateRootName} { + + private ${AggregateRootName}Id id; + + private ${AggregateRootName}Number number; + + private ${AggregateRootName}Total total; + + private List<${AggregateRootName}LineItem> lineItems = new ArrayList<>(); + + protected ${AggregateRootName}() { + // Default constructor for instantiation + } + + public static ${AggregateRootName} create() { + ${AggregateRootName} aggregate = new ${AggregateRootName}(); + aggregate.id = ${AggregateRootName}Id.generate(); + aggregate.number = ${AggregateRootName}Number.generate(); + aggregate.total = ${AggregateRootName}Total.ZERO; + return aggregate; + } + + public void addLineItem(Product product, int quantity) { + // Business logic here + ${AggregateRootName}LineItem item = new ${AggregateRootName}LineItem(this, product, quantity); + lineItems.add(item); + total = calculateTotal(); + } + + private ${AggregateRootName}Total calculateTotal() { + // Calculation logic + BigDecimal sum = lineItems.stream() + .map(item -> item.getTotal().getAmount()) + .reduce(BigDecimal.ZERO, BigDecimal::add); + return ${AggregateRootName}Total.of(sum); + } +} \ No newline at end of file diff --git a/jmolecules-architecture-guide/references/architecture-styles.md b/jmolecules-architecture-guide/references/architecture-styles.md new file mode 100644 index 0000000..eebb269 --- /dev/null +++ b/jmolecules-architecture-guide/references/architecture-styles.md @@ -0,0 +1,219 @@ +# Architecture Styles + +This document provides information about the different architecture styles supported by jMolecules. + +## Layered Architecture + +Layered architecture follows strict unidirectional dependency rules where the domain layer is the most independent: + +``` +┌─────────────────────┐ +│ Interface Layer │ +│ (Controllers, APIs) │ +│ DEPENDS ON │ +└─────────────────────┘ + ↓ +┌─────────────────────┐ +│ Application Layer │ +│ (Use Cases) │ +│ DEPENDS ON │ +└─────────────────────┘ + ↓ +┌─────────────────────┐ +│ Domain Layer │ +│ (Business Logic) │ +│ MOST INDEPENDENT │ +│ NO DEPENDENCIES ON OTHER LAYERS │ +└─────────────────────┘ + ↑ +┌─────────────────────┐ +│ Infrastructure Layer│ +│ (Data Access, APIs) │ +│ DEPENDS ON │ +└─────────────────────┘ +``` + +### Key Principles (Modern Interpretation) +- **Domain layer is the core and most independent** - Contains pure business logic +- **Strict dependency inversion** - Lower layers (infrastructure) depend on higher layers (domain) +- **Application layer mediates between interface and domain** - Coordinates business logic +- **Infrastructure layer supports the domain** - Provides implementations of interfaces defined in higher layers + +### Dependency Flow (Correct Modern Approach) +``` +Interface Layer → Application Layer → Domain Layer ← Infrastructure Layer +``` + +This approach aligns with modern architecture principles like Dependency Inversion and Clean Architecture. + +### jMolecules Annotations +- `@DomainLayer` - For business logic (**most independent**) +- `@ApplicationLayer` - For use cases (depends on domain layer) +- `@InfrastructureLayer` - For data access and external API (**depends on domain layer**) +- `@InterfaceLayer` - For controllers and API endpoints (depends on application layer) + +### Common Violations to Avoid +1. Domain layer should **never** depend on infrastructure layer +2. Application layer should **never** depend directly on infrastructure layer +3. Infrastructure layer should **only** depend on interfaces defined in higher layers + +### Traditional vs Modern Interpretation + +**Traditional (Incorrect) Approach:** +``` +Interface → Application → Domain → Infrastructure (Domain depends on Infrastructure) +``` + +**Modern (Correct) Approach:** +``` +Interface → Application → Domain ← Infrastructure (Infrastructure depends on Domain) +``` + +The modern approach is recommended by jMolecules and aligns with DDD and Clean Architecture principles. + +## Onion Architecture + +Onion architecture follows strict inward dependency rules: + +``` +┌─────────────────────────────────┐ +│ Infrastructure (Adapters) │ +│ DEPENDS ON │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Application Services │ +│ DEPENDS ON │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Domain Services │ +│ DEPENDS ON │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Domain Model (Entities, VO) │ +│ MOST INDEPENDENT │ +└─────────────────────────────────┘ +``` + +### Key Principles +- **Dependencies point inward** - All outer layers depend on inner layers +- **Domain model is at the center and most independent** - Contains pure business logic +- **Inversion of Control (IoC)** - External dependencies are inverted +- **Clear separation between core and infrastructure** - Inner layers define interfaces, outer layers implement them + +### Dependency Flow +``` +Infrastructure → Application Services → Domain Services → Domain Model +``` + +All dependencies go from outer layers to inner layers. The domain model is completely independent. + +### jMolecules Annotations +- `@DomainModelRing` - For the core domain model (**most independent**) +- `@DomainServiceRing` - For domain services (depends on domain model) +- `@ApplicationServiceRing` - For application services (depends on domain services) +- `@InfrastructureRing` - For infrastructure and adapters (**depends on all inner layers**) + +## Hexagonal Architecture + +Hexagonal architecture (Ports and Adapters) focuses on the application core with strict dependency inversion: + +``` +┌─────────────────────────────────┐ +│ Primary Adapters (Driving) │ +│ (UI, API, CLI) │ +│ DEPENDS ON │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Primary Ports │ +│ (Application Interfaces) │ +│ DEPENDS ON │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Application Core │ +│ (Business Logic) │ +│ DEPENDED ON BY │ +└─────────────────────────────────┘ + ↑ +┌─────────────────────────────────┐ +│ Secondary Ports │ +│ (Database, External APIs) │ +│ DEPENDED ON BY │ +└─────────────────────────────────┘ + ↑ +┌─────────────────────────────────┐ +│ Secondary Adapters (Driven) │ +│ (Implementations) │ +└─────────────────────────────────┘ +``` + +### Key Principles +- **Application core is at the center and independent of external systems** +- **Ports define interfaces that the core needs** +- **Adapters implement these interfaces and adapt to external systems** +- **Dependencies point inward towards the application core** +- **Inversion of Control (IoC) principle strictly followed** + +### Dependency Flow + +``` +Primary Adapters (UI) → Primary Ports → Application Core ← Secondary Ports ← Secondary Adapters (Database) +``` + +- **Primary side (driving)**:UI → API → Application(normal flow) +- **Secondary side (driven)**:Application → Port → Adapter(inverted flow) + +### jMolecules Annotations +- `@Application` - For the application core +- `@PrimaryPort` - For primary (driving) ports +- `@SecondaryPort` - For secondary (driven) ports +- `@PrimaryAdapter` - For primary adapter implementations +- `@SecondaryAdapter` - For secondary adapter implementations + +## CQRS Architecture + +CQRS (Command Query Responsibility Segregation) separates command and query operations: + +``` +┌─────────────────────────────────┐ +│ Command Side │ +│ (Write Operations) │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Event Store │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Query Side │ +│ (Read Operations) │ +└─────────────────────────────────┘ +``` + +### Key Principles +- Separate models for reads and writes +- Events propagate state changes +- Optimized data stores for each side +- High scalability and performance + +### jMolecules Annotations +- `@Command` - For command messages +- `@CommandDispatcher` - For command dispatching +- `@CommandHandler` - For command handling +- `@QueryModel` - For query models + +## Choosing an Architecture Style + +Consider these factors: + +1. **Project Size**: Layered for small, Hexagonal for large +2. **Complexity**: Onion or Hexagonal for complex business logic +3. **Team Expertise**: Choose styles your team is comfortable with +4. **Requirements**: CQRS for high-read or high-write systems +5. **Time Constraints**: Layered is quickest to implement + +jMolecules allows you to start with one style and evolve as needed. \ No newline at end of file diff --git a/jmolecules-architecture-guide/references/archunit-examples.md b/jmolecules-architecture-guide/references/archunit-examples.md new file mode 100644 index 0000000..1418515 --- /dev/null +++ b/jmolecules-architecture-guide/references/archunit-examples.md @@ -0,0 +1,173 @@ +# ArchUnit Examples + +This document contains example ArchUnit rules for validating jMolecules architecture. + +## Getting Started + +First, add the required dependencies: + +```xml + + + com.tngtech.archunit + archunit-junit5 + 1.2.1 + test + + +``` + +## Basic Layer Dependency Rules + +```java +@AnalyzeClasses(packages = "com.example") +class ArchitectureTests { + + // Domain layer should not depend on any other layer + @ArchTest + static final ArchRule domain_layer_should_not_depend_on_other_layers = noClasses() + .that().areAnnotatedWith(DomainLayer.class) + .should().dependOnClassesThat().areAnnotatedWith(ApplicationLayer.class) + .orShould().dependOnClassesThat().areAnnotatedWith(InfrastructureLayer.class) + .orShould().dependOnClassesThat().areAnnotatedWith(InterfaceLayer.class); + + // Application layer should not depend on infrastructure layer + @ArchTest + static final ArchRule application_layer_should_not_depend_on_infrastructure = noClasses() + .that().areAnnotatedWith(ApplicationLayer.class) + .should().dependOnClassesThat().areAnnotatedWith(InfrastructureLayer.class); + + // Infrastructure layer should not depend on interface layer + @ArchTest + static final ArchRule infrastructure_layer_should_not_depend_on_interface = noClasses() + .that().areAnnotatedWith(InfrastructureLayer.class) + .should().dependOnClassesThat().areAnnotatedWith(InterfaceLayer.class); +} +``` + +## Value Object Rules + +```java +@AnalyzeClasses(packages = "com.example") +class ValueObjectTests { + + // Classes annotated with @ValueObject should not have setters + @ArchTest + static final ArchRule value_objects_should_be_immutable = noClasses() + .that().areAnnotatedWith(ValueObject.class) + .should().haveMethodsThat().arePublic() + .and().haveNameMatching("set.*") + .because("Value objects should be immutable"); + + // Classes annotated with @ValueObject should implement equals() and hashCode() + @ArchTest + static final ArchRule value_objects_should_implement_equals_and_hashCode = allClasses() + .that().areAnnotatedWith(ValueObject.class) + .should().implementEqualsAndHashCode() + .because("Value objects need to be compared by value"); +} +``` + +## Aggregate Root Rules + +```java +@AnalyzeClasses(packages = "com.example") +class AggregateRootTests { + + // Aggregate roots should be the only classes with @Entity annotation + @ArchTest + static final ArchRule only_aggregate_roots_should_be_entities = allClasses() + .that().areAnnotatedWith(Entity.class) + .should().beAnnotatedWith(AggregateRoot.class) + .because("Only aggregate roots should be directly persistent"); + + // Repositories should only work with aggregate roots + @ArchTest + static final ArchRule repositories_should_work_with_aggregate_roots = allClasses() + .that().areAnnotatedWith(Repository.class) + .should().onlyDependOnClassesThat().areAnnotatedWith(AggregateRoot.class) + .orShould().onlyDependOnClassesThat().areAssignableTo(Collection.class) + .orShould().onlyDependOnClassesThat().arePrimitiveTypes() + .because("Repositories should only manage aggregate roots"); +} +``` + +## Event-Driven Architecture Rules + +```java +@AnalyzeClasses(packages = "com.example") +class EventDrivenTests { + + // Event handlers should only depend on domain events + @ArchTest + static final ArchRule event_handlers_should_depend_on_events = allClasses() + .that().areAnnotatedWith(DomainEventHandler.class) + .should().onlyDependOnClassesThat().areAnnotatedWith(DomainEvent.class) + .orShould().onlyDependOnClassesThat().areAnnotatedWith(Repository.class) + .because("Event handlers should only process events and access data"); + + // Events should be immutable + @ArchTest + static final ArchRule events_should_be_immutable = noClasses() + .that().areAnnotatedWith(DomainEvent.class) + .should().haveMethodsThat().arePublic() + .and().haveNameMatching("set.*") + .because("Events should be immutable once published"); +} +``` + +## Package Structure Rules + +```java +@AnalyzeClasses(packages = "com.example") +class PackageStructureTests { + + // Classes in domain package should be in domain layer + @ArchTest + static final ArchRule domain_package_classes_should_be_domain_layer = allClasses() + .that().resideInAPackage("..domain..") + .should().beAnnotatedWith(DomainLayer.class) + .because("Classes in domain package should belong to domain layer"); + + // Classes in infrastructure package should be in infrastructure layer + @ArchTest + static final ArchRule infrastructure_package_classes_should_be_infrastructure = allClasses() + .that().resideInAPackage("..infrastructure..") + .should().beAnnotatedWith(InfrastructureLayer.class) + .because("Classes in infrastructure package should belong to infrastructure layer"); +} +``` + +## Custom Rule Example + +```java +@AnalyzeClasses(packages = "com.example") +class CustomRules { + + // Classes should be in the correct package based on annotation + @ArchTest + static final ArchRule classes_should_be_in_correct_package = allClasses() + .that().areAnnotatedWith(ValueObject.class) + .should().resideInAPackage("..valueobject..") + .orShould().resideInAPackage("..vo..") + .because("Value objects should be in dedicated packages"); + + // Services should be interfaces or abstract classes + @ArchTest + static final ArchRule services_should_be_interfaces = allClasses() + .that().areAnnotatedWith(Service.class) + .should().beInterfaces() + .orShould().beAbstractClasses() + .because("Services should be abstractions"); +} +``` + +## Running the Tests + +Run the tests as regular JUnit tests: + +```bash +mvn test +``` + +or in your IDE. \ No newline at end of file diff --git a/jmolecules-architecture-guide/references/ddd-best-practices.md b/jmolecules-architecture-guide/references/ddd-best-practices.md new file mode 100644 index 0000000..60c667a --- /dev/null +++ b/jmolecules-architecture-guide/references/ddd-best-practices.md @@ -0,0 +1,220 @@ +# DDD Best Practices + +This document provides guidelines and best practices for implementing Domain-Driven Design using jMolecules. + +## Core Principles + +1. **Ubiquitous Language** - Use business terminology consistently throughout the codebase +2. **Bounded Contexts** - Separate the system into bounded contexts with explicit boundaries +3. **Value Objects** - Prefer value objects over primitive types for domain concepts +4. **Aggregate Roots** - Use aggregates with strict consistency boundaries +5. **Repositories** - Encapsulate data access behind repository interfaces +6. **Domain Services** - Keep business logic in domain services or entities +7. **Domain Events** - Use events to communicate between bounded contexts + +## Implementation Guidelines + +### 1. Value Objects + +```java +// Good - Value Object +@ValueObject +public class EmailAddress { + + private final String value; + + private EmailAddress(String value) { + this.value = value; + } + + public static EmailAddress of(String value) { + validateEmail(value); + return new EmailAddress(value); + } + + public String getValue() { + return value; + } +} + +// Bad - Using primitive type +private String email; +``` + +### 2. Aggregate Roots + +```java +import org.jmolecules.ddd.annotation.AggregateRoot; +import org.jmolecules.ddd.annotation.Entity; +import org.jmolecules.ddd.annotation.ValueObject; + +@AggregateRoot +@Entity +public class Order { + + private OrderId id; + + private OrderNumber orderNumber; + + private OrderTotal total; + + private List lineItems = new ArrayList<>(); + + // Aggregate root should manage all state changes + public void addLineItem(Product product, int quantity) { + // Business logic here + OrderLineItem item = new OrderLineItem(this, product, quantity); + lineItems.add(item); + total = calculateTotal(); + } +} +``` + +### 3. Repositories + +```java +@Repository +public interface OrderRepository extends CrudRepository { + + List findByCustomerId(CustomerId customerId); + + List findOrdersWithMinimumTotal(BigDecimal minimumAmount); +} +``` + +### 4. Domain Events + +```java +@DomainEvent +public class OrderPlaced { + + private final OrderId orderId; + private final LocalDateTime placedAt; + private final CustomerId customerId; + private final OrderTotal total; + + public OrderPlaced(OrderId orderId, LocalDateTime placedAt, CustomerId customerId, OrderTotal total) { + this.orderId = orderId; + this.placedAt = placedAt; + this.customerId = customerId; + this.total = total; + } + + // Getters +} +``` + +### 5. Architecture Layers + +```java +import org.jmolecules.ddd.annotation.ApplicationLayer; +import org.jmolecules.ddd.annotation.DomainLayer; +import org.jmolecules.ddd.annotation.Service; + +@ApplicationLayer +@Service +public class OrderApplicationService { + + private final OrderRepository orderRepository; + private final OrderDomainService orderDomainService; + private final CustomerRepository customerRepository; + private final ProductDomainService productDomainService; + private final PaymentGateway paymentGateway; + + public OrderApplicationService(OrderRepository orderRepository, OrderDomainService orderDomainService, + CustomerRepository customerRepository, ProductService productDomainService, + PaymentGateway paymentGateway) { + this.orderRepository = orderRepository; + this.orderDomainService = orderDomainService; + this.customerRepository = customerRepository; + this.productDomainService = productDomainService; + this.paymentGateway = paymentGateway; + } + + @Transactional + public Order placeOrder(CustomerId customerId, List items) { + // Application orchestration logic here + + // 1. Validate customer exists + Customer customer = customerRepository.findById(customerId) + .orElseThrow(() -> new CustomerNotFoundException(customerId)); + + // 2. Check customer can place orders + if (!customer.isActive()) { + throw new CustomerNotActiveException(customerId); + } + + // 3. Validate products and quantities are available (orchestration layer) + List availableProducts = new ArrayList<>(); + for (OrderLineItemDTO itemDTO : items) { + Product product = productDomainService.getProduct(itemDTO.getProductId()); + if (product.getStockLevel() < itemDTO.getQuantity()) { + throw new ProductOutOfStockException(itemDTO.getProductId(), itemDTO.getQuantity()); + } + availableProducts.add(product); + } + + // 4. Create order using domain service + Order order = orderDomainService.createOrder(customerId, items); + + // 5. Save order + orderRepository.save(order); + + // 6. Reduce product quantities (orchestration layer) + for (int i = 0; i < items.size(); i++) { + OrderLineItemDTO itemDTO = items.get(i); + productDomainService.reduceStock(itemDTO.getProductId(), itemDTO.getQuantity()); + } + + // 7. Start payment process (asynchronous) + paymentGateway.initiatePayment(order.getId(), order.getTotal()); + + // 8. Publish order placed event + DomainEventPublisher.publish(new OrderPlaced(order.getId(), LocalDateTime.now(), customerId, order.getTotal())); + + return order; + } +} + +@DomainLayer +@Service +public class OrderDomainService { + + public Order createOrder(CustomerId customerId, List items) { + // Domain logic here + Order order = new Order(customerId); + + for (OrderLineItemDTO itemDTO : items) { + // In real implementation, we would use ProductReference or similar + OrderLineItem item = new OrderLineItem(order, itemDTO.getProductId(), itemDTO.getQuantity()); + order.addLineItem(item); + } + + return order; + } +} + +@DomainLayer +@Service +public class ProductDomainService { + + private final ProductRepository productRepository; + + public ProductService(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + public Product getProduct(ProductId productId) { + return productRepository.findById(productId) + .orElseThrow(() -> new ProductNotFoundException(productId)); + } + + public void reduceStock(ProductId productId, int quantity) { + Product product = productRepository.findById(productId) + .orElseThrow(() -> new ProductNotFoundException(productId)); + + product.reduceStock(quantity); + productRepository.save(product); + } +} +``` \ No newline at end of file diff --git a/jmolecules-architecture-guide/references/jmolecules-stereotypes.json b/jmolecules-architecture-guide/references/jmolecules-stereotypes.json new file mode 100644 index 0000000..6744518 --- /dev/null +++ b/jmolecules-architecture-guide/references/jmolecules-stereotypes.json @@ -0,0 +1,199 @@ +# jMolecules Stereotypes Configuration + +This file contains information about jMolecules stereotypes and their properties, including both annotational and type-driven approaches. + +## Annotational vs Type-Driven Approaches + +jMolecules provides two complementary ways to define architectural concepts: + +### 1. Annotational Approach (Annotations) +Useful for existing projects, minimal changes required. + +**Example:** +```java +import org.jmolecules.ddd.annotation.AggregateRoot; +import org.jmolecules.ddd.annotation.Entity; +import org.jmolecules.ddd.annotation.ValueObject; + +@AggregateRoot +@Entity +public class Order { + + private OrderId id; + + @ValueObject + public static class OrderId { + private String value; + + private OrderId(String value) { + this.value = value; + } + + public static OrderId of(String value) { + return new OrderId(value); + } + } +} +``` + +### 2. Type-Driven Approach (Interfaces) +Provides stronger type safety and compile-time checks. + +**Example:** +```java +import org.jmolecules.ddd.types.AggregateRoot; +import org.jmolecules.ddd.types.Identifier; + +public class Order implements AggregateRoot { + + private OrderId id; + + public class OrderId implements Identifier { + private String value; + + private OrderId(String value) { + this.value = value; + } + + public static OrderId of(String value) { + return new OrderId(value); + } + } +} +``` + +## Stereotype Groups + +### Domain-Driven Design (DDD) +```json +{ + "groups": [ + { + "name": "ddd", + "displayName": "DDD Building Blocks", + "description": "Domain-Driven Design building blocks", + "properties": { + "layer": null, + "scope": null + } + } + ] +} +``` + +## jMolecules Annotations + +### Core DDD Annotations + +#### Entities and Aggregates +- `@Entity` - Represents a domain entity +- `@AggregateRoot` - Represents an aggregate root entity +- `@ValueObject` - Represents a value object +- `@Service` - Represents a domain or application service +- `@Repository` - Represents a repository for data access +- `@Factory` - Represents a factory for creating complex objects + +#### Context and Modules +- `@BoundedContext` - Represents a bounded context +- `@Module` - Represents a module within a bounded context + +### Architecture Layer Annotations + +#### Layered Architecture +- `@DomainLayer` - Represents the domain layer +- `@ApplicationLayer` - Represents the application layer +- `@InfrastructureLayer` - Represents the infrastructure layer +- `@InterfaceLayer` - Represents the interface layer + +#### Onion Architecture +- `@DomainModelRing` - For the core domain model +- `@DomainServiceRing` - For domain services +- `@ApplicationServiceRing` - For application services +- `@InfrastructureRing` - For infrastructure and adapters + +#### Hexagonal Architecture +- `@Application` - For the application core +- `@PrimaryPort` - For primary (driving) ports +- `@SecondaryPort` - For secondary (driven) ports +- `@PrimaryAdapter` - For primary adapter implementations +- `@SecondaryAdapter` - For secondary adapter implementations + +#### CQRS Architecture +- `@Command` - Represents a command message +- `@CommandDispatcher` - Represents a command dispatcher +- `@CommandHandler` - Represents a command handler +- `@QueryModel` - Represents a query model + +### Event-Driven Architecture + +#### Events and Handlers +- `@DomainEvent` - Represents a domain event +- `@DomainEventHandler` - Represents a domain event handler +- `@DomainEventPublisher` - Represents a domain event publisher +- `@Externalized` - Indicates an externalized event + +## Type-Driven Interfaces + +### Core DDD Interfaces + +#### Identifiers and Entities +- `Identifier` - Base interface for all identifier types +- `Identifiable, ID extends Identifier>` - Base interface for identifiable objects +- `Entity, ID extends Identifier>` - Base interface for entities that belong to an aggregate + +#### Aggregates and Roots +- `AggregateRoot, ID extends Identifier>` - Base interface for aggregate roots +- `Association, ID extends Identifier>` - Represents an association to another aggregate + +#### Services and Repositories +- `Service` - Base interface for services +- `Repository, ID extends Identifier>` - Base interface for repositories + +### Architecture Interfaces + +#### Layers +- `DomainLayer` - Marker interface for domain layer components +- `ApplicationLayer` - Marker interface for application layer components +- `InfrastructureLayer` - Marker interface for infrastructure layer components +- `InterfaceLayer` - Marker interface for interface layer components + +#### Event-Driven +- `DomainEvent` - Marker interface for domain events +- `DomainEventHandler` - Marker interface for domain event handlers +- `DomainEventPublisher` - Marker interface for domain event publishers + +## Choosing an Approach + +### When to Use Annotational Approach +- Existing codebase +- Minimal changes required +- Compatibility with legacy systems +- Better integration with tools that read annotations + +### When to Use Type-Driven Approach +- New projects +- Strong type safety requirements +- Compile-time validation +- Better tool support for static analysis +- Clearer intent from the type system + +### Combining Approaches +You can mix both approaches: +```java +import org.jmolecules.ddd.annotation.AggregateRoot; +import org.jmolecules.ddd.types.AggregateRoot; + +@AggregateRoot +public class Order implements AggregateRoot { + // Implementation +} +``` + +## Best Practices + +1. **Consistency within a bounded context** - Choose one approach and stick with it +2. **Communicate the choice** - Document which approach your team is using +3. **Use type-driven for new code** - Provides stronger guarantees +4. **Gradually migrate existing code** - Refactor to type-driven approach + +Both approaches are supported by jMolecules tools and integrations. \ No newline at end of file diff --git a/jmolecules-architecture-guide/references/jqassistant-examples.md b/jmolecules-architecture-guide/references/jqassistant-examples.md new file mode 100644 index 0000000..71cc3e8 --- /dev/null +++ b/jmolecules-architecture-guide/references/jqassistant-examples.md @@ -0,0 +1,220 @@ +# jQAssistant Examples + +This document contains example Cypher queries for querying jMolecules architecture with jQAssistant. + +## Getting Started + +jQAssistant uses Neo4j for storing code structure information. Install jQAssistant and run: + +```bash +mvn jqassistant:scan +mvn jqassistant:analyze +``` + +## Basic Queries + +### Find all classes with jMolecules annotations + +```cypher +MATCH (c:Class) +WHERE exists(c.`jmolecules:annotations`) +RETURN c.fqn AS className, c.`jmolecules:annotations` AS jmoleculesAnnotations +ORDER BY c.fqn +``` + +### Find all DDD building blocks + +```cypher +MATCH (c:Class) +WHERE + exists(c.`jmolecules:annotation:Entity`) OR + exists(c.`jmolecules:annotation:AggregateRoot`) OR + exists(c.`jmolecules:annotation:ValueObject`) OR + exists(c.`jmolecules:annotation:Repository`) OR + exists(c.`jmolecules:annotation:Service`) OR + exists(c.`jmolecules:annotation:Factory`) +RETURN + c.fqn AS className, + CASE + WHEN exists(c.`jmolecules:annotation:Entity`) THEN 'Entity' + WHEN exists(c.`jmolecules:annotation:AggregateRoot`) THEN 'AggregateRoot' + WHEN exists(c.`jmolecules:annotation:ValueObject`) THEN 'ValueObject' + WHEN exists(c.`jmolecules:annotation:Repository`) THEN 'Repository' + WHEN exists(c.`jmolecules:annotation:Service`) THEN 'Service' + WHEN exists(c.`jmolecules:annotation:Factory`) THEN 'Factory' + END AS type +ORDER BY type, className +``` + +## Layer Architecture Queries + +### Find layer dependencies + +```cypher +MATCH (s:Class)-[:DEPENDS_ON]->(t:Class) +WHERE + exists(s.`jmolecules:annotation:DomainLayer`) AND + exists(t.`jmolecules:annotation:ApplicationLayer`) +RETURN + s.fqn AS source, + t.fqn AS target, + 'Domain → Application' AS dependencyType +``` + +### Find layer violations + +```cypher +MATCH (s:Class)-[:DEPENDS_ON]->(t:Class) +WHERE + exists(s.`jmolecules:annotation:DomainLayer`) AND + exists(t.`jmolecules:annotation:InfrastructureLayer`) +RETURN + s.fqn AS source, + t.fqn AS target +``` + +## Value Object Queries + +### Find potential value objects + +```cypher +MATCH (c:Class)-[:DECLARES]->(f:Field) +WHERE + NOT exists(c.`jmolecules:annotation:ValueObject`) AND + f.type IN ['String', 'int', 'long', 'double', 'float', 'boolean', 'LocalDate', 'LocalDateTime'] AND + NOT f.name IN ['id', 'name', 'description'] +RETURN + c.fqn AS className, + f.name AS fieldName, + f.type AS fieldType +ORDER BY className +``` + +## Aggregate Root Queries + +### Find aggregates with their entities + +```cypher +MATCH (ar:Class)-[:DECLARES]->(f:Field) +WHERE + exists(ar.`jmolecules:annotation:AggregateRoot`) AND + exists(f.`jmolecules:annotation:Entity`) +RETURN + ar.fqn AS aggregateRoot, + collect(f.name) AS entities +``` + +## Repository Queries + +### Find all repositories + +```cypher +MATCH (r:Class) +WHERE exists(r.`jmolecules:annotation:Repository`) +RETURN r.fqn AS repositoryName +ORDER BY r.fqn +``` + +### Find repositories with dependencies + +```cypher +MATCH (r:Class)-[:DEPENDS_ON]->(d:Class) +WHERE exists(r.`jmolecules:annotation:Repository`) +RETURN r.fqn AS repository, d.fqn AS dependency +ORDER BY repository +``` + +## Event-Driven Architecture Queries + +### Find all domain events + +```cypher +MATCH (e:Class) +WHERE exists(e.`jmolecules:annotation:DomainEvent`) +RETURN e.fqn AS eventName +ORDER BY e.fqn +``` + +### Find event handlers and their events + +```cypher +MATCH (h:Class)-[:DEPENDS_ON]->(e:Class) +WHERE + exists(h.`jmolecules:annotation:DomainEventHandler`) AND + exists(e.`jmolecules:annotation:DomainEvent`) +RETURN + h.fqn AS handler, + e.fqn AS event +``` + +## Architecture Analysis Queries + +### Check layer structure compliance + +```cypher +MATCH (c:Class) +WHERE + exists(c.`jmolecules:annotation:DomainLayer`) AND + (exists(c.`jmolecules:annotation:ApplicationLayer`) OR exists(c.`jmolecules:annotation:InfrastructureLayer`)) +RETURN c.fqn AS invalidClass +``` + +### Find classes with multiple layer annotations + +```cypher +MATCH (c:Class) +WHERE + exists(c.`jmolecules:annotation:DomainLayer`) AND + exists(c.`jmolecules:annotation:ApplicationLayer`) +RETURN c.fqn AS classWithMultipleLayerAnnotations +``` + +## Custom Queries + +### Create your own rules file + +Create a `jqassistant/rules` directory with a `.cypher` file: + +```cypher +# my-architecture.rules.cypher +@id: "my-architecture:layer-dependency-violation" +@severity: "MAJOR" +@message: "Domain layer should not depend on infrastructure layer" +MATCH (s:Class)-[:DEPENDS_ON]->(t:Class) +WHERE + exists(s.`jmolecules:annotation:DomainLayer`) AND + exists(t.`jmolecules:annotation:InfrastructureLayer`) +RETURN + s.fqn AS Source, + t.fqn AS Target +``` + +## Visualization Queries + +### Generate architecture diagram + +```cypher +MATCH (c:Class)-[d:DEPENDS_ON]->(e:Class) +WHERE + exists(c.`jmolecules:annotations`) OR + exists(e.`jmolecules:annotations`) +RETURN * +``` + +Use the jQAssistant web interface to visualize this. + +## Running Queries + +You can run queries from the command line: + +```bash +mvn jqassistant:execute -Djqassistant.query="your-cypher-query" +``` + +or from the web interface: http://localhost:7474 + +## Further Reading + +- [jQAssistant Documentation](https://jqassistant.org/documentation/) +- [Cypher Query Language](https://neo4j.com/developer/cypher/) +- [jMolecules jQAssistant Integration](https://github.com/xmolecules/jmolecules-jqassistant) \ No newline at end of file diff --git a/jmolecules-architecture-guide/references/spring-modulith.md b/jmolecules-architecture-guide/references/spring-modulith.md new file mode 100644 index 0000000..72d206a --- /dev/null +++ b/jmolecules-architecture-guide/references/spring-modulith.md @@ -0,0 +1,269 @@ +# Spring Modulith Integration + +This document provides guidance on integrating jMolecules with Spring Modulith for module decomposition and documentation. + +## What is Spring Modulith? + +Spring Modulith is a module decomposition and documentation tool for Spring Boot applications. It helps you: + +1. Define modules with clear boundaries +2. Visualize module dependencies +3. Validate module structure +4. Generate module documentation + +## Getting Started + +Add the required dependencies: + +```xml + + + org.springframework.modulith + spring-modulith-core + 1.1.0 + + + org.springframework.modulith + spring-modulith-starter-test + 1.1.0 + test + + +``` + +## Configuration + +Enable Spring Modulith in your application: + +```java +@SpringBootApplication +@EnableSpringModulith +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} +``` + +## Module Structure + +Spring Modulith follows a conventional module structure: + +``` +src/main/java +└── com/example + ├── inventory + │ ├── InventoryController.java + │ ├── InventoryService.java + │ ├── InventoryRepository.java + │ └── InventoryItem.java + ├── order + │ ├── OrderController.java + │ ├── OrderService.java + │ ├── OrderRepository.java + │ └── Order.java + └── payment + ├── PaymentController.java + ├── PaymentService.java + ├── PaymentRepository.java + └── Payment.java +``` + +## Integrating with jMolecules + +### Module Identification + +Spring Modulith automatically identifies modules based on package structure. You can customize this with: + +```java +@Module +@BoundedContext("Inventory Management") +public interface InventoryModule { + + @ModuleInfo(name = "Inventory Management", description = "Manages inventory items") + class Configuration {} +} +``` + +### Validating Architecture + +Spring Modulith provides tests for validating your architecture: + +```java +@SpringBootTest +@ExtendWith(SpringExtension.class) +class ApplicationTests { + + @Test + void verifiesModuleStructure(ModuleTestAPI modules) { + modules.verify() + .haveExactlyOneModuleWithName("inventory") + .haveExactlyOneModuleWithName("order") + .haveExactlyOneModuleWithName("payment"); + } + + @Test + void verifiesModuleDependencies(ModuleTestAPI modules) { + modules.verify() + .that(modules.byName("inventory")) + .doesNotDependOnModulesThat() + .haveNameMatching("order|payment"); + } +} +``` + +### Documenting Modules + +Spring Modulith can generate module documentation: + +```java +@ApplicationModuleTest +class ModuleDocumentationTests { + + @Test + void documentModules(Documenter documenter) { + documenter.writeDocumentation(Documenter.Options.defaults()); + } +} +``` + +Run this test to generate documentation in: + +``` +target/spring-modulith-docs/ +├── modules.md +├── dependencies.png +├── module-structure.html +└── ... +``` + +## Advanced Configuration + +### Customizing Module Detection + +```java +@Configuration +public class ModulithConfiguration { + + @Bean + ApplicationModuleDetectionStrategy moduleDetection() { + return ApplicationModuleDetectionStrategy.explicit() + .including("com.example.order", "com.example.payment") + .excluding("com.example.shared"); + } +} +``` + +### Module Boundaries + +```java +@Module +public class OrderModule { + + @PublicApi + public void placeOrder(Order order) { + // Implementation + } + + void internalMethod() { + // Implementation + } +} +``` + +### Event Publication + +```java +@AggregateRoot +@Entity +public class Order { + + @DomainEvent + public static class OrderPlaced { + // Event properties + } + + public OrderPlaced placeOrder() { + // Business logic + return new OrderPlaced(); + } +} + +@Module +public class OrderModule { + + @EventListener + void handleOrderPlaced(OrderPlaced event) { + // Event handling + } +} +``` + +## Running Application Tests + +Spring Modulith provides a test support module: + +```java +@ApplicationModuleTest +class OrderModuleTests { + + @Autowired + private OrderService orderService; + + @Test + void shouldCreateOrder() { + // Test logic + Order order = orderService.createOrder(); + assertThat(order).isNotNull(); + } +} +``` + +## Generating Documentation + +Run the module documentation test to generate: + +1. Markdown documentation +2. PNG dependency diagrams +3. HTML documentation +4. Asciidoctor documentation + +Run: + +```bash +mvn test -Dtest=ModuleDocumentationTests +``` + +## Integrating with jQAssistant + +Spring Modulith works well with jQAssistant: + +```java +@ArchTest +static ArchRule should_follow_spring_modulith_conventions = ArchRuleDefinition.allClasses() + .that().areAnnotatedWith(Module.class) + .should().resideInAPackage("com.example..module..") + .orShould().resideInAPackage("com.example..modulith.."); +``` + +## Best Practices + +1. **Keep modules small**: Each module should address one business capability +2. **Limit dependencies**: Minimize cross-module dependencies +3. **Explicit APIs**: Use `@PublicApi` to define clear boundaries +4. **Document thoroughly**: Generate module documentation regularly +5. **Test boundaries**: Use Spring Modulith tests to validate architecture + +## Tools and Integrations + +- **jQAssistant**: Static analysis and documentation +- **Spring Boot DevTools**: Hot reloading for faster development +- **Spring Boot Actuator**: Health checks and metrics +- **Docker**: Containerization for deployment + +## Resources + +- [Spring Modulith Documentation](https://docs.spring.io/spring-modulith/reference/index.html) +- [jMolecules Reference](https://jmolecules.org/reference/) +- [Spring Modulith Examples](https://github.com/spring-projects/spring-modulith-examples) \ No newline at end of file