├── .gitignore ├── docs ├── domain.png ├── domain_model.png ├── queries_handle.png ├── executor_command.png └── code │ ├── executor-command.puml │ ├── queries-handle.puml │ └── domain-model.puml ├── .travis.yml ├── co.com.sofka.domain ├── src │ └── main │ │ └── java │ │ ├── module-info.java │ │ └── co │ │ └── com │ │ └── sofka │ │ └── domain │ │ ├── generic │ │ ├── Incremental.java │ │ ├── Query.java │ │ ├── ViewModel.java │ │ ├── ValueObject.java │ │ ├── EventChange.java │ │ ├── AggregateRoot.java │ │ ├── Command.java │ │ ├── Entity.java │ │ ├── Identity.java │ │ ├── ChangeEventSubscriber.java │ │ ├── AggregateEvent.java │ │ └── DomainEvent.java │ │ └── annotation │ │ ├── Aggregate.java │ │ └── BehaviorHandles.java └── pom.xml ├── co.com.sofka.application ├── src │ └── main │ │ └── java │ │ ├── module-info.java │ │ └── co │ │ └── com │ │ └── sofka │ │ └── application │ │ ├── ServiceBuildException.java │ │ ├── init │ │ ├── InitializerCommandExecutor.java │ │ ├── InitializerArgumentExecutor.java │ │ └── BaseApplicationExecutor.java │ │ ├── ApplicationQueryExecutor.java │ │ ├── ApplicationCommandExecutor.java │ │ ├── ApplicationArgumentExecutor.java │ │ └── ApplicationEventDrive.java └── pom.xml ├── co.com.sofka.business ├── src │ └── main │ │ └── java │ │ ├── co │ │ └── com │ │ │ └── sofka │ │ │ └── business │ │ │ ├── annotation │ │ │ ├── QueryHandles.java │ │ │ ├── CommandHandles.java │ │ │ ├── QueryPath.java │ │ │ ├── EventListener.java │ │ │ ├── ExtensionService.java │ │ │ └── CommandType.java │ │ │ ├── support │ │ │ ├── TriggeredEvent.java │ │ │ ├── RequestCommand.java │ │ │ └── ResponseEvents.java │ │ │ ├── repository │ │ │ ├── DomainEventRepository.java │ │ │ ├── QueryMapperRepository.java │ │ │ └── QueryRepository.java │ │ │ ├── generic │ │ │ ├── UseCaseResponse.java │ │ │ ├── ReplyBusinessException.java │ │ │ ├── UnexpectedException.java │ │ │ ├── ServiceBuilder.java │ │ │ ├── BusinessException.java │ │ │ ├── UseCaseHandler.java │ │ │ ├── UseCaseReplyUtil.java │ │ │ └── UseCase.java │ │ │ ├── asyn │ │ │ ├── PublisherEvent.java │ │ │ ├── UseCaseArgumentExecutor.java │ │ │ ├── UseCaseCommandExecutor.java │ │ │ └── BaseUseCaseExecutor.java │ │ │ └── sync │ │ │ ├── ViewModelExecutor.java │ │ │ └── UseCaseExecutor.java │ │ └── module-info.java └── pom.xml ├── CHANGELOG.txt ├── co.com.sofka.infrastructure ├── src │ └── main │ │ └── java │ │ ├── co │ │ └── com │ │ │ └── sofka │ │ │ └── infraestructure │ │ │ ├── handle │ │ │ ├── CommandHandler.java │ │ │ ├── HandlerExecutionError.java │ │ │ ├── ExecutionNoFound.java │ │ │ ├── QueryHandler.java │ │ │ ├── QueryExecutor.java │ │ │ ├── CommandWrapper.java │ │ │ ├── ArgumentExecutor.java │ │ │ └── CommandExecutor.java │ │ │ ├── repository │ │ │ ├── QueryFaultException.java │ │ │ └── EventStoreRepository.java │ │ │ ├── DeserializeEventException.java │ │ │ ├── bus │ │ │ ├── CommandBus.java │ │ │ ├── EventBus.java │ │ │ ├── serialize │ │ │ │ ├── ErrorNotificationSerializer.java │ │ │ │ └── SuccessNotificationSerializer.java │ │ │ └── notification │ │ │ │ ├── Notification.java │ │ │ │ ├── ErrorNotification.java │ │ │ │ └── SuccessNotification.java │ │ │ ├── controller │ │ │ ├── CommandController.java │ │ │ └── CommandWrapperSerializer.java │ │ │ ├── event │ │ │ ├── ErrorEvent.java │ │ │ ├── EventSerializer.java │ │ │ └── ErrorEventSerializer.java │ │ │ ├── store │ │ │ ├── StoredEventSerializer.java │ │ │ └── StoredEvent.java │ │ │ ├── AbstractSerializer.java │ │ │ └── asyn │ │ │ └── SubscriberEvent.java │ │ └── module-info.java └── pom.xml ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | lsp 4 | *.iml -------------------------------------------------------------------------------- /docs/domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofka-XT/ddd-generic-java/HEAD/docs/domain.png -------------------------------------------------------------------------------- /docs/domain_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofka-XT/ddd-generic-java/HEAD/docs/domain_model.png -------------------------------------------------------------------------------- /docs/queries_handle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofka-XT/ddd-generic-java/HEAD/docs/queries_handle.png -------------------------------------------------------------------------------- /docs/executor_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofka-XT/ddd-generic-java/HEAD/docs/executor_command.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: java 3 | jdk: 4 | - openjdk11 5 | install: 6 | - mvn install -DskipTests=true -Dgpg.skip=true -Dmaven.javadoc.skip=true -B -V 7 | -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module co.com.sofka.domain { 2 | exports co.com.sofka.domain.generic; 3 | exports co.com.sofka.domain.annotation; 4 | } -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/Incremental.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | /** 4 | * The interface Incremental. 5 | */ 6 | public interface Incremental { 7 | } 8 | -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/Query.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * The interface Query. 7 | */ 8 | public interface Query extends Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /co.com.sofka.application/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module co.com.sofka.application { 2 | requires java.logging; 3 | requires io.github.classgraph; 4 | 5 | requires co.com.sofka.infraestructure; 6 | requires co.com.sofka.business; 7 | requires co.com.sofka.domain; 8 | 9 | exports co.com.sofka.application; 10 | 11 | } -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/annotation/Aggregate.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * The interface Aggregate. 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface Aggregate { 12 | } 13 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/annotation/QueryHandles.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * The interface Query handles. 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface QueryHandles { 12 | } 13 | -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/annotation/BehaviorHandles.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * The interface Behavior handles. 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface BehaviorHandles { 12 | } 13 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/annotation/CommandHandles.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * The interface Command handles. 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface CommandHandles { 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | f7647b3 (HEAD -> fixed_domain) fixed of the domain and usecase, helper for executer use case 2 | 3283b59 (origin/fixed_domain) best way for create behaviors and domain events 3 | 5c3a7d6 (origin/features/divide_for_modules, features/divide_for_modules) best documentation according with the segregation concepts 4 | fbb5f6f best documentation according with the segregation concepts -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/ViewModel.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | 4 | import java.io.Serializable; 5 | 6 | /** 7 | * The interface View model. 8 | */ 9 | public interface ViewModel extends Serializable { 10 | /** 11 | * Gets identity. 12 | * 13 | * @return the identity 14 | */ 15 | String getIdentity(); 16 | } 17 | -------------------------------------------------------------------------------- /docs/code/executor-command.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | UI -> "Command Handle": [POST] Request Command 3 | "Command Handle" -> UI: Response OK 200 4 | "Command Handle" --> "Use Case": Executor Async 5 | "Use Case" -> Aggregate: Behavior 6 | Aggregate -> Aggregate: Event Change 7 | Aggregate -> "Use Case": Emit Domain Events 8 | "Use Case" -> Infrastructure: Domain Events 9 | Infrastructure --> UI: Domain Events 10 | @enduml -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/ValueObject.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * The interface Value object. 7 | * 8 | * @param the type parameter 9 | */ 10 | public interface ValueObject extends Serializable { 11 | /** 12 | * Value t. 13 | * 14 | * @return the t 15 | */ 16 | T value(); 17 | } 18 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module co.com.sofka.business { 2 | exports co.com.sofka.business.asyn; 3 | exports co.com.sofka.business.generic; 4 | exports co.com.sofka.business.support; 5 | exports co.com.sofka.business.annotation; 6 | exports co.com.sofka.business.repository; 7 | exports co.com.sofka.business.sync; 8 | requires co.com.sofka.domain; 9 | requires java.logging; 10 | } -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/handle/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.handle; 2 | 3 | /** 4 | * The interface Command handler. 5 | * 6 | * @param the type parameter 7 | */ 8 | @FunctionalInterface 9 | public interface CommandHandler { 10 | /** 11 | * Execute. 12 | * 13 | * @param args the args 14 | */ 15 | void execute(T args); 16 | } -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/annotation/QueryPath.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * The interface Query path. 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface QueryPath { 12 | /** 13 | * Name string. 14 | * 15 | * @return the string 16 | */ 17 | String name(); 18 | } 19 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/annotation/EventListener.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * The interface Event listener. 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface EventListener { 12 | /** 13 | * Event type string. 14 | * 15 | * @return the string 16 | */ 17 | String eventType(); 18 | } -------------------------------------------------------------------------------- /co.com.sofka.application/src/main/java/co/com/sofka/application/ServiceBuildException.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.application; 2 | 3 | /** 4 | * The type Service build exception. 5 | */ 6 | public class ServiceBuildException extends RuntimeException { 7 | /** 8 | * Instantiates a new Service build exception. 9 | * 10 | * @param throwable the throwable 11 | */ 12 | public ServiceBuildException(Throwable throwable) { 13 | super(throwable); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/annotation/ExtensionService.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * The interface Extension service. 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface ExtensionService { 12 | /** 13 | * Value class [ ]. 14 | * 15 | * @return the class [ ] 16 | */ 17 | Class[] value(); 18 | } 19 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/handle/HandlerExecutionError.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.handle; 2 | 3 | /** 4 | * The type Handler execution error. 5 | */ 6 | public final class HandlerExecutionError extends Exception { 7 | /** 8 | * Instantiates a new Handler execution error. 9 | * 10 | * @param cause the cause 11 | */ 12 | public HandlerExecutionError(Throwable cause) { 13 | super(cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/repository/QueryFaultException.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.repository; 2 | 3 | /** 4 | * The type Query fault exception. 5 | */ 6 | public class QueryFaultException extends Exception { 7 | /** 8 | * Instantiates a new Query fault exception. 9 | * 10 | * @param message the message 11 | */ 12 | public QueryFaultException(String message) { 13 | super(message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/DeserializeEventException.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure; 2 | 3 | /** 4 | * The type Deserialize event exception. 5 | */ 6 | public class DeserializeEventException extends RuntimeException { 7 | /** 8 | * Instantiates a new Deserialize event exception. 9 | * 10 | * @param cause the cause 11 | */ 12 | public DeserializeEventException(Throwable cause) { 13 | super(cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module co.com.sofka.infraestructure { 2 | exports co.com.sofka.infraestructure; 3 | exports co.com.sofka.infraestructure.event; 4 | exports co.com.sofka.infraestructure.bus; 5 | exports co.com.sofka.infraestructure.handle; 6 | exports co.com.sofka.infraestructure.repository; 7 | exports co.com.sofka.infraestructure.asyn; 8 | 9 | requires co.com.sofka.domain; 10 | requires co.com.sofka.business; 11 | requires com.google.gson; 12 | 13 | } -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/handle/ExecutionNoFound.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.handle; 2 | 3 | 4 | /** 5 | * The type Execution no found. 6 | */ 7 | public class ExecutionNoFound extends RuntimeException { 8 | /** 9 | * Instantiates a new Execution no found. 10 | * 11 | * @param type the type 12 | */ 13 | public ExecutionNoFound(String type) { 14 | super(String.format("The type [%s] to be executed does not have a handler", type)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/code/queries-handle.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | Query -> "Query Handle": [GET] find and get 3 | "Query Handle" -> BD: Basic Query 4 | BD -> "Query Handle": View Model 5 | "Query Handle" -> Query: Response (View Model) 6 | Command -> "Use Case": Request Command 7 | "Use Case" -> Aggregate: Behavior 8 | Aggregate -> Aggregate: Event Change 9 | Aggregate -> "Use Case": Emit Domain Events 10 | "Use Case" --> Bus: Publish Event 11 | Bus -> Listener: Domain Events 12 | Listener -> Materialize: Event Handle 13 | Materialize -> BD: Insert/Update/Delete 14 | @enduml -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/annotation/CommandType.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * The interface Command type. 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface CommandType { 12 | /** 13 | * Name string. 14 | * 15 | * @return the string 16 | */ 17 | String name(); 18 | 19 | /** 20 | * Aggregate string. 21 | * 22 | * @return the string 23 | */ 24 | String aggregate(); 25 | } 26 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/bus/CommandBus.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.bus; 2 | 3 | import co.com.sofka.domain.generic.Command; 4 | import co.com.sofka.infraestructure.handle.HandlerExecutionError; 5 | 6 | /** 7 | * The interface Command bus. 8 | */ 9 | @FunctionalInterface 10 | public interface CommandBus { 11 | /** 12 | * Dispatch. 13 | * 14 | * @param command the command 15 | * @throws HandlerExecutionError the handler execution error 16 | */ 17 | void dispatch(Command command) throws HandlerExecutionError; 18 | } 19 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/bus/EventBus.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.bus; 2 | 3 | import co.com.sofka.domain.generic.DomainEvent; 4 | import co.com.sofka.infraestructure.event.ErrorEvent; 5 | 6 | /** 7 | * The interface Event bus. 8 | */ 9 | public interface EventBus { 10 | /** 11 | * Publish. 12 | * 13 | * @param event the event 14 | */ 15 | void publish(DomainEvent event); 16 | 17 | /** 18 | * Publish error. 19 | * 20 | * @param errorEvent the error event 21 | */ 22 | void publishError(ErrorEvent errorEvent); 23 | } 24 | -------------------------------------------------------------------------------- /co.com.sofka.domain/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | domain-driven-design 7 | co.com.sofka 8 | 1.5.0 9 | 10 | 4.0.0 11 | 12 | domain 13 | jar 14 | ${ddd.version} 15 | -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/EventChange.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * The type Event change. 9 | */ 10 | public abstract class EventChange { 11 | 12 | /** 13 | * The Behaviors. 14 | */ 15 | protected Set> behaviors = new HashSet<>(); 16 | 17 | /** 18 | * Apply. 19 | * 20 | * @param changeEvent the change event 21 | */ 22 | protected void apply(Consumer changeEvent) { 23 | behaviors.add((Consumer) changeEvent); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/support/TriggeredEvent.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.support; 2 | 3 | import co.com.sofka.business.generic.UseCase; 4 | import co.com.sofka.domain.generic.DomainEvent; 5 | 6 | /** 7 | * The type Triggered event. 8 | * 9 | * @param the type parameter 10 | */ 11 | public final class TriggeredEvent implements UseCase.RequestEvent { 12 | private final T event; 13 | 14 | /** 15 | * Instantiates a new Triggered event. 16 | * 17 | * @param event the event 18 | */ 19 | public TriggeredEvent(T event) { 20 | this.event = event; 21 | } 22 | 23 | @Override 24 | public T getDomainEvent() { 25 | return event; 26 | } 27 | } -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/handle/QueryHandler.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.handle; 2 | 3 | import co.com.sofka.domain.generic.ViewModel; 4 | 5 | import java.util.List; 6 | 7 | 8 | /** 9 | * The interface Query handler. 10 | * 11 | * @param the type parameter 12 | */ 13 | public interface QueryHandler { 14 | /** 15 | * Get view model. 16 | * 17 | * @param path the path 18 | * @param query the query 19 | * @return the view model 20 | */ 21 | ViewModel get(String path, Q query); 22 | 23 | /** 24 | * Find list. 25 | * 26 | * @param path the path 27 | * @param query the query 28 | * @return the list 29 | */ 30 | List find(String path, Q query); 31 | } -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/AggregateRoot.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | /** 4 | * The type Aggregate root. 5 | * 6 | * @param the type parameter 7 | */ 8 | public abstract class AggregateRoot extends Entity { 9 | 10 | /** 11 | * The Version type. 12 | */ 13 | protected Long versionType; 14 | 15 | /** 16 | * Instantiates a new Aggregate root. 17 | * 18 | * @param entityId the entity id 19 | */ 20 | public AggregateRoot(T entityId) { 21 | super(entityId); 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | return super.equals(o); 27 | } 28 | 29 | @Override 30 | public int hashCode() { 31 | return super.hashCode(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/support/RequestCommand.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.support; 2 | 3 | import co.com.sofka.business.generic.UseCase; 4 | import co.com.sofka.domain.generic.Command; 5 | 6 | /** 7 | * The type Request command. 8 | * 9 | * @param the type parameter 10 | */ 11 | public final class RequestCommand implements UseCase.RequestValues { 12 | 13 | private final T command; 14 | 15 | /** 16 | * Instantiates a new Request command. 17 | * 18 | * @param command the command 19 | */ 20 | public RequestCommand(T command) { 21 | this.command = command; 22 | } 23 | 24 | /** 25 | * Gets command. 26 | * 27 | * @return the command 28 | */ 29 | public T getCommand() { 30 | return command; 31 | } 32 | } -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/repository/DomainEventRepository.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.repository; 2 | 3 | import co.com.sofka.domain.generic.DomainEvent; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * The interface Domain event repository. 9 | */ 10 | public interface DomainEventRepository { 11 | /** 12 | * Gets events by. 13 | * 14 | * @param aggregateRootId the aggregate root id 15 | * @return the events by 16 | */ 17 | List getEventsBy(String aggregateRootId); 18 | 19 | /** 20 | * Gets events by. 21 | * 22 | * @param aggregate the aggregate 23 | * @param aggregateRootId the aggregate root id 24 | * @return the events by 25 | */ 26 | List getEventsBy(String aggregate, String aggregateRootId); 27 | } 28 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/support/ResponseEvents.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.support; 2 | 3 | import co.com.sofka.business.generic.UseCase; 4 | import co.com.sofka.domain.generic.DomainEvent; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * The type Response events. 10 | */ 11 | public final class ResponseEvents implements UseCase.ResponseValues { 12 | 13 | private final List events; 14 | 15 | /** 16 | * Instantiates a new Response events. 17 | * 18 | * @param events the events 19 | */ 20 | public ResponseEvents(List events) { 21 | this.events = events; 22 | } 23 | 24 | /** 25 | * Gets domain events. 26 | * 27 | * @return the domain events 28 | */ 29 | public List getDomainEvents() { 30 | return events; 31 | } 32 | } -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/Command.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | 4 | import java.io.Serializable; 5 | import java.time.Instant; 6 | import java.util.UUID; 7 | 8 | /** 9 | * The type Command. 10 | */ 11 | public class Command implements Serializable { 12 | 13 | private final long when; 14 | private final String uuid; 15 | 16 | 17 | /** 18 | * Instantiates a new Command. 19 | */ 20 | public Command() { 21 | this.uuid = UUID.randomUUID().toString(); 22 | this.when = Instant.now().toEpochMilli(); 23 | } 24 | 25 | /** 26 | * When long. 27 | * 28 | * @return the long 29 | */ 30 | public long when() { 31 | return when; 32 | } 33 | 34 | /** 35 | * Uuid string. 36 | * 37 | * @return the string 38 | */ 39 | public String uuid() { 40 | return uuid; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /co.com.sofka.business/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | domain-driven-design 7 | co.com.sofka 8 | 1.5.0 9 | 10 | 4.0.0 11 | business 12 | jar 13 | ${ddd.version} 14 | 15 | 16 | 17 | co.com.sofka 18 | domain 19 | ${ddd.version} 20 | compile 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/generic/UseCaseResponse.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.generic; 2 | 3 | /** 4 | * The type Use case response. 5 | * 6 | * @param the type parameter 7 | */ 8 | public final class UseCaseResponse implements UseCase.UseCaseFormat { 9 | /** 10 | * The Response. 11 | */ 12 | protected R response; 13 | /** 14 | * The Exception. 15 | */ 16 | protected RuntimeException exception; 17 | 18 | @Override 19 | public void onResponse(R response) { 20 | this.response = response; 21 | } 22 | 23 | @Override 24 | public void onError(RuntimeException exception) { 25 | this.exception = exception; 26 | } 27 | 28 | /** 29 | * Has error boolean. 30 | * 31 | * @return the boolean 32 | */ 33 | public boolean hasError() { 34 | return exception != null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/generic/ReplyBusinessException.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.generic; 2 | 3 | 4 | /** 5 | * The type Reply business exception. 6 | */ 7 | public class ReplyBusinessException extends UnexpectedException { 8 | 9 | 10 | /** 11 | * Instantiates a new Reply business exception. 12 | * 13 | * @param identify the identify 14 | * @param message the message 15 | * @param throwable the throwable 16 | */ 17 | public ReplyBusinessException(String identify, String message, Throwable throwable) { 18 | super(identify, message, throwable); 19 | } 20 | 21 | 22 | /** 23 | * Instantiates a new Reply business exception. 24 | * 25 | * @param identify the identify 26 | * @param message the message 27 | */ 28 | public ReplyBusinessException(String identify, String message) { 29 | this(identify, message, null); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/controller/CommandController.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.controller; 2 | 3 | 4 | import co.com.sofka.domain.generic.Command; 5 | import co.com.sofka.infraestructure.bus.CommandBus; 6 | import co.com.sofka.infraestructure.handle.HandlerExecutionError; 7 | 8 | /** 9 | * The type Command controller. 10 | */ 11 | public abstract class CommandController { 12 | private final CommandBus commandBus; 13 | 14 | /** 15 | * Instantiates a new Command controller. 16 | * 17 | * @param commandBus the command bus 18 | */ 19 | public CommandController(CommandBus commandBus) { 20 | this.commandBus = commandBus; 21 | } 22 | 23 | /** 24 | * Dispatch. 25 | * 26 | * @param command the command 27 | * @throws HandlerExecutionError the handler execution error 28 | */ 29 | protected void dispatch(Command command) throws HandlerExecutionError { 30 | commandBus.dispatch(command); 31 | } 32 | } -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/event/ErrorEvent.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.event; 2 | 3 | import java.time.Instant; 4 | import java.util.UUID; 5 | 6 | /** 7 | * The type Error event. 8 | */ 9 | public class ErrorEvent { 10 | 11 | /** 12 | * The When. 13 | */ 14 | public final Instant when; 15 | /** 16 | * The Uuid. 17 | */ 18 | public final UUID uuid; 19 | /** 20 | * The Identify. 21 | */ 22 | public final String identify; 23 | /** 24 | * The Error. 25 | */ 26 | public final Throwable error; 27 | 28 | /** 29 | * Instantiates a new Error event. 30 | * 31 | * @param identify the identify 32 | * @param throwable the throwable 33 | */ 34 | public ErrorEvent(String identify, Throwable throwable) { 35 | this.identify = identify; 36 | this.error = throwable; 37 | this.when = Instant.now(); 38 | this.uuid = UUID.randomUUID(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/repository/EventStoreRepository.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.repository; 2 | 3 | import co.com.sofka.domain.generic.DomainEvent; 4 | import co.com.sofka.infraestructure.store.StoredEvent; 5 | 6 | import java.util.List; 7 | 8 | 9 | /** 10 | * The interface Event store repository. 11 | */ 12 | public interface EventStoreRepository { 13 | /** 14 | * Gets events by. 15 | * 16 | * @param aggregateName the aggregate name 17 | * @param aggregateRootId the aggregate root id 18 | * @return the events by 19 | */ 20 | List getEventsBy(String aggregateName, String aggregateRootId); 21 | 22 | /** 23 | * Save event. 24 | * 25 | * @param aggregateName the aggregate name 26 | * @param aggregateRootId the aggregate root id 27 | * @param storedEvent the stored event 28 | */ 29 | void saveEvent(String aggregateName, String aggregateRootId, StoredEvent storedEvent); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/asyn/PublisherEvent.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.asyn; 2 | 3 | import co.com.sofka.business.generic.UseCase; 4 | import co.com.sofka.business.support.ResponseEvents; 5 | import co.com.sofka.domain.generic.DomainEvent; 6 | 7 | import java.util.concurrent.SubmissionPublisher; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | 11 | /** 12 | * The type Publisher event. 13 | */ 14 | public final class PublisherEvent extends SubmissionPublisher implements UseCase.UseCaseFormat { 15 | private static final Logger logger = Logger.getLogger(PublisherEvent.class.getName()); 16 | 17 | @Override 18 | public void onResponse(ResponseEvents responseEvents) { 19 | logger.log(Level.INFO, "Events processed {0}", responseEvents.getDomainEvents().size()); 20 | responseEvents.getDomainEvents().forEach(this::submit); 21 | } 22 | 23 | @Override 24 | public void onError(RuntimeException exception) { 25 | getSubscribers() 26 | .forEach(sub -> sub.onError(exception)); 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/Entity.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * The type Entity. 7 | * 8 | * @param the type parameter 9 | */ 10 | public abstract class Entity { 11 | /** 12 | * The Entity id. 13 | */ 14 | protected I entityId; 15 | 16 | /** 17 | * Instantiates a new Entity. 18 | * 19 | * @param entityId the entity id 20 | */ 21 | public Entity(I entityId) { 22 | this.entityId = Objects.requireNonNull(entityId, "The identity cannot be a value null"); 23 | } 24 | 25 | /** 26 | * Identity . 27 | * 28 | * @return the 29 | */ 30 | public I identity() { 31 | return entityId; 32 | } 33 | 34 | @Override 35 | public boolean equals(Object object) { 36 | if (this == object) return true; 37 | if (object == null || getClass() != object.getClass()) return false; 38 | Entity entity = (Entity) object; 39 | return entityId.value().equals(entity.entityId.value()); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Objects.hash(entityId); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/generic/UnexpectedException.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.generic; 2 | 3 | /** 4 | * The type Unexpected exception. 5 | */ 6 | public class UnexpectedException extends RuntimeException { 7 | private final String identify; 8 | 9 | private String request; 10 | 11 | /** 12 | * Instantiates a new Unexpected exception. 13 | * 14 | * @param identify the identify 15 | * @param message the message 16 | * @param cause the cause 17 | */ 18 | public UnexpectedException(String identify, String message, Throwable cause) { 19 | super(message, cause); 20 | this.identify = identify; 21 | } 22 | 23 | /** 24 | * Gets request. 25 | * 26 | * @return the request 27 | */ 28 | public String getRequest() { 29 | return request; 30 | } 31 | 32 | /** 33 | * Sets request. 34 | * 35 | * @param request the request 36 | */ 37 | public void setRequest(String request) { 38 | this.request = request; 39 | } 40 | 41 | /** 42 | * Gets identify. 43 | * 44 | * @return the identify 45 | */ 46 | public String getIdentify() { 47 | return identify; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /co.com.sofka.application/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | domain-driven-design 7 | co.com.sofka 8 | 1.5.0 9 | 10 | 4.0.0 11 | application 12 | jar 13 | ${ddd.version} 14 | 15 | 16 | 17 | co.com.sofka 18 | infrastructure 19 | ${ddd.version} 20 | 21 | 22 | io.github.classgraph 23 | classgraph 24 | 4.8.78 25 | 26 | 27 | co.com.sofka 28 | domain 29 | ${ddd.version} 30 | compile 31 | 32 | 33 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | domain-driven-design 7 | co.com.sofka 8 | 1.5.0 9 | 10 | 4.0.0 11 | 12 | infrastructure 13 | jar 14 | ${ddd.version} 15 | 16 | 17 | 18 | co.com.sofka 19 | domain 20 | ${ddd.version} 21 | compile 22 | 23 | 24 | co.com.sofka 25 | business 26 | ${ddd.version} 27 | compile 28 | 29 | 30 | com.google.code.gson 31 | gson 32 | 2.8.6 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/generic/ServiceBuilder.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.generic; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | /** 8 | * The type Service builder. 9 | */ 10 | public class ServiceBuilder { 11 | private final Map builder = new ConcurrentHashMap<>(); 12 | 13 | /** 14 | * Add service service builder. 15 | * 16 | * @param object the object 17 | * @return the service builder 18 | */ 19 | public ServiceBuilder addService(Object object) { 20 | builder.put(object.getClass().getCanonicalName(), object); 21 | return this; 22 | } 23 | 24 | 25 | /** 26 | * Gets service. 27 | * 28 | * @param the type parameter 29 | * @param clasz the clasz 30 | * @return the service 31 | */ 32 | public Optional getService(Class clasz) { 33 | return builder.values().stream().filter(clasz::isInstance) 34 | .map(inst -> (T) inst) 35 | .findFirst(); 36 | } 37 | 38 | /** 39 | * Exist boolean. 40 | * 41 | * @param loadClass the load class 42 | * @return the boolean 43 | */ 44 | public boolean exist(Class loadClass) { 45 | return builder.containsKey(loadClass.getCanonicalName()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/repository/QueryMapperRepository.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.repository; 2 | 3 | 4 | import co.com.sofka.domain.generic.Query; 5 | import co.com.sofka.domain.generic.ViewModel; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * The interface Query mapper repository. 11 | */ 12 | public interface QueryMapperRepository { 13 | 14 | /** 15 | * Gets data mapped. 16 | * 17 | * @param the type parameter 18 | * @param category the category 19 | * @param classViewModel the class view model 20 | * @return the data mapped 21 | */ 22 | ApplyQuery getDataMapped(String category, Class classViewModel); 23 | 24 | /** 25 | * The interface Apply query. 26 | * 27 | * @param the type parameter 28 | */ 29 | interface ApplyQuery { 30 | /** 31 | * Apply as list list. 32 | * 33 | * @param the type parameter 34 | * @param query the query 35 | * @return the list 36 | */ 37 | List applyAsList(Q query); 38 | 39 | /** 40 | * Apply as element t. 41 | * 42 | * @param the type parameter 43 | * @param query the query 44 | * @return the t 45 | */ 46 | T applyAsElement(Q query); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/controller/CommandWrapperSerializer.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.controller; 2 | 3 | import co.com.sofka.infraestructure.AbstractSerializer; 4 | import co.com.sofka.infraestructure.handle.CommandWrapper; 5 | 6 | /** 7 | * The type Command wrapper serializer. 8 | */ 9 | public final class CommandWrapperSerializer extends AbstractSerializer { 10 | 11 | private static CommandWrapperSerializer eventSerializer; 12 | 13 | private CommandWrapperSerializer() { 14 | super(); 15 | } 16 | 17 | 18 | /** 19 | * Instance command wrapper serializer. 20 | * 21 | * @return the command wrapper serializer 22 | */ 23 | public static synchronized CommandWrapperSerializer instance() { 24 | if (eventSerializer == null) { 25 | eventSerializer = new CommandWrapperSerializer(); 26 | } 27 | return eventSerializer; 28 | } 29 | 30 | 31 | /** 32 | * Deserialize command wrapper. 33 | * 34 | * @param aSerialization the a serialization 35 | * @return the command wrapper 36 | */ 37 | public CommandWrapper deserialize(String aSerialization) { 38 | return gson.fromJson(aSerialization, CommandWrapper.class); 39 | } 40 | 41 | 42 | /** 43 | * Serialize string. 44 | * 45 | * @param object the object 46 | * @return the string 47 | */ 48 | public String serialize(CommandWrapper object) { 49 | return gson.toJson(object); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/store/StoredEventSerializer.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.store; 2 | 3 | import co.com.sofka.infraestructure.AbstractSerializer; 4 | 5 | 6 | /** 7 | * The type Stored event serializer. 8 | */ 9 | public final class StoredEventSerializer extends AbstractSerializer { 10 | 11 | private static StoredEventSerializer eventSerializer; 12 | 13 | private StoredEventSerializer() { 14 | super(); 15 | } 16 | 17 | /** 18 | * Instance stored event serializer. 19 | * 20 | * @return the stored event serializer 21 | */ 22 | public static synchronized StoredEventSerializer instance() { 23 | if (StoredEventSerializer.eventSerializer == null) { 24 | StoredEventSerializer.eventSerializer = new StoredEventSerializer(); 25 | } 26 | return StoredEventSerializer.eventSerializer; 27 | } 28 | 29 | /** 30 | * Deserialize stored event. 31 | * 32 | * @param aSerialization the a serialization 33 | * @param aType the a type 34 | * @return the stored event 35 | */ 36 | public StoredEvent deserialize(String aSerialization, Class aType) { 37 | return gson.fromJson(aSerialization, aType); 38 | } 39 | 40 | /** 41 | * Serialize string. 42 | * 43 | * @param object the object 44 | * @return the string 45 | */ 46 | public String serialize(StoredEvent object) { 47 | return gson.toJson(object); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/bus/serialize/ErrorNotificationSerializer.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.bus.serialize; 2 | 3 | import co.com.sofka.infraestructure.AbstractSerializer; 4 | import co.com.sofka.infraestructure.bus.notification.ErrorNotification; 5 | 6 | /** 7 | * The type Error notification serializer. 8 | */ 9 | public final class ErrorNotificationSerializer extends AbstractSerializer { 10 | 11 | private static ErrorNotificationSerializer eventSerializer; 12 | 13 | private ErrorNotificationSerializer() { 14 | super(); 15 | } 16 | 17 | 18 | /** 19 | * Instance error notification serializer. 20 | * 21 | * @return the error notification serializer 22 | */ 23 | public static synchronized ErrorNotificationSerializer instance() { 24 | if (eventSerializer == null) { 25 | eventSerializer = new ErrorNotificationSerializer(); 26 | } 27 | return eventSerializer; 28 | } 29 | 30 | 31 | /** 32 | * Deserialize error notification. 33 | * 34 | * @param aSerialization the a serialization 35 | * @return the error notification 36 | */ 37 | public ErrorNotification deserialize(String aSerialization) { 38 | return gson.fromJson(aSerialization, ErrorNotification.class); 39 | } 40 | 41 | 42 | /** 43 | * Serialize string. 44 | * 45 | * @param object the object 46 | * @return the string 47 | */ 48 | public String serialize(ErrorNotification object) { 49 | return gson.toJson(object); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/event/EventSerializer.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.event; 2 | 3 | import co.com.sofka.domain.generic.DomainEvent; 4 | import co.com.sofka.infraestructure.AbstractSerializer; 5 | 6 | import java.lang.reflect.Type; 7 | 8 | 9 | /** 10 | * The type Event serializer. 11 | */ 12 | public final class EventSerializer extends AbstractSerializer { 13 | 14 | private static EventSerializer eventSerializer; 15 | 16 | private EventSerializer() { 17 | super(); 18 | } 19 | 20 | /** 21 | * Instance event serializer. 22 | * 23 | * @return the event serializer 24 | */ 25 | public static synchronized EventSerializer instance() { 26 | if (EventSerializer.eventSerializer == null) { 27 | EventSerializer.eventSerializer = new EventSerializer(); 28 | } 29 | return EventSerializer.eventSerializer; 30 | } 31 | 32 | 33 | /** 34 | * Deserialize t. 35 | * 36 | * @param the type parameter 37 | * @param aSerialization the a serialization 38 | * @param aType the a type 39 | * @return the t 40 | */ 41 | public T deserialize(String aSerialization, final Class aType) { 42 | return gson.fromJson(aSerialization, (Type) aType); 43 | } 44 | 45 | /** 46 | * Serialize string. 47 | * 48 | * @param object the object 49 | * @return the string 50 | */ 51 | public String serialize(DomainEvent object) { 52 | return gson.toJson(object); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/event/ErrorEventSerializer.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.event; 2 | 3 | import co.com.sofka.infraestructure.AbstractSerializer; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | 8 | /** 9 | * The type Error event serializer. 10 | */ 11 | public final class ErrorEventSerializer extends AbstractSerializer { 12 | 13 | private static ErrorEventSerializer eventSerializer; 14 | 15 | private ErrorEventSerializer() { 16 | super(); 17 | } 18 | 19 | /** 20 | * Instance error event serializer. 21 | * 22 | * @return the error event serializer 23 | */ 24 | public static synchronized ErrorEventSerializer instance() { 25 | if (ErrorEventSerializer.eventSerializer == null) { 26 | ErrorEventSerializer.eventSerializer = new ErrorEventSerializer(); 27 | } 28 | return ErrorEventSerializer.eventSerializer; 29 | } 30 | 31 | 32 | /** 33 | * Deserialize t. 34 | * 35 | * @param the type parameter 36 | * @param aSerialization the a serialization 37 | * @param aType the a type 38 | * @return the t 39 | */ 40 | public T deserialize(String aSerialization, final Class aType) { 41 | return gson.fromJson(aSerialization, (Type) aType); 42 | } 43 | 44 | /** 45 | * Serialize string. 46 | * 47 | * @param object the object 48 | * @return the string 49 | */ 50 | public String serialize(ErrorEvent object) { 51 | return gson.toJson(object); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/bus/serialize/SuccessNotificationSerializer.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.bus.serialize; 2 | 3 | import co.com.sofka.infraestructure.AbstractSerializer; 4 | import co.com.sofka.infraestructure.bus.notification.SuccessNotification; 5 | 6 | /** 7 | * The type Success notification serializer. 8 | */ 9 | public final class SuccessNotificationSerializer extends AbstractSerializer { 10 | 11 | private static SuccessNotificationSerializer eventSerializer; 12 | 13 | private SuccessNotificationSerializer() { 14 | super(); 15 | } 16 | 17 | 18 | /** 19 | * Instance success notification serializer. 20 | * 21 | * @return the success notification serializer 22 | */ 23 | public static synchronized SuccessNotificationSerializer instance() { 24 | if (eventSerializer == null) { 25 | eventSerializer = new SuccessNotificationSerializer(); 26 | } 27 | return eventSerializer; 28 | } 29 | 30 | 31 | /** 32 | * Deserialize success notification. 33 | * 34 | * @param aSerialization the a serialization 35 | * @return the success notification 36 | */ 37 | public SuccessNotification deserialize(String aSerialization) { 38 | return gson.fromJson(aSerialization, SuccessNotification.class); 39 | } 40 | 41 | 42 | /** 43 | * Serialize string. 44 | * 45 | * @param object the object 46 | * @return the string 47 | */ 48 | public String serialize(SuccessNotification object) { 49 | return gson.toJson(object); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/generic/BusinessException.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.generic; 2 | 3 | import co.com.sofka.domain.generic.Command; 4 | 5 | /** 6 | * The type Business exception. 7 | */ 8 | public class BusinessException extends UnexpectedException { 9 | 10 | private final Command command; 11 | 12 | /** 13 | * Instantiates a new Business exception. 14 | * 15 | * @param identify the identify 16 | * @param message the message 17 | * @param throwable the throwable 18 | * @param command the command 19 | */ 20 | public BusinessException(String identify, String message, Throwable throwable, Command command) { 21 | super(identify, message, throwable); 22 | this.command = command; 23 | } 24 | 25 | /** 26 | * Instantiates a new Business exception. 27 | * 28 | * @param identify the identify 29 | * @param message the message 30 | * @param throwable the throwable 31 | */ 32 | public BusinessException(String identify, String message, Throwable throwable) { 33 | this(identify, message, throwable, null); 34 | } 35 | 36 | /** 37 | * Instantiates a new Business exception. 38 | * 39 | * @param identify the identify 40 | * @param message the message 41 | */ 42 | public BusinessException(String identify, String message) { 43 | this(identify, message, null, null); 44 | } 45 | 46 | /** 47 | * Gets command. 48 | * 49 | * @return the command 50 | */ 51 | public Command getCommand() { 52 | return command; 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/Identity.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | 4 | import java.util.Objects; 5 | import java.util.UUID; 6 | 7 | /** 8 | * The type Identity. 9 | */ 10 | public class Identity implements ValueObject { 11 | private final String uuid; 12 | 13 | /** 14 | * Instantiates a new Identity. 15 | * 16 | * @param uuid the uuid 17 | */ 18 | public Identity(String uuid) { 19 | this.uuid = Objects.requireNonNull(uuid, "Identity can´t be null"); 20 | if (this.uuid.isBlank()) { 21 | throw new IllegalArgumentException("Identity can´t be blank"); 22 | } 23 | } 24 | 25 | /** 26 | * Instantiates a new Identity. 27 | */ 28 | public Identity() { 29 | this.uuid = this.generateUUID().toString(); 30 | } 31 | 32 | /** 33 | * Generate uuid uuid. 34 | * 35 | * @return the uuid 36 | */ 37 | public UUID generateUUID() { 38 | return UUID.randomUUID(); 39 | } 40 | 41 | @Override 42 | public boolean equals(Object object) { 43 | if (this == object) return true; 44 | if (object == null || getClass() != object.getClass()) return false; 45 | Identity identity = (Identity) object; 46 | return Objects.equals(uuid, identity.uuid); 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return Objects.hash(uuid); 52 | } 53 | 54 | 55 | @Override 56 | public String value() { 57 | return uuid; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return uuid; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/AbstractSerializer.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure; 2 | 3 | import com.google.gson.*; 4 | 5 | import java.lang.reflect.Type; 6 | import java.time.Instant; 7 | 8 | /** 9 | * The type Abstract serializer. 10 | */ 11 | public abstract class AbstractSerializer { 12 | /** 13 | * The Gson. 14 | */ 15 | protected Gson gson; 16 | 17 | /** 18 | * Instantiates a new Abstract serializer. 19 | */ 20 | protected AbstractSerializer() { 21 | this.gson = new GsonBuilder() 22 | .registerTypeAdapter(Instant.class, new AbstractSerializer.DateSerializer()) 23 | .registerTypeAdapter(Instant.class, new AbstractSerializer.DateDeserializer()) 24 | .serializeNulls() 25 | .create(); 26 | } 27 | 28 | /** 29 | * Gets gson. 30 | * 31 | * @return the gson 32 | */ 33 | public Gson getGson() { 34 | return gson; 35 | } 36 | 37 | private static class DateSerializer implements JsonSerializer { 38 | @Override 39 | public JsonElement serialize(Instant source, Type typeOfSource, JsonSerializationContext context) { 40 | return new JsonPrimitive(Long.toString(source.toEpochMilli())); 41 | } 42 | } 43 | 44 | private static class DateDeserializer implements JsonDeserializer { 45 | @Override 46 | public Instant deserialize(JsonElement json, Type typeOfTarget, JsonDeserializationContext context) { 47 | long time = Long.parseLong(json.getAsJsonPrimitive().getAsString()); 48 | return Instant.ofEpochMilli(time); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/bus/notification/Notification.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.bus.notification; 2 | 3 | 4 | import java.util.Date; 5 | 6 | /** 7 | * The type Notification. 8 | */ 9 | public class Notification { 10 | private String origin; 11 | private String body; 12 | private Date occurredOn; 13 | private String typeName; 14 | 15 | /** 16 | * Instantiates a new Notification. 17 | */ 18 | public Notification() { 19 | } 20 | 21 | /** 22 | * Instantiates a new Notification. 23 | * 24 | * @param origin the origin 25 | * @param typeName the type name 26 | * @param occurredOn the occurred on 27 | * @param body the body 28 | */ 29 | public Notification(String origin, String typeName, Date occurredOn, String body) { 30 | this.origin = origin; 31 | this.body = body; 32 | this.occurredOn = occurredOn; 33 | this.typeName = typeName; 34 | } 35 | 36 | /** 37 | * Gets origin. 38 | * 39 | * @return the origin 40 | */ 41 | public String getOrigin() { 42 | return origin; 43 | } 44 | 45 | /** 46 | * Gets body. 47 | * 48 | * @return the body 49 | */ 50 | public String getBody() { 51 | return body; 52 | } 53 | 54 | /** 55 | * Gets occurred on. 56 | * 57 | * @return the occurred on 58 | */ 59 | public Date getOccurredOn() { 60 | return occurredOn; 61 | } 62 | 63 | /** 64 | * Gets type name. 65 | * 66 | * @return the type name 67 | */ 68 | public String getTypeName() { 69 | return typeName; 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/repository/QueryRepository.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.repository; 2 | 3 | import co.com.sofka.domain.generic.Query; 4 | 5 | import java.util.Optional; 6 | import java.util.stream.Stream; 7 | 8 | /** 9 | * The interface Query repository. 10 | * 11 | * @param the type parameter 12 | */ 13 | public interface QueryRepository { 14 | /** 15 | * Find all stream. 16 | * 17 | * @return the stream 18 | */ 19 | default Stream findAll() { 20 | return Stream.empty(); 21 | } 22 | 23 | /** 24 | * Find all stream. 25 | * 26 | * @param sort the sort 27 | * @return the stream 28 | */ 29 | default Stream findAll(Sort sort) { 30 | return Stream.empty(); 31 | } 32 | 33 | /** 34 | * Find stream. 35 | * 36 | * @param query the query 37 | * @return the stream 38 | */ 39 | default Stream find(Query query) { 40 | return Stream.empty(); 41 | } 42 | 43 | /** 44 | * Find stream. 45 | * 46 | * @param query the query 47 | * @param sort the sort 48 | * @return the stream 49 | */ 50 | default Stream find(Query query, Sort sort) { 51 | return Stream.empty(); 52 | } 53 | 54 | /** 55 | * Get optional. 56 | * 57 | * @param query the query 58 | * @return the optional 59 | */ 60 | default Optional get(Query query) { 61 | return Optional.empty(); 62 | } 63 | 64 | /** 65 | * The enum Sort. 66 | */ 67 | enum Sort { 68 | /** 69 | * Asc sort. 70 | */ 71 | ASC, 72 | /** 73 | * Desc sort. 74 | */ 75 | DESC 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/handle/QueryExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.handle; 2 | 3 | import co.com.sofka.domain.generic.ViewModel; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.function.Function; 9 | import java.util.logging.Logger; 10 | 11 | /** 12 | * The type Query executor. 13 | */ 14 | public class QueryExecutor implements QueryHandler> { 15 | private static final Logger logger = Logger.getLogger(QueryExecutor.class.getName()); 16 | 17 | /** 18 | * The Handles. 19 | */ 20 | protected Map, ?>> handles = new ConcurrentHashMap<>(); 21 | 22 | /** 23 | * Put. 24 | * 25 | * @param queryPath the query path 26 | * @param function the function 27 | */ 28 | protected void put(String queryPath, Function, ?> function) { 29 | handles.put(queryPath, function); 30 | } 31 | 32 | @Override 33 | public ViewModel get(String path, Map params) { 34 | 35 | if (!handles.containsKey(path)) { 36 | throw new ExecutionNoFound(path); 37 | } 38 | var result = handles.get(path).apply(params); 39 | logger.info("View model applied OK --> " + params); 40 | 41 | return (ViewModel) result; 42 | } 43 | 44 | @Override 45 | public List find(String path, Map params) { 46 | if (!handles.containsKey(path)) { 47 | throw new ExecutionNoFound(path); 48 | } 49 | var result = handles.get(path).apply(params); 50 | logger.info("View model list applied OK --> " + params); 51 | 52 | return (List) result; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/bus/notification/ErrorNotification.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.bus.notification; 2 | 3 | import co.com.sofka.infraestructure.DeserializeEventException; 4 | import co.com.sofka.infraestructure.bus.serialize.ErrorNotificationSerializer; 5 | import co.com.sofka.infraestructure.event.ErrorEvent; 6 | import co.com.sofka.infraestructure.event.ErrorEventSerializer; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * The type Error notification. 12 | */ 13 | public class ErrorNotification extends Notification { 14 | 15 | private ErrorNotification(String origin, String typeName, Date occurredOn, String body) { 16 | super(origin, typeName, occurredOn, body); 17 | } 18 | 19 | /** 20 | * Wrap event error notification. 21 | * 22 | * @param origin the origin 23 | * @param errorEvent the error event 24 | * @return the error notification 25 | */ 26 | public static ErrorNotification wrapEvent(String origin, ErrorEvent errorEvent) { 27 | return new ErrorNotification(origin, errorEvent.getClass().getCanonicalName(), 28 | new Date(errorEvent.when.toEpochMilli()), 29 | ErrorEventSerializer.instance().serialize(errorEvent) 30 | ); 31 | } 32 | 33 | /** 34 | * Deserialize event error event. 35 | * 36 | * @return the error event 37 | */ 38 | public ErrorEvent deserializeEvent() { 39 | try { 40 | return ErrorEventSerializer 41 | .instance() 42 | .deserialize(this.getBody(), Class.forName(this.getTypeName())); 43 | } catch (ClassNotFoundException e) { 44 | throw new DeserializeEventException(e.getCause()); 45 | } 46 | } 47 | 48 | /** 49 | * To json string. 50 | * 51 | * @return the string 52 | */ 53 | public String toJson() { 54 | return ErrorNotificationSerializer.instance().serialize(this); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/bus/notification/SuccessNotification.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.bus.notification; 2 | 3 | import co.com.sofka.domain.generic.DomainEvent; 4 | import co.com.sofka.infraestructure.DeserializeEventException; 5 | import co.com.sofka.infraestructure.bus.serialize.SuccessNotificationSerializer; 6 | import co.com.sofka.infraestructure.event.EventSerializer; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * The type Success notification. 12 | */ 13 | public class SuccessNotification extends Notification { 14 | 15 | private SuccessNotification(String origin, String typeName, Date occurredOn, String body) { 16 | super(origin, typeName, occurredOn, body); 17 | } 18 | 19 | /** 20 | * Wrap event success notification. 21 | * 22 | * @param origin the origin 23 | * @param domainEvent the domain event 24 | * @return the success notification 25 | */ 26 | public static SuccessNotification wrapEvent(String origin, DomainEvent domainEvent) { 27 | return new SuccessNotification(origin, domainEvent.getClass().getCanonicalName(), 28 | new Date(domainEvent.when.toEpochMilli()), 29 | EventSerializer.instance().serialize(domainEvent) 30 | ); 31 | } 32 | 33 | /** 34 | * Deserialize event domain event. 35 | * 36 | * @return the domain event 37 | */ 38 | public DomainEvent deserializeEvent() { 39 | try { 40 | return EventSerializer 41 | .instance() 42 | .deserialize(this.getBody(), Class.forName(this.getTypeName())); 43 | } catch (ClassNotFoundException e) { 44 | throw new DeserializeEventException(e.getCause()); 45 | } 46 | } 47 | 48 | /** 49 | * To json string. 50 | * 51 | * @return the string 52 | */ 53 | public String toJson() { 54 | return SuccessNotificationSerializer.instance().serialize(this); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/handle/CommandWrapper.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.handle; 2 | 3 | 4 | import co.com.sofka.domain.generic.Command; 5 | import co.com.sofka.infraestructure.controller.CommandWrapperSerializer; 6 | import com.google.gson.stream.JsonReader; 7 | 8 | import java.io.StringReader; 9 | import java.lang.reflect.Type; 10 | 11 | /** 12 | * The type Command wrapper. 13 | */ 14 | public class CommandWrapper { 15 | private final String aggregateId; 16 | private final String commandType; 17 | private final Object payLoad; 18 | 19 | /** 20 | * Instantiates a new Command wrapper. 21 | * 22 | * @param aggregateId the aggregate id 23 | * @param commandType the command type 24 | * @param payLoad the pay load 25 | */ 26 | public CommandWrapper(String aggregateId, String commandType, Object payLoad) { 27 | this.aggregateId = aggregateId; 28 | this.commandType = commandType; 29 | this.payLoad = payLoad; 30 | } 31 | 32 | /** 33 | * Gets pay load. 34 | * 35 | * @return the pay load 36 | */ 37 | public Object getPayLoad() { 38 | return CommandWrapperSerializer.instance().getGson().toJson(payLoad); 39 | } 40 | 41 | /** 42 | * Gets aggregate id. 43 | * 44 | * @return the aggregate id 45 | */ 46 | public String getAggregateId() { 47 | return aggregateId; 48 | } 49 | 50 | /** 51 | * Gets command type. 52 | * 53 | * @return the command type 54 | */ 55 | public String getCommandType() { 56 | return commandType; 57 | } 58 | 59 | /** 60 | * Value of t. 61 | * 62 | * @param the type parameter 63 | * @param zzz the zzz 64 | * @return the t 65 | */ 66 | public T valueOf(Type zzz) { 67 | JsonReader reader = new JsonReader(new StringReader(getPayLoad().toString())); 68 | reader.setLenient(true); 69 | return CommandWrapperSerializer.instance() 70 | .getGson() 71 | .fromJson(reader, zzz); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/ChangeEventSubscriber.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | import java.util.*; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.concurrent.atomic.AtomicLong; 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * The type Change event subscriber. 10 | */ 11 | public class ChangeEventSubscriber { 12 | private final List changes = new LinkedList<>(); 13 | private final Map versions = new ConcurrentHashMap<>(); 14 | private final Set> observables = new HashSet<>(); 15 | 16 | /** 17 | * Gets changes. 18 | * 19 | * @return the changes 20 | */ 21 | public List getChanges() { 22 | return changes; 23 | } 24 | 25 | /** 26 | * Append change change apply. 27 | * 28 | * @param event the event 29 | * @return the change apply 30 | */ 31 | public final ChangeApply appendChange(DomainEvent event) { 32 | changes.add(event); 33 | return () -> applyEvent(event); 34 | } 35 | 36 | /** 37 | * Subscribe. 38 | * 39 | * @param eventChange the event change 40 | */ 41 | public final void subscribe(EventChange eventChange) { 42 | this.observables.addAll(eventChange.behaviors); 43 | } 44 | 45 | /** 46 | * Apply event. 47 | * 48 | * @param domainEvent the domain event 49 | */ 50 | public final void applyEvent(DomainEvent domainEvent) { 51 | observables.forEach(consumer -> { 52 | try { 53 | consumer.accept(domainEvent); 54 | if (domainEvent instanceof Incremental) { 55 | var map = versions.get(domainEvent.type); 56 | long version = nextVersion(domainEvent, map); 57 | domainEvent.setVersionType(version); 58 | } 59 | } catch (ClassCastException ignored) { 60 | } 61 | }); 62 | } 63 | 64 | private long nextVersion(DomainEvent domainEvent, AtomicLong map) { 65 | if (map == null) { 66 | versions.put(domainEvent.type, new AtomicLong(domainEvent.versionType())); 67 | return domainEvent.versionType(); 68 | } 69 | return versions.get(domainEvent.type).incrementAndGet(); 70 | } 71 | 72 | 73 | /** 74 | * The interface Change apply. 75 | */ 76 | @FunctionalInterface 77 | public interface ChangeApply { 78 | /** 79 | * Apply. 80 | */ 81 | void apply(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/sync/ViewModelExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.sync; 2 | 3 | import co.com.sofka.business.repository.QueryMapperRepository; 4 | import co.com.sofka.business.repository.QueryRepository; 5 | import co.com.sofka.domain.generic.ViewModel; 6 | 7 | import java.util.Map; 8 | import java.util.Objects; 9 | import java.util.function.Function; 10 | 11 | /** 12 | * The type View model executor. 13 | * 14 | * @param the type parameter 15 | */ 16 | public abstract class ViewModelExecutor implements Function, T> { 17 | 18 | private QueryMapperRepository queryMapperRepository; 19 | private QueryRepository queryRepository; 20 | 21 | /** 22 | * Witch query mapper repository view model executor. 23 | * 24 | * @param queryMapperRepository the query mapper repository 25 | * @return the view model executor 26 | */ 27 | public ViewModelExecutor witchQueryMapperRepository(QueryMapperRepository queryMapperRepository) { 28 | this.queryMapperRepository = queryMapperRepository; 29 | return this; 30 | } 31 | 32 | /** 33 | * Witch query repository view model executor. 34 | * 35 | * @param queryRepository the query repository 36 | * @return the view model executor 37 | */ 38 | public ViewModelExecutor witchQueryRepository(QueryRepository queryRepository) { 39 | this.queryRepository = queryRepository; 40 | return this; 41 | } 42 | 43 | /** 44 | * Query mapper repository query mapper repository. 45 | * 46 | * @return the query mapper repository 47 | */ 48 | public QueryMapperRepository queryMapperRepository() { 49 | Objects.requireNonNull(queryMapperRepository, "The query mapper is not defined, consider using the witchQueryMapperRepository method"); 50 | return queryMapperRepository; 51 | } 52 | 53 | /** 54 | * Query repository query repository. 55 | * 56 | * @return the query repository 57 | */ 58 | public QueryRepository queryRepository() { 59 | Objects.requireNonNull(queryRepository, "The query repository is not defined, consider using the witchQueryRepository method"); 60 | return queryRepository; 61 | } 62 | 63 | /** 64 | * Gets data mapped. 65 | * 66 | * @param the type parameter 67 | * @param category the category 68 | * @param classViewModel the class view model 69 | * @return the data mapped 70 | */ 71 | public QueryMapperRepository.ApplyQuery getDataMapped(String category, Class classViewModel) { 72 | return queryMapperRepository() 73 | .getDataMapped(category, classViewModel); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/asyn/UseCaseArgumentExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.asyn; 2 | 3 | import co.com.sofka.business.generic.UseCase; 4 | import co.com.sofka.business.support.RequestCommand; 5 | import co.com.sofka.business.support.ResponseEvents; 6 | import co.com.sofka.domain.generic.Command; 7 | 8 | import java.util.Map; 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | import java.util.function.Consumer; 12 | import java.util.logging.Logger; 13 | 14 | /** 15 | * The type Use case argument executor. 16 | * 17 | * @param the type parameter 18 | */ 19 | public abstract class UseCaseArgumentExecutor extends BaseUseCaseExecutor implements Consumer> { 20 | private static final Logger logger = Logger.getLogger(UseCaseArgumentExecutor.class.getName()); 21 | 22 | /** 23 | * Register use case use case. 24 | * 25 | * @return the use case 26 | */ 27 | public abstract UseCase, ResponseEvents> registerUseCase(); 28 | 29 | 30 | /** 31 | * Register request command request command. 32 | * 33 | * @return the request command 34 | */ 35 | public abstract RequestCommand registerRequestCommand(); 36 | 37 | /** 38 | * Executor. 39 | * 40 | * @param args the args 41 | * @param headers the headers 42 | */ 43 | public void executor(Map args, Map headers) { 44 | Objects.requireNonNull(args, "The command object is required"); 45 | withHeaders(headers); 46 | accept(args); 47 | runUseCase(registerUseCase(), registerRequestCommand()); 48 | } 49 | 50 | /** 51 | * Executor. 52 | * 53 | * @param args the args 54 | */ 55 | public void executor(Map args) { 56 | Objects.requireNonNull(args, "The command object is required"); 57 | accept(args); 58 | runUseCase(registerUseCase(), registerRequestCommand()); 59 | } 60 | 61 | private void runUseCase(UseCase useCase, T request) { 62 | this.request = request; 63 | Optional.ofNullable(repository).ifPresentOrElse(useCase::addRepository, () -> 64 | logger.warning("No repository found for use case") 65 | ); 66 | Optional.ofNullable(serviceBuilder).ifPresent(useCase::addServiceBuilder); 67 | Optional.ofNullable(useCases).ifPresent(useCase::addUseCases); 68 | Optional.ofNullable(aggregateId()).ifPresent(useCase::setIdentify); 69 | 70 | useCaseHandler() 71 | .setIdentifyExecutor(aggregateId()) 72 | .asyncExecutor(useCase, request) 73 | .subscribe(subscriberEvent()); 74 | } 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/asyn/UseCaseCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.asyn; 2 | 3 | import co.com.sofka.business.generic.UseCase; 4 | import co.com.sofka.business.support.RequestCommand; 5 | import co.com.sofka.business.support.ResponseEvents; 6 | import co.com.sofka.domain.generic.Command; 7 | 8 | import java.util.Map; 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | import java.util.function.Consumer; 12 | import java.util.logging.Logger; 13 | 14 | /** 15 | * The type Use case command executor. 16 | * 17 | * @param the type parameter 18 | */ 19 | public abstract class UseCaseCommandExecutor extends BaseUseCaseExecutor implements Consumer { 20 | private static final Logger logger = Logger.getLogger(UseCaseCommandExecutor.class.getName()); 21 | 22 | private C command; 23 | 24 | /** 25 | * Register use case use case. 26 | * 27 | * @return the use case 28 | */ 29 | public abstract UseCase, ResponseEvents> registerUseCase(); 30 | 31 | /** 32 | * Executor. 33 | * 34 | * @param command the command 35 | * @param headers the headers 36 | */ 37 | public void executor(C command, Map headers) { 38 | this.command = Objects.requireNonNull(command, "The command object is required"); 39 | withHeaders(headers); 40 | accept(command); 41 | runUseCase(registerUseCase()); 42 | } 43 | 44 | /** 45 | * Executor. 46 | * 47 | * @param command the command 48 | */ 49 | public void executor(C command) { 50 | this.command = Objects.requireNonNull(command, "The command object is required"); 51 | accept(command); 52 | runUseCase(registerUseCase()); 53 | } 54 | 55 | 56 | /** 57 | * Run use case. 58 | * 59 | * @param the type parameter 60 | * @param the type parameter 61 | * @param useCase the use case 62 | */ 63 | public void runUseCase(UseCase useCase) { 64 | var request = (T) (new RequestCommand<>(command)); 65 | this.request = request; 66 | Optional.ofNullable(repository).ifPresentOrElse(useCase::addRepository, () -> 67 | logger.warning("No repository found for use case") 68 | ); 69 | Optional.ofNullable(serviceBuilder).ifPresent(useCase::addServiceBuilder); 70 | Optional.ofNullable(useCases).ifPresent(useCase::addUseCases); 71 | Optional.ofNullable(aggregateId()).ifPresent(useCase::setIdentify); 72 | 73 | useCaseHandler() 74 | .setIdentifyExecutor(aggregateId()) 75 | .asyncExecutor(useCase, request) 76 | .subscribe(subscriberEvent()); 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /co.com.sofka.application/src/main/java/co/com/sofka/application/init/InitializerCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.application.init; 2 | 3 | import co.com.sofka.business.annotation.CommandHandles; 4 | import co.com.sofka.business.annotation.CommandType; 5 | import co.com.sofka.business.generic.ServiceBuilder; 6 | import co.com.sofka.infraestructure.asyn.SubscriberEvent; 7 | import co.com.sofka.infraestructure.handle.CommandExecutor; 8 | import co.com.sofka.infraestructure.repository.EventStoreRepository; 9 | import io.github.classgraph.*; 10 | 11 | import java.util.logging.Logger; 12 | 13 | import static co.com.sofka.application.init.BaseApplicationExecutor.getAggregate; 14 | import static co.com.sofka.application.init.BaseApplicationExecutor.getCommandType; 15 | 16 | 17 | /** 18 | * The type Initializer command executor. 19 | */ 20 | public abstract class InitializerCommandExecutor extends CommandExecutor { 21 | private static final Logger logger = Logger.getLogger(InitializerCommandExecutor.class.getName()); 22 | /** 23 | * The Subscriber event. 24 | */ 25 | protected final SubscriberEvent subscriberEvent; 26 | /** 27 | * The Repository. 28 | */ 29 | protected final EventStoreRepository repository; 30 | 31 | private final ServiceBuilder serviceBuilder; 32 | 33 | /** 34 | * Instantiates a new Initializer command executor. 35 | * 36 | * @param packageUseCase the package use case 37 | * @param subscriberEvent the subscriber event 38 | * @param repository the repository 39 | */ 40 | protected InitializerCommandExecutor( 41 | String packageUseCase, 42 | SubscriberEvent subscriberEvent, 43 | EventStoreRepository repository) { 44 | this.subscriberEvent = subscriberEvent; 45 | this.repository = repository; 46 | this.serviceBuilder = new ServiceBuilder(); 47 | logger.info("---- Registered Commands -----"); 48 | try (ScanResult result = new ClassGraph() 49 | .enableAllInfo() 50 | .whitelistPackages(packageUseCase) 51 | .scan()) { 52 | ClassInfoList classInfos = result.getClassesWithAnnotation(CommandHandles.class.getName()); 53 | classInfos.parallelStream().forEach(handleClassInfo -> { 54 | AnnotationInfo annotationInfo = handleClassInfo.getAnnotationInfo(CommandType.class.getName()); 55 | String type = getCommandType(packageUseCase, handleClassInfo, annotationInfo); 56 | String aggregate = getAggregate(annotationInfo); 57 | addHandle(handleClassInfo, serviceBuilder, aggregate, type); 58 | }); 59 | } 60 | } 61 | 62 | /** 63 | * Add handle. 64 | * 65 | * @param handleClassInfo the handle class info 66 | * @param aggregate the aggregate 67 | * @param type the type 68 | */ 69 | public abstract void addHandle(ClassInfo handleClassInfo, ServiceBuilder serviceBuilder, String aggregate, String type); 70 | 71 | } 72 | -------------------------------------------------------------------------------- /co.com.sofka.application/src/main/java/co/com/sofka/application/init/InitializerArgumentExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.application.init; 2 | 3 | import co.com.sofka.business.annotation.CommandHandles; 4 | import co.com.sofka.business.annotation.CommandType; 5 | import co.com.sofka.business.generic.ServiceBuilder; 6 | import co.com.sofka.infraestructure.asyn.SubscriberEvent; 7 | import co.com.sofka.infraestructure.handle.ArgumentExecutor; 8 | import co.com.sofka.infraestructure.repository.EventStoreRepository; 9 | import io.github.classgraph.*; 10 | 11 | import java.util.logging.Logger; 12 | 13 | import static co.com.sofka.application.init.BaseApplicationExecutor.getAggregate; 14 | import static co.com.sofka.application.init.BaseApplicationExecutor.getCommandType; 15 | 16 | 17 | /** 18 | * The type Initializer argument executor. 19 | */ 20 | public abstract class InitializerArgumentExecutor extends ArgumentExecutor { 21 | private static final Logger logger = Logger.getLogger(InitializerArgumentExecutor.class.getName()); 22 | /** 23 | * The Subscriber event. 24 | */ 25 | protected final SubscriberEvent subscriberEvent; 26 | /** 27 | * The Repository. 28 | */ 29 | protected final EventStoreRepository repository; 30 | 31 | protected final ServiceBuilder serviceBuilder; 32 | 33 | /** 34 | * Instantiates a new Initializer argument executor. 35 | * 36 | * @param packageUseCase the package use case 37 | * @param subscriberEvent the subscriber event 38 | * @param repository the repository 39 | */ 40 | protected InitializerArgumentExecutor( 41 | String packageUseCase, 42 | SubscriberEvent subscriberEvent, 43 | EventStoreRepository repository) { 44 | this.subscriberEvent = subscriberEvent; 45 | this.repository = repository; 46 | this.serviceBuilder = new ServiceBuilder(); 47 | logger.info("---- Registered Commands -----"); 48 | try (ScanResult result = new ClassGraph() 49 | .enableAllInfo() 50 | .whitelistPackages(packageUseCase) 51 | .scan()) { 52 | ClassInfoList classInfos = result.getClassesWithAnnotation(CommandHandles.class.getName()); 53 | classInfos.parallelStream().forEach(handleClassInfo -> { 54 | AnnotationInfo annotationInfo = handleClassInfo.getAnnotationInfo(CommandType.class.getName()); 55 | String type = getCommandType(packageUseCase, handleClassInfo, annotationInfo); 56 | String aggregate = getAggregate(annotationInfo); 57 | addHandle(handleClassInfo, serviceBuilder, aggregate, type); 58 | }); 59 | } 60 | } 61 | 62 | /** 63 | * Add handle. 64 | * 65 | * @param handleClassInfo the handle class info 66 | * @param aggregate the aggregate 67 | * @param type the type 68 | */ 69 | public abstract void addHandle(ClassInfo handleClassInfo, ServiceBuilder serviceBuilder, String aggregate, String type); 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/handle/ArgumentExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.handle; 2 | 3 | import co.com.sofka.business.asyn.UseCaseArgumentExecutor; 4 | import co.com.sofka.business.generic.ServiceBuilder; 5 | import co.com.sofka.business.generic.UseCase; 6 | 7 | import java.util.Map; 8 | import java.util.Objects; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | import java.util.function.Consumer; 11 | import java.util.logging.Logger; 12 | 13 | 14 | /** 15 | * The type Argument executor. 16 | */ 17 | public abstract class ArgumentExecutor implements CommandHandler> { 18 | 19 | private static final Logger logger = Logger.getLogger(ArgumentExecutor.class.getName()); 20 | /** 21 | * The Handles. 22 | */ 23 | protected Map>> handles = new ConcurrentHashMap<>(); 24 | 25 | private UseCase.RequestValues request; 26 | 27 | 28 | /** 29 | * Put. 30 | * 31 | * @param type the type 32 | * @param consumer the consumer 33 | */ 34 | protected void put(String type, Consumer> consumer) { 35 | handles.put(type, consumer); 36 | } 37 | 38 | @Override 39 | public final void execute(Map args) { 40 | var commandRequired = new CommandWrapper( 41 | args.get("commandType"), 42 | args.get("aggregateId"), 43 | args 44 | ); 45 | 46 | if (Objects.isNull(commandRequired.getCommandType()) || commandRequired.getCommandType().isBlank()) { 47 | throw new IllegalArgumentException("The commandType of the aggregate must be specified"); 48 | } 49 | 50 | if (Objects.isNull(commandRequired.getAggregateId()) || commandRequired.getAggregateId().isBlank()) { 51 | throw new IllegalArgumentException("The aggregateId of the aggregate must be specified"); 52 | } 53 | 54 | var type = commandRequired.getCommandType(); 55 | 56 | if (!handles.containsKey(type)) { 57 | throw new ExecutionNoFound(type); 58 | } 59 | 60 | executeCommand(commandRequired); 61 | } 62 | 63 | private void executeCommand(CommandWrapper commandWrapper) { 64 | logger.info("####### Executor Command #######"); 65 | var consumer = handles.get(commandWrapper.getCommandType()); 66 | var useCaseExecutor = (UseCaseArgumentExecutor) consumer; 67 | 68 | if (!Objects.isNull(commandWrapper.getAggregateId()) && !commandWrapper.getAggregateId().isBlank()) { 69 | useCaseExecutor.withAggregateId(commandWrapper.getAggregateId()); 70 | } 71 | 72 | useCaseExecutor.executor((Map) commandWrapper.getPayLoad()); 73 | request = useCaseExecutor.request(); 74 | } 75 | 76 | /** 77 | * Request use case . request values. 78 | * 79 | * @return the use case . request values 80 | */ 81 | public UseCase.RequestValues request() { 82 | return request; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/handle/CommandExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.handle; 2 | 3 | import co.com.sofka.business.asyn.UseCaseCommandExecutor; 4 | import co.com.sofka.business.generic.UseCase; 5 | import co.com.sofka.domain.generic.Command; 6 | import co.com.sofka.infraestructure.controller.CommandWrapperSerializer; 7 | 8 | import java.lang.reflect.ParameterizedType; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | import java.util.function.Consumer; 13 | import java.util.logging.Logger; 14 | 15 | 16 | /** 17 | * The type Command executor. 18 | */ 19 | public abstract class CommandExecutor implements CommandHandler { 20 | 21 | private static final Logger logger = Logger.getLogger(CommandExecutor.class.getName()); 22 | /** 23 | * The Handles. 24 | */ 25 | protected Map> handles = new ConcurrentHashMap<>(); 26 | 27 | private UseCase.RequestValues request; 28 | 29 | 30 | /** 31 | * Put. 32 | * 33 | * @param type the type 34 | * @param consumer the consumer 35 | */ 36 | protected void put(String type, Consumer consumer) { 37 | handles.put(type, consumer); 38 | } 39 | 40 | @Override 41 | public final void execute(String json) { 42 | var commandRequired = CommandWrapperSerializer.instance().deserialize(json); 43 | 44 | if (Objects.isNull(commandRequired.getCommandType()) || commandRequired.getCommandType().isBlank()) { 45 | throw new IllegalArgumentException("The commandType of the aggregate must be specified"); 46 | } 47 | 48 | if (Objects.isNull(commandRequired.getAggregateId()) || commandRequired.getAggregateId().isBlank()) { 49 | throw new IllegalArgumentException("The aggregateId of the aggregate must be specified"); 50 | } 51 | 52 | var type = commandRequired.getCommandType(); 53 | 54 | if (!handles.containsKey(type)) { 55 | throw new ExecutionNoFound(type); 56 | } 57 | 58 | executeCommand(commandRequired); 59 | } 60 | 61 | private void executeCommand(CommandWrapper commandWrapper) { 62 | logger.info("####### Executor Command #######"); 63 | var consumer = handles.get(commandWrapper.getCommandType()); 64 | var useCaseExecutor = (UseCaseCommandExecutor) consumer; 65 | 66 | if (!Objects.isNull(commandWrapper.getAggregateId()) || !commandWrapper.getAggregateId().isBlank()) { 67 | useCaseExecutor.withAggregateId(commandWrapper.getAggregateId()); 68 | } 69 | 70 | useCaseExecutor.executor(commandWrapper.valueOf( 71 | ((ParameterizedType) useCaseExecutor.getClass().getGenericSuperclass()) 72 | .getActualTypeArguments()[0] 73 | )); 74 | request = useCaseExecutor.request(); 75 | } 76 | 77 | /** 78 | * Request use case . request values. 79 | * 80 | * @return the use case . request values 81 | */ 82 | public UseCase.RequestValues request() { 83 | return request; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /co.com.sofka.application/src/main/java/co/com/sofka/application/ApplicationQueryExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.application; 2 | 3 | import co.com.sofka.business.annotation.QueryHandles; 4 | import co.com.sofka.business.annotation.QueryPath; 5 | import co.com.sofka.business.repository.QueryMapperRepository; 6 | import co.com.sofka.business.sync.ViewModelExecutor; 7 | import co.com.sofka.infraestructure.handle.QueryExecutor; 8 | import io.github.classgraph.*; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.util.Optional; 12 | import java.util.logging.Level; 13 | import java.util.logging.Logger; 14 | 15 | /** 16 | * The type Application query executor. 17 | */ 18 | public class ApplicationQueryExecutor extends QueryExecutor { 19 | private static final Logger logger = Logger.getLogger(ApplicationQueryExecutor.class.getName()); 20 | private final QueryMapperRepository queryMapperRepo; 21 | private final String packageQueries; 22 | 23 | /** 24 | * Instantiates a new Application query executor. 25 | * 26 | * @param packageQueries the package queries 27 | * @param queryMapperRepo the query mapper repo 28 | */ 29 | public ApplicationQueryExecutor( 30 | String packageQueries, 31 | QueryMapperRepository queryMapperRepo) { 32 | this.queryMapperRepo = queryMapperRepo; 33 | this.packageQueries = packageQueries; 34 | initialize(); 35 | } 36 | 37 | private void initialize() { 38 | try (ScanResult result = new ClassGraph() 39 | .enableAllInfo() 40 | .whitelistPackages(packageQueries) 41 | .scan()) { 42 | ClassInfoList classInfos = result.getClassesWithAnnotation(QueryHandles.class.getName()); 43 | classInfos.parallelStream().forEach(handleClassInfo -> { 44 | try { 45 | 46 | AnnotationInfo annotationInfo = handleClassInfo.getAnnotationInfo(QueryPath.class.getName()); 47 | String path = getPath(handleClassInfo, annotationInfo); 48 | 49 | var handle = (ViewModelExecutor) handleClassInfo.loadClass() 50 | .getDeclaredConstructor() 51 | .newInstance(); 52 | put(path, handle.witchQueryMapperRepository(queryMapperRepo)); 53 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 54 | logger.log(Level.SEVERE, "An error caused by the mapper generic and cast view model", e); 55 | } 56 | }); 57 | } 58 | } 59 | 60 | private String getPath(ClassInfo handleClassInfo, AnnotationInfo annotationInfo) { 61 | return Optional.ofNullable(annotationInfo).map(annotation -> { 62 | AnnotationParameterValueList paramVals = annotation.getParameterValues(); 63 | return (String) paramVals.getValue("name"); 64 | }).orElse(handleClassInfo 65 | .loadClass() 66 | .getCanonicalName() 67 | .toLowerCase().replace(packageQueries + ".", "") 68 | ); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /co.com.sofka.application/src/main/java/co/com/sofka/application/ApplicationCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.application; 2 | 3 | import co.com.sofka.application.init.InitializerCommandExecutor; 4 | import co.com.sofka.business.asyn.UseCaseCommandExecutor; 5 | import co.com.sofka.business.generic.ServiceBuilder; 6 | import co.com.sofka.business.generic.UseCaseHandler; 7 | import co.com.sofka.business.repository.DomainEventRepository; 8 | import co.com.sofka.domain.generic.DomainEvent; 9 | import co.com.sofka.infraestructure.asyn.SubscriberEvent; 10 | import co.com.sofka.infraestructure.repository.EventStoreRepository; 11 | import io.github.classgraph.ClassInfo; 12 | 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.util.List; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | import static co.com.sofka.application.init.BaseApplicationExecutor.getServiceBuilder; 19 | 20 | 21 | /** 22 | * The type Application command executor. 23 | */ 24 | public class ApplicationCommandExecutor extends InitializerCommandExecutor { 25 | 26 | private static final Logger logger = Logger.getLogger(ApplicationCommandExecutor.class.getName()); 27 | 28 | /** 29 | * Instantiates a new Application command executor. 30 | * 31 | * @param packageUseCase the package use case 32 | * @param subscriberEvent the subscriber event 33 | * @param repository the repository 34 | */ 35 | public ApplicationCommandExecutor(String packageUseCase, SubscriberEvent subscriberEvent, EventStoreRepository repository) { 36 | super(packageUseCase, subscriberEvent, repository); 37 | } 38 | 39 | @Override 40 | public void addHandle(ClassInfo handleClassInfo, ServiceBuilder serviceBuilder, String aggregate, String type) { 41 | UseCaseCommandExecutor handle; 42 | try { 43 | handle = (UseCaseCommandExecutor) handleClassInfo.loadClass().getDeclaredConstructor().newInstance(); 44 | var baseUseCaseExecutor = handle.withSubscriberEvent(subscriberEvent) 45 | .withUseCaseHandler(UseCaseHandler.getInstance()) 46 | .withServiceBuilder(getServiceBuilder(serviceBuilder, handleClassInfo)) 47 | .withDomainEventRepo(new DomainEventRepository() { 48 | @Override 49 | public List getEventsBy(String aggregateRootId) { 50 | return repository.getEventsBy(aggregate, aggregateRootId); 51 | } 52 | 53 | @Override 54 | public List getEventsBy(String aggregate, String aggregateRootId) { 55 | return repository.getEventsBy(aggregate, aggregateRootId); 56 | } 57 | }); 58 | put(type, (UseCaseCommandExecutor) baseUseCaseExecutor); 59 | var message = String.format("@@@@ %s Registered handle command with type --> %s", aggregate, type); 60 | logger.info(message); 61 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 62 | logger.log(Level.SEVERE, String.format("There is a error inside register command type -->%s", type), e); 63 | } 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /co.com.sofka.application/src/main/java/co/com/sofka/application/ApplicationArgumentExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.application; 2 | 3 | import co.com.sofka.application.init.InitializerArgumentExecutor; 4 | import co.com.sofka.business.asyn.UseCaseArgumentExecutor; 5 | import co.com.sofka.business.generic.ServiceBuilder; 6 | import co.com.sofka.business.generic.UseCaseHandler; 7 | import co.com.sofka.business.repository.DomainEventRepository; 8 | import co.com.sofka.domain.generic.DomainEvent; 9 | import co.com.sofka.infraestructure.asyn.SubscriberEvent; 10 | import co.com.sofka.infraestructure.repository.EventStoreRepository; 11 | import io.github.classgraph.ClassInfo; 12 | 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.util.List; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | import static co.com.sofka.application.init.BaseApplicationExecutor.getServiceBuilder; 19 | 20 | 21 | /** 22 | * The type Application argument executor. 23 | */ 24 | public class ApplicationArgumentExecutor extends InitializerArgumentExecutor { 25 | 26 | private static final Logger logger = Logger.getLogger(ApplicationArgumentExecutor.class.getName()); 27 | 28 | /** 29 | * Instantiates a new Application argument executor. 30 | * 31 | * @param packageUseCase the package use case 32 | * @param subscriberEvent the subscriber event 33 | * @param repository the repository 34 | */ 35 | public ApplicationArgumentExecutor(String packageUseCase, SubscriberEvent subscriberEvent, EventStoreRepository repository) { 36 | super(packageUseCase, subscriberEvent, repository); 37 | } 38 | 39 | @Override 40 | public void addHandle(ClassInfo handleClassInfo, ServiceBuilder serviceBuilder, String aggregate, String type) { 41 | UseCaseArgumentExecutor handle; 42 | try { 43 | handle = (UseCaseArgumentExecutor) handleClassInfo.loadClass().getDeclaredConstructor().newInstance(); 44 | var baseUseCaseExecutor = handle.withSubscriberEvent(subscriberEvent) 45 | .withUseCaseHandler(UseCaseHandler.getInstance()) 46 | .withServiceBuilder(getServiceBuilder(serviceBuilder, handleClassInfo)) 47 | .withDomainEventRepo(new DomainEventRepository() { 48 | @Override 49 | public List getEventsBy(String aggregateRootId) { 50 | return repository.getEventsBy(aggregate, aggregateRootId); 51 | } 52 | 53 | @Override 54 | public List getEventsBy(String aggregate, String aggregateRootId) { 55 | return repository.getEventsBy(aggregate, aggregateRootId); 56 | } 57 | }); 58 | put(type, (UseCaseArgumentExecutor) baseUseCaseExecutor); 59 | var message = String.format("@@@@ %s Registered handle command with type --> %s", aggregate, type); 60 | logger.info(message); 61 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 62 | logger.log(Level.SEVERE, String.format("There is a error inside register command type -->%s", type), e); 63 | } 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/asyn/SubscriberEvent.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.asyn; 2 | 3 | 4 | import co.com.sofka.business.generic.UnexpectedException; 5 | import co.com.sofka.domain.generic.DomainEvent; 6 | import co.com.sofka.infraestructure.bus.EventBus; 7 | import co.com.sofka.infraestructure.event.ErrorEvent; 8 | import co.com.sofka.infraestructure.repository.EventStoreRepository; 9 | import co.com.sofka.infraestructure.store.StoredEvent; 10 | 11 | import java.util.Objects; 12 | import java.util.Optional; 13 | import java.util.concurrent.Flow; 14 | import java.util.logging.Logger; 15 | 16 | 17 | /** 18 | * The type Subscriber event. 19 | */ 20 | public class SubscriberEvent implements Flow.Subscriber { 21 | 22 | private static final Logger logger = Logger.getLogger(SubscriberEvent.class.getName()); 23 | private final EventStoreRepository repository; 24 | private final EventBus eventBus; 25 | private Flow.Subscription subscription; 26 | 27 | /** 28 | * Instantiates a new Subscriber event. 29 | * 30 | * @param repository the repository 31 | * @param eventBus the event bus 32 | */ 33 | public SubscriberEvent(EventStoreRepository repository, EventBus eventBus) { 34 | this.repository = repository; 35 | this.eventBus = eventBus; 36 | } 37 | 38 | /** 39 | * Instantiates a new Subscriber event. 40 | * 41 | * @param repository the repository 42 | */ 43 | public SubscriberEvent(EventStoreRepository repository) { 44 | this(repository, null); 45 | } 46 | 47 | /** 48 | * Instantiates a new Subscriber event. 49 | */ 50 | public SubscriberEvent() { 51 | this(null, null); 52 | } 53 | 54 | @Override 55 | public void onSubscribe(Flow.Subscription subscription) { 56 | this.subscription = subscription; 57 | subscription.request(100); 58 | } 59 | 60 | @Override 61 | public final void onNext(DomainEvent event) { 62 | Optional.ofNullable(eventBus).ifPresentOrElse(bus -> 63 | bus.publish(event) 64 | , () -> 65 | logger.warning("No EVENT BUS configured") 66 | ); 67 | 68 | Optional.ofNullable(repository).ifPresentOrElse(repo -> { 69 | StoredEvent storedEvent = StoredEvent.wrapEvent(event); 70 | Optional.ofNullable(event.aggregateRootId()).ifPresent(aggregateId -> { 71 | if (Objects.nonNull(event.getAggregateName()) && !event.getAggregateName().isBlank()) { 72 | repo.saveEvent(event.getAggregateName(), aggregateId, storedEvent); 73 | } 74 | }); 75 | }, () -> 76 | logger.warning("No REPOSITORY configured") 77 | ); 78 | subscription.request(100); 79 | } 80 | 81 | @Override 82 | public void onError(Throwable throwable) { 83 | Optional.ofNullable(eventBus).ifPresent(bus -> { 84 | var identify = ((UnexpectedException) throwable).getIdentify(); 85 | var event = new ErrorEvent(identify, throwable); 86 | bus.publishError(event); 87 | }); 88 | subscription.cancel(); 89 | } 90 | 91 | @Override 92 | public void onComplete() { 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /co.com.sofka.application/src/main/java/co/com/sofka/application/init/BaseApplicationExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.application.init; 2 | 3 | import co.com.sofka.application.ServiceBuildException; 4 | import co.com.sofka.business.annotation.ExtensionService; 5 | import co.com.sofka.business.generic.ServiceBuilder; 6 | import io.github.classgraph.AnnotationClassRef; 7 | import io.github.classgraph.AnnotationInfo; 8 | import io.github.classgraph.AnnotationParameterValueList; 9 | import io.github.classgraph.ClassInfo; 10 | 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.util.Optional; 13 | 14 | /** 15 | * The type Base application executor. 16 | */ 17 | public class BaseApplicationExecutor { 18 | private BaseApplicationExecutor() { 19 | 20 | } 21 | 22 | /** 23 | * Get command type string. 24 | * 25 | * @param packageUseCase the package use case 26 | * @param handleClassInfo the handle class info 27 | * @param annotationInfo the annotation info 28 | * @return the string 29 | */ 30 | public static String getCommandType(String packageUseCase, ClassInfo handleClassInfo, AnnotationInfo annotationInfo) { 31 | return Optional.ofNullable(annotationInfo).map(annotation -> { 32 | AnnotationParameterValueList paramVals = annotation.getParameterValues(); 33 | return (String) paramVals.getValue("name"); 34 | }).orElse(handleClassInfo 35 | .loadClass() 36 | .getCanonicalName() 37 | .toLowerCase().replace(packageUseCase + ".", "") 38 | ); 39 | } 40 | 41 | /** 42 | * Gets aggregate. 43 | * 44 | * @param annotationInfo the annotation info 45 | * @return the aggregate 46 | */ 47 | public static String getAggregate(AnnotationInfo annotationInfo) { 48 | return Optional.ofNullable(annotationInfo).map(annotation -> { 49 | AnnotationParameterValueList paramVals = annotation.getParameterValues(); 50 | return (String) paramVals.getValue("aggregate"); 51 | }).orElse("default"); 52 | } 53 | 54 | /** 55 | * Gets service builder. 56 | * 57 | * @param serviceBuilder the service builder 58 | * @param handleClassInfo the handle class info 59 | * @return the service builder 60 | */ 61 | public static ServiceBuilder getServiceBuilder(ServiceBuilder serviceBuilder, ClassInfo handleClassInfo) { 62 | AnnotationInfo annotationInfo = handleClassInfo.getAnnotationInfo(ExtensionService.class.getName()); 63 | return Optional.ofNullable(annotationInfo).map(annotation -> { 64 | AnnotationParameterValueList paramVals = annotation.getParameterValues(); 65 | var list = (Object[]) paramVals.getValue("value"); 66 | for (Object o : list) { 67 | try { 68 | var ref = (AnnotationClassRef) o; 69 | if (!serviceBuilder.exist(ref.loadClass())) { 70 | serviceBuilder.addService(ref.loadClass().getDeclaredConstructor().newInstance()); 71 | } 72 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 73 | throw new ServiceBuildException(e); 74 | } 75 | } 76 | return serviceBuilder; 77 | }).orElse(serviceBuilder); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /co.com.sofka.infrastructure/src/main/java/co/com/sofka/infraestructure/store/StoredEvent.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.infraestructure.store; 2 | 3 | import co.com.sofka.domain.generic.DomainEvent; 4 | import co.com.sofka.infraestructure.DeserializeEventException; 5 | import co.com.sofka.infraestructure.event.EventSerializer; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * The type Stored event. 11 | */ 12 | public final class StoredEvent { 13 | 14 | private String eventBody; 15 | private Date occurredOn; 16 | private String typeName; 17 | 18 | /** 19 | * Instantiates a new Stored event. 20 | */ 21 | public StoredEvent() { 22 | } 23 | 24 | /** 25 | * Instantiates a new Stored event. 26 | * 27 | * @param typeName the type name 28 | * @param occurredOn the occurred on 29 | * @param eventBody the event body 30 | */ 31 | public StoredEvent(String typeName, Date occurredOn, String eventBody) { 32 | this.setEventBody(eventBody); 33 | this.setOccurredOn(occurredOn); 34 | this.setTypeName(typeName); 35 | } 36 | 37 | /** 38 | * Wrap event stored event. 39 | * 40 | * @param domainEvent the domain event 41 | * @return the stored event 42 | */ 43 | public static StoredEvent wrapEvent(DomainEvent domainEvent) { 44 | return new StoredEvent(domainEvent.getClass().getCanonicalName(), 45 | new Date(), 46 | EventSerializer.instance().serialize(domainEvent) 47 | ); 48 | } 49 | 50 | /** 51 | * Gets event body. 52 | * 53 | * @return the event body 54 | */ 55 | public String getEventBody() { 56 | return eventBody; 57 | } 58 | 59 | /** 60 | * Sets event body. 61 | * 62 | * @param eventBody the event body 63 | */ 64 | public void setEventBody(String eventBody) { 65 | this.eventBody = eventBody; 66 | } 67 | 68 | /** 69 | * Gets occurred on. 70 | * 71 | * @return the occurred on 72 | */ 73 | public Date getOccurredOn() { 74 | return occurredOn; 75 | } 76 | 77 | /** 78 | * Sets occurred on. 79 | * 80 | * @param occurredOn the occurred on 81 | */ 82 | public void setOccurredOn(Date occurredOn) { 83 | this.occurredOn = occurredOn; 84 | } 85 | 86 | /** 87 | * Gets type name. 88 | * 89 | * @return the type name 90 | */ 91 | public String getTypeName() { 92 | return typeName; 93 | } 94 | 95 | /** 96 | * Sets type name. 97 | * 98 | * @param typeName the type name 99 | */ 100 | public void setTypeName(String typeName) { 101 | this.typeName = typeName; 102 | } 103 | 104 | /** 105 | * Deserialize event domain event. 106 | * 107 | * @return the domain event 108 | */ 109 | public DomainEvent deserializeEvent() { 110 | try { 111 | return EventSerializer 112 | .instance() 113 | .deserialize(this.getEventBody(), Class.forName(this.getTypeName())); 114 | } catch (ClassNotFoundException e) { 115 | throw new DeserializeEventException(e.getCause()); 116 | } 117 | } 118 | 119 | @Override 120 | public String toString() { 121 | return StoredEventSerializer.instance().serialize(this); 122 | } 123 | 124 | 125 | } 126 | -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/AggregateEvent.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | 4 | import java.util.List; 5 | import java.util.Optional; 6 | import java.util.stream.Stream; 7 | 8 | /** 9 | * The type Aggregate event. 10 | * 11 | * @param the type parameter 12 | */ 13 | public abstract class AggregateEvent extends AggregateRoot { 14 | 15 | 16 | private final ChangeEventSubscriber changeEventSubscriber; 17 | 18 | /** 19 | * Instantiates a new Aggregate event. 20 | * 21 | * @param entityId the entity id 22 | */ 23 | public AggregateEvent(T entityId) { 24 | super(entityId); 25 | changeEventSubscriber = new ChangeEventSubscriber(); 26 | } 27 | 28 | 29 | /** 30 | * Gets uncommitted changes. 31 | * 32 | * @return the uncommitted changes 33 | */ 34 | public List getUncommittedChanges() { 35 | return List.copyOf(changeEventSubscriber.getChanges()); 36 | } 37 | 38 | /** 39 | * Append change change event subscriber . change apply. 40 | * 41 | * @param event the event 42 | * @return the change event subscriber . change apply 43 | */ 44 | protected ChangeEventSubscriber.ChangeApply appendChange(DomainEvent event) { 45 | var nameClass = entityId.getClass().getSimpleName(); 46 | var aggregate = nameClass.replaceAll("(Identity|Id|ID)", "").toLowerCase(); 47 | event.setAggregateName(aggregate); 48 | event.setAggregateRootId(entityId.value()); 49 | return changeEventSubscriber.appendChange(event); 50 | } 51 | 52 | /** 53 | * Subscribe. 54 | * 55 | * @param eventChange the event change 56 | */ 57 | protected final void subscribe(EventChange eventChange) { 58 | changeEventSubscriber.subscribe(eventChange); 59 | } 60 | 61 | /** 62 | * Apply event. 63 | * 64 | * @param domainEvent the domain event 65 | */ 66 | protected void applyEvent(DomainEvent domainEvent) { 67 | changeEventSubscriber.applyEvent(domainEvent); 68 | } 69 | 70 | /** 71 | * Mark changes as committed. 72 | */ 73 | public void markChangesAsCommitted() { 74 | changeEventSubscriber.getChanges().clear(); 75 | } 76 | 77 | /** 78 | * Refund event. 79 | */ 80 | public void refundEvent() { 81 | changeEventSubscriber.getChanges().clear(); 82 | } 83 | 84 | 85 | /** 86 | * Find event uncommitted optional. 87 | * 88 | * @param the type parameter 89 | * @param event the event 90 | * @return the optional 91 | */ 92 | public Optional findEventUncommitted(Class event) { 93 | return changeEventSubscriber.getChanges().stream() 94 | .filter(event::isInstance).map(e -> (E) e) 95 | .findFirst(); 96 | } 97 | 98 | /** 99 | * Find all event uncommitted stream. 100 | * 101 | * @param the type parameter 102 | * @param event the event 103 | * @return the stream 104 | */ 105 | public Stream findAllEventUncommitted(Class event) { 106 | return changeEventSubscriber.getChanges().stream() 107 | .filter(event::isInstance).map(e -> (E) e); 108 | } 109 | 110 | @Override 111 | public boolean equals(Object object) { 112 | return super.equals(object); 113 | } 114 | 115 | @Override 116 | public int hashCode() { 117 | return super.hashCode(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/generic/UseCaseHandler.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.generic; 2 | 3 | 4 | import co.com.sofka.business.asyn.PublisherEvent; 5 | import co.com.sofka.business.support.ResponseEvents; 6 | import co.com.sofka.domain.generic.DomainEvent; 7 | 8 | import java.util.Optional; 9 | import java.util.concurrent.Flow; 10 | 11 | /** 12 | * The type Use case handler. 13 | */ 14 | public class UseCaseHandler { 15 | 16 | private static UseCaseHandler instance; 17 | private String identifyExecutor = null; 18 | 19 | private UseCaseHandler() { 20 | } 21 | 22 | /** 23 | * Gets instance. 24 | * 25 | * @return the instance 26 | */ 27 | public static synchronized UseCaseHandler getInstance() { 28 | if (instance == null) { 29 | instance = new UseCaseHandler(); 30 | } 31 | return instance; 32 | } 33 | 34 | /** 35 | * Async executor function subscriber. 36 | * 37 | * @param the type parameter 38 | * @param the type parameter 39 | * @param useCase the use case 40 | * @param values the values 41 | * @return the function subscriber 42 | */ 43 | @SuppressWarnings("unchecked") 44 | public FunctionSubscriber asyncExecutor( 45 | final UseCase useCase, 46 | final T values 47 | ) { 48 | return UseCaseReplyUtil.retry(() -> subscriber -> { 49 | try (PublisherEvent publisher = new PublisherEvent()) { 50 | publisher.subscribe(subscriber); 51 | useCase.setRequest(values); 52 | useCase.setIdentify(identifyExecutor()); 53 | useCase.setUseCaseCallback((UseCase.UseCaseFormat) publisher); 54 | useCase.run(); 55 | } 56 | }, 5, ReplyBusinessException.class); 57 | } 58 | 59 | /** 60 | * Sync executor optional. 61 | * 62 | * @param the type parameter 63 | * @param the type parameter 64 | * @param useCase the use case 65 | * @param values the values 66 | * @return the optional 67 | */ 68 | @SuppressWarnings("unchecked") 69 | public Optional syncExecutor( 70 | final UseCase useCase, 71 | final T values 72 | ) { 73 | return UseCaseReplyUtil.retry(() -> { 74 | UseCaseResponse useCaseResponse = new UseCaseResponse<>(); 75 | useCase.setRequest(values); 76 | useCase.setIdentify(identifyExecutor()); 77 | useCase.setUseCaseCallback(useCaseResponse); 78 | useCase.run(); 79 | if (useCaseResponse.hasError()) { 80 | throw useCaseResponse.exception; 81 | } 82 | return Optional.ofNullable(useCaseResponse.response); 83 | }, 5, ReplyBusinessException.class); 84 | 85 | } 86 | 87 | /** 88 | * Identify executor string. 89 | * 90 | * @return the string 91 | */ 92 | public String identifyExecutor() { 93 | return identifyExecutor; 94 | } 95 | 96 | /** 97 | * Sets identify executor. 98 | * 99 | * @param identifyExecutor the identify executor 100 | * @return the identify executor 101 | */ 102 | public UseCaseHandler setIdentifyExecutor(String identifyExecutor) { 103 | this.identifyExecutor = identifyExecutor; 104 | return this; 105 | } 106 | 107 | /** 108 | * The interface Function subscriber. 109 | */ 110 | @FunctionalInterface 111 | public interface FunctionSubscriber { 112 | /** 113 | * Subscribe. 114 | * 115 | * @param subscriber the subscriber 116 | */ 117 | void subscribe(Flow.Subscriber subscriber); 118 | } 119 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | co.com.sofka 5 | domain-driven-design 6 | Style project DDD as Sofka Make it 7 | pom 8 | 1.5.0 9 | ddd-generic-java 10 | https://sofka.com.co 11 | 12 | 13 | The Apache Software License, Version 2.0 14 | http://www.apache.org/licenses/LICENSE-2.0.txt 15 | 16 | 17 | 18 | 19 | Raul A. Alzate 20 | raul.alzate@sofka.com.co 21 | Sofka 22 | https://www.sofka.com.co 23 | 24 | 25 | 26 | 27 | scm:git:git://github.com/Sofka-XT/ddd-generic-java.git 28 | scm:git:ssh://github.com/Sofka-XT/ddd-generic-java 29 | https://github.com/Sofka-XT/ddd-generic-java/tree/master 30 | 31 | 32 | 33 | 34 | ossrh 35 | https://oss.sonatype.org/content/repositories/snapshots 36 | 37 | 38 | ossrh 39 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 40 | 41 | 42 | 43 | 44 | co.com.sofka.domain 45 | co.com.sofka.business 46 | co.com.sofka.infrastructure 47 | co.com.sofka.application 48 | 49 | 50 | 51 | UTF-8 52 | 11 53 | 11 54 | 1.5.0 55 | 56 | 57 | 58 | 59 | 60 | 61 | maven-deploy-plugin 62 | 2.8.2 63 | 64 | true 65 | 66 | 67 | 68 | 69 | 70 | 71 | maven-deploy-plugin 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-compiler-plugin 76 | 3.8.1 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-source-plugin 81 | 3.2.1 82 | 83 | 84 | attach-sources 85 | 86 | jar-no-fork 87 | 88 | 89 | 90 | 91 | 92 | org.apache.maven.plugins 93 | maven-gpg-plugin 94 | 1.6 95 | 96 | 97 | sign-artifacts 98 | verify 99 | 100 | sign 101 | 102 | 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-javadoc-plugin 108 | 3.2.0 109 | 110 | 111 | attach-javadocs 112 | 113 | jar 114 | 115 | 116 | 117 | 118 | 119 | maven-surefire-plugin 120 | 2.22.2 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /co.com.sofka.domain/src/main/java/co/com/sofka/domain/generic/DomainEvent.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.domain.generic; 2 | 3 | import java.io.Serializable; 4 | import java.time.Instant; 5 | import java.util.Objects; 6 | import java.util.UUID; 7 | 8 | /** 9 | * The type Domain event. 10 | */ 11 | public abstract class DomainEvent implements Serializable { 12 | /** 13 | * The When. 14 | */ 15 | public final Instant when; 16 | /** 17 | * The Uuid. 18 | */ 19 | public final UUID uuid; 20 | /** 21 | * The Type. 22 | */ 23 | public final String type; 24 | private String aggregateRootId; 25 | private String aggregateParentId; 26 | private String aggregate; 27 | private Long versionType; 28 | 29 | /** 30 | * Instantiates a new Domain event. 31 | * 32 | * @param type the type 33 | * @param aggregateRootId the aggregate root id 34 | * @param aggregateParentId the aggregate parent id 35 | * @param uuid the uuid 36 | */ 37 | public DomainEvent(final String type, String aggregateRootId, String aggregateParentId, UUID uuid) { 38 | this.type = type; 39 | this.aggregateRootId = aggregateRootId; 40 | this.aggregateParentId = aggregateParentId; 41 | this.aggregate = "default"; 42 | this.when = Instant.now(); 43 | this.uuid = uuid; 44 | this.versionType = 1L; 45 | } 46 | 47 | 48 | /** 49 | * Instantiates a new Domain event. 50 | * 51 | * @param type the type 52 | * @param aggregateRootId the aggregate root id 53 | * @param uuid the uuid 54 | */ 55 | public DomainEvent(final String type, String aggregateRootId, UUID uuid) { 56 | this.type = type; 57 | this.aggregateRootId = aggregateRootId; 58 | this.aggregate = "default"; 59 | this.when = Instant.now(); 60 | this.uuid = uuid; 61 | this.versionType = 1L; 62 | } 63 | 64 | 65 | /** 66 | * Instantiates a new Domain event. 67 | * 68 | * @param type the type 69 | * @param uuid the uuid 70 | */ 71 | public DomainEvent(final String type, UUID uuid) { 72 | this(type, null, null, uuid); 73 | } 74 | 75 | /** 76 | * Instantiates a new Domain event. 77 | * 78 | * @param type the type 79 | */ 80 | public DomainEvent(final String type) { 81 | this(type, null, null, UUID.randomUUID()); 82 | } 83 | 84 | /** 85 | * Version type long. 86 | * 87 | * @return the long 88 | */ 89 | public Long versionType() { 90 | return versionType; 91 | } 92 | 93 | /** 94 | * Sets version type. 95 | * 96 | * @param versionType the version type 97 | */ 98 | public void setVersionType(Long versionType) { 99 | this.versionType = versionType; 100 | } 101 | 102 | /** 103 | * Aggregate root id string. 104 | * 105 | * @return the string 106 | */ 107 | public String aggregateRootId() { 108 | return aggregateRootId; 109 | } 110 | 111 | /** 112 | * Aggregate parent id string. 113 | * 114 | * @return the string 115 | */ 116 | public String aggregateParentId() { 117 | return aggregateParentId; 118 | } 119 | 120 | /** 121 | * Sets aggregate root id. 122 | * 123 | * @param aggregateRootId the aggregate root id 124 | */ 125 | public void setAggregateRootId(String aggregateRootId) { 126 | this.aggregateRootId = Objects.requireNonNull(aggregateRootId, "The aggregateRootId cannot be a value null"); 127 | } 128 | 129 | 130 | /** 131 | * Sets aggregate parent id. 132 | * 133 | * @param aggregateParentId the aggregate parent id 134 | */ 135 | public void setAggregateParentId(String aggregateParentId) { 136 | this.aggregateParentId = Objects.requireNonNull(aggregateParentId, "The aggregateParentId cannot be a value null"); 137 | } 138 | 139 | /** 140 | * Gets aggregate name. 141 | * 142 | * @return the aggregate name 143 | */ 144 | public String getAggregateName() { 145 | return aggregate; 146 | } 147 | 148 | /** 149 | * Sets aggregate name. 150 | * 151 | * @param aggregate the aggregate 152 | */ 153 | public void setAggregateName(String aggregate) { 154 | this.aggregate = aggregate; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /docs/code/domain-model.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | ' hide the spot 4 | hide circle 5 | 6 | entity "[AR] Dojo" as Dojo { 7 | [ID] id 8 | [E] coach 9 | [VO] dataInfo 10 | [VO] status 11 | [List] rules 12 | [VO] location 13 | [VO] groupGit 14 | -- 15 | **createdGitGroup** 16 | **changeStatus** 17 | **updateDataInfo** 18 | **addRules** 19 | **removeRules** 20 | **updateLocation** 21 | **updateOpeningHours** 22 | **updateUrlMeet** 23 | **updateCoachName** 24 | **updateCoachSpecialty** 25 | **addAccomplishmentToCoach** 26 | **removeAccomplishmentToCoach** 27 | 28 | -- 29 | CreatedDojo 30 | AssociatedCoach 31 | CreatedDojo 32 | AssociatedCoach 33 | CreatedGitGroup 34 | ChangedStatus 35 | UpdatedDateInfo 36 | AddedDojoRule 37 | RemovedDojoRule 38 | UpdatedLocation 39 | UpdatedOpeningHours 40 | UpdatedUrlMeet 41 | UpdatedCoachName 42 | UpdatedCoachSpecialty 43 | AddedAccomplishmentToCoach 44 | RemovedAccomplishmentToCoach 45 | } 46 | 47 | class "[VO] DataInfo" as DataInfo{ 48 | [String] name 49 | [String] legend 50 | } 51 | class "[Enum] Status" as Status{ 52 | CLOSED 53 | CANCELLED 54 | OPEN 55 | } 56 | class "[VO] Location" as Location { 57 | [String] urlMeet 58 | [String] location 59 | [String] description 60 | [VO] openingHours 61 | } 62 | class "[VO] OpeningHours" as OpeningHours { 63 | [Integer] hourStart 64 | [Integer] hourEnd 65 | [Enum] frequency 66 | } 67 | class "[Enum] Frequency" as Frequency{ 68 | MONTHLY 69 | BIWEEKLY 70 | WEEKLY 71 | EACH_THREE_DAYS 72 | WEEKENDS 73 | EVERY_TWO_DAYS 74 | } 75 | entity "[E] Couch" as Couch { 76 | [ID] id 77 | [VO] name 78 | [VO] personId 79 | [VO] specialty 80 | [VO] email 81 | [List] accomplishment 82 | -- 83 | **updateName** 84 | **updateSpecialty** 85 | **addAccomplishment** 86 | **removeAccomplishment** 87 | } 88 | class "[VO] Accomplishment" as Accomplishment { 89 | [String] label 90 | [Int] point 91 | [Date] date 92 | } 93 | entity "[AR] Clan" as Clan { 94 | [ID] id 95 | [E] members 96 | [VO] name 97 | [VO] color 98 | [VO] groupGit 99 | -- 100 | **createGitGroup** 101 | **addNewMember** 102 | **addGitMember** 103 | **createGitMember** 104 | **revokeMember** 105 | **removeGitMember** 106 | **updateName** 107 | **updateMemberName** 108 | **addScoreToMember** 109 | -- 110 | AddedScoreOfMember 111 | UpdatedMember 112 | UpdatedName 113 | RevokedMember 114 | RemoveGitMember 115 | CreatedGitMember 116 | AddedGitMember 117 | AddedMember 118 | CreatedGitGroup 119 | CreatedClan 120 | } 121 | 122 | entity "[E] Member" as Member { 123 | [ID] id 124 | [VO] name 125 | [VO] gender 126 | [Boolean] isOwner 127 | [VO] personId 128 | [VO] memberGit 129 | [List] scores 130 | -- 131 | **addScore** 132 | **updateName** 133 | **updateEmail** 134 | **updateGender** 135 | **updateMemberGit** 136 | } 137 | 138 | class "[VO] Score" as Score { 139 | [Int] point 140 | [VO] dojoId 141 | [Date] date 142 | } 143 | 144 | entity "[AR] Challenge" as Challenge{ 145 | [ID] id 146 | [VO] name 147 | [Set] clanIds 148 | [VO] dojoId 149 | [List] katas 150 | [VO] assessment 151 | [Boolean] isRevoked 152 | [VO] durationDays 153 | -- 154 | **updateChallenge** 155 | **addNewKata** 156 | **revoke** 157 | **assignRepoUrl** 158 | **addExercise** 159 | **subscribeClan** 160 | **unsubscribeClan** 161 | **updateChallengeName** 162 | **updateChallengeDuration** 163 | **updateKata** 164 | **deleteKata** 165 | -- 166 | CreatedChallenge 167 | CreatedChallenge 168 | AddedKata 169 | ChallengeRevoked 170 | UrlRepoAssigned 171 | AddedExercise 172 | SubscribedClan 173 | UnsubscribedClan 174 | UpdatedChallengeName 175 | UpdatedChallengeDuration 176 | UpdatedKata 177 | DeletedKata 178 | } 179 | 180 | entity "[E] Kata" as Kata { 181 | [ID] id 182 | [VO] purpose 183 | [Int] limitOfTime 184 | [List] exercises 185 | -- 186 | **changeLimitOfTime** 187 | **changePurpose** 188 | **removeExercise** 189 | **addNewExercise** 190 | } 191 | 192 | Challenge }o.. Dojo 193 | Challenge }|.. Kata 194 | OpeningHours ||-- Frequency 195 | Location ||-- OpeningHours 196 | Dojo ||-- Location 197 | Dojo ||-- DataInfo 198 | Dojo ||-- Status 199 | Couch }|-- Accomplishment 200 | Dojo ||-- Couch 201 | Dojo }o.. Clan 202 | Clan }|-- Member 203 | Member }|-- Score 204 | 205 | @enduml -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/generic/UseCaseReplyUtil.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.generic; 2 | 3 | 4 | import java.util.Arrays; 5 | import java.util.function.Supplier; 6 | import java.util.logging.Logger; 7 | 8 | /** 9 | * The type Use case reply util. 10 | */ 11 | public final class UseCaseReplyUtil { 12 | private static final Logger logger = Logger.getLogger(UseCaseReplyUtil.class.getName()); 13 | 14 | private UseCaseReplyUtil() { 15 | } 16 | 17 | /** 18 | * Retry t. 19 | * 20 | * @param the type parameter 21 | * @param function the function 22 | * @param maxRetries the max retries 23 | * @param timeoutExceptionClasses the timeout exception classes 24 | * @return the t 25 | */ 26 | public static T retry(Supplier function, int maxRetries, Class... timeoutExceptionClasses) { 27 | timeoutExceptionClasses = timeoutExceptionClasses.length == 0 ? new Class[]{Exception.class} : timeoutExceptionClasses; 28 | int retryCounter = 0; 29 | Exception lastException = null; 30 | while (retryCounter < maxRetries) { 31 | try { 32 | return function.get(); 33 | } catch (RuntimeException e) { 34 | UtilReply utilReply = new UtilReply(maxRetries, retryCounter, e, timeoutExceptionClasses).invoke(); 35 | if (utilReply.is()) break; 36 | retryCounter = utilReply.getRetryCounter(); 37 | lastException = utilReply.getLastException(); 38 | } 39 | } 40 | throw lastException != null ? 41 | ((RuntimeException) lastException) : 42 | new RuntimeException(); 43 | } 44 | 45 | 46 | private static class UtilReply { 47 | private final int maxRetries; 48 | private final Class[] timeoutExceptionClasses; 49 | private final Exception exception; 50 | private boolean myResult; 51 | private int retryCounter; 52 | private Exception lastException; 53 | 54 | 55 | /** 56 | * Instantiates a new Util reply. 57 | * 58 | * @param maxRetries the max retries 59 | * @param retryCounter the retry counter 60 | * @param exception the exception 61 | * @param timeoutExceptionClasses the timeout exception classes 62 | */ 63 | @SafeVarargs 64 | public UtilReply(int maxRetries, int retryCounter, Exception exception, Class... timeoutExceptionClasses) { 65 | this.maxRetries = maxRetries; 66 | this.retryCounter = retryCounter; 67 | this.exception = exception; 68 | this.timeoutExceptionClasses = timeoutExceptionClasses; 69 | } 70 | 71 | /** 72 | * Is boolean. 73 | * 74 | * @return the boolean 75 | */ 76 | boolean is() { 77 | return myResult; 78 | } 79 | 80 | /** 81 | * Gets retry counter. 82 | * 83 | * @return the retry counter 84 | */ 85 | public int getRetryCounter() { 86 | return retryCounter; 87 | } 88 | 89 | /** 90 | * Gets last exception. 91 | * 92 | * @return the last exception 93 | */ 94 | public Exception getLastException() { 95 | return lastException; 96 | } 97 | 98 | /** 99 | * Invoke util reply. 100 | * 101 | * @return the util reply 102 | */ 103 | public UtilReply invoke() { 104 | lastException = exception; 105 | if (Arrays.stream(timeoutExceptionClasses).noneMatch(tClass -> 106 | tClass.isAssignableFrom(exception.getClass()) 107 | )) throw (RuntimeException) lastException; 108 | else { 109 | retryCounter++; 110 | logger.info("-- Reply(" + retryCounter + "/" + maxRetries + ") => " + exception.getMessage()); 111 | try { 112 | Thread.sleep(1000); 113 | } catch (InterruptedException interruptedException) { 114 | interruptedException.printStackTrace(); 115 | } 116 | if (retryCounter >= maxRetries) { 117 | myResult = true; 118 | return this; 119 | } 120 | } 121 | myResult = false; 122 | return this; 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/sync/UseCaseExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.sync; 2 | 3 | import co.com.sofka.business.generic.ServiceBuilder; 4 | import co.com.sofka.business.generic.UseCase; 5 | import co.com.sofka.business.generic.UseCaseHandler; 6 | import co.com.sofka.business.repository.DomainEventRepository; 7 | 8 | import java.util.*; 9 | import java.util.function.Function; 10 | import java.util.logging.Logger; 11 | 12 | /** 13 | * The type Use case executor. 14 | * 15 | * @param the type parameter 16 | * @param the type parameter 17 | */ 18 | public abstract class UseCaseExecutor implements Function { 19 | private static final Logger logger = Logger.getLogger(UseCaseExecutor.class.getName()); 20 | 21 | private DomainEventRepository repository; 22 | private String aggregateId; 23 | private UseCaseHandler useCaseHandler; 24 | private ServiceBuilder serviceBuilder; 25 | private Map headers; 26 | private Set useCases; 27 | 28 | /** 29 | * With service builder use case executor. 30 | * 31 | * @param serviceBuilder the service builder 32 | * @return the use case executor 33 | */ 34 | public UseCaseExecutor withServiceBuilder(ServiceBuilder serviceBuilder) { 35 | this.serviceBuilder = Objects.requireNonNull(serviceBuilder); 36 | return this; 37 | } 38 | 39 | 40 | /** 41 | * With headers use case executor. 42 | * 43 | * @param headers the headers 44 | * @return the use case executor 45 | */ 46 | public UseCaseExecutor withHeaders(Map headers) { 47 | this.headers = Objects.requireNonNull(headers, "headers is required"); 48 | return this; 49 | } 50 | 51 | /** 52 | * With domain event repo use case executor. 53 | * 54 | * @param repository the repository 55 | * @return the use case executor 56 | */ 57 | public UseCaseExecutor withDomainEventRepo(DomainEventRepository repository) { 58 | this.repository = Objects.requireNonNull(repository, "domain event repository is required"); 59 | return this; 60 | } 61 | 62 | /** 63 | * With aggregate id use case executor. 64 | * 65 | * @param aggregateId the aggregate id 66 | * @return the use case executor 67 | */ 68 | public UseCaseExecutor withAggregateId(String aggregateId) { 69 | this.aggregateId = Objects.requireNonNull(aggregateId, "aggregate id required"); 70 | return this; 71 | } 72 | 73 | /** 74 | * With use case handler use case executor. 75 | * 76 | * @param useCaseHandler the use case handler 77 | * @return the use case executor 78 | */ 79 | public UseCaseExecutor withUseCaseHandler(UseCaseHandler useCaseHandler) { 80 | this.useCaseHandler = Objects.requireNonNull(useCaseHandler, "handle for the executor use case is required"); 81 | return this; 82 | } 83 | 84 | /** 85 | * With use cases use case executor. 86 | * 87 | * @param useCases the use cases 88 | * @return the use case executor 89 | */ 90 | public UseCaseExecutor withUseCases(Set useCases) { 91 | this.useCases = Objects.requireNonNull(useCases, "use cases is required"); 92 | return this; 93 | } 94 | 95 | /** 96 | * Headers map. 97 | * 98 | * @return the map 99 | */ 100 | public Map headers() { 101 | return Optional.ofNullable(headers).orElse(new HashMap<>()); 102 | } 103 | 104 | /** 105 | * Use cases set. 106 | * 107 | * @return the set 108 | */ 109 | public Set useCases() { 110 | return useCases; 111 | } 112 | 113 | 114 | /** 115 | * Aggregate id string. 116 | * 117 | * @return the string 118 | */ 119 | public String aggregateId() { 120 | Objects.requireNonNull(aggregateId, "Aggregate identifier not available, consider using withAggregateId method"); 121 | return aggregateId; 122 | } 123 | 124 | /** 125 | * Service builder service builder. 126 | * 127 | * @return the service builder 128 | */ 129 | public ServiceBuilder serviceBuilder() { 130 | return serviceBuilder; 131 | } 132 | 133 | /** 134 | * Repository domain event repository. 135 | * 136 | * @return the domain event repository 137 | */ 138 | public DomainEventRepository repository() { 139 | Objects.requireNonNull(repository, "No repository identified, consider using the withDomainEventRepo method"); 140 | return repository; 141 | } 142 | 143 | /** 144 | * Use case handler use case handler. 145 | * 146 | * @return the use case handler 147 | */ 148 | public UseCaseHandler useCaseHandler() { 149 | return Optional.ofNullable(useCaseHandler).orElse(UseCaseHandler.getInstance()); 150 | } 151 | 152 | /** 153 | * Run syn use case optional. 154 | * 155 | * @param the type parameter 156 | * @param the type parameter 157 | * @param useCase the use case 158 | * @param request the request 159 | * @return the optional 160 | */ 161 | public Optional runSynUseCase(UseCase useCase, T request) { 162 | Optional.ofNullable(repository).ifPresentOrElse(useCase::addRepository, () -> 163 | logger.warning("No repository found for use case") 164 | ); 165 | Optional.ofNullable(serviceBuilder).ifPresentOrElse(useCase::addServiceBuilder, () -> 166 | logger.warning("No service builder found for use case") 167 | ); 168 | Optional.ofNullable(useCases).ifPresentOrElse(useCase::addUseCases, () -> 169 | logger.warning("No uses cases found for use case") 170 | ); 171 | return useCaseHandler() 172 | .setIdentifyExecutor(aggregateId()) 173 | .syncExecutor(useCase, request); 174 | } 175 | 176 | 177 | } 178 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/asyn/BaseUseCaseExecutor.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.asyn; 2 | 3 | import co.com.sofka.business.generic.ServiceBuilder; 4 | import co.com.sofka.business.generic.UseCase; 5 | import co.com.sofka.business.generic.UseCaseHandler; 6 | import co.com.sofka.business.repository.DomainEventRepository; 7 | import co.com.sofka.domain.generic.DomainEvent; 8 | 9 | import java.util.*; 10 | import java.util.concurrent.Flow; 11 | 12 | 13 | /** 14 | * The type Base use case executor. 15 | */ 16 | public abstract class BaseUseCaseExecutor { 17 | /** 18 | * The Subscriber event. 19 | */ 20 | protected Flow.Subscriber subscriberEvent; 21 | /** 22 | * The Repository. 23 | */ 24 | protected DomainEventRepository repository; 25 | /** 26 | * The Aggregate id. 27 | */ 28 | protected String aggregateId; 29 | /** 30 | * The Use case handler. 31 | */ 32 | protected UseCaseHandler useCaseHandler; 33 | /** 34 | * The Service builder. 35 | */ 36 | protected ServiceBuilder serviceBuilder; 37 | /** 38 | * The Request. 39 | */ 40 | protected UseCase.RequestValues request; 41 | /** 42 | * The Headers. 43 | */ 44 | protected Map headers; 45 | /** 46 | * The Use cases. 47 | */ 48 | protected Set useCases; 49 | 50 | /** 51 | * Subscriber event flow . subscriber. 52 | * 53 | * @return the flow . subscriber 54 | */ 55 | public Flow.Subscriber subscriberEvent() { 56 | Objects.requireNonNull(subscriberEvent, "If the subscriber is not identified, consider using the withSubscriberEvent method"); 57 | return subscriberEvent; 58 | } 59 | 60 | 61 | /** 62 | * Use case handler use case handler. 63 | * 64 | * @return the use case handler 65 | */ 66 | public UseCaseHandler useCaseHandler() { 67 | return Optional.ofNullable(useCaseHandler).orElse(UseCaseHandler.getInstance()); 68 | } 69 | 70 | /** 71 | * Repository domain event repository. 72 | * 73 | * @return the domain event repository 74 | */ 75 | public DomainEventRepository repository() { 76 | return Objects.requireNonNull(repository, "No repository identified, consider using the withDomainEventRepo method"); 77 | } 78 | 79 | /** 80 | * Aggregate id string. 81 | * 82 | * @return the string 83 | */ 84 | public String aggregateId() { 85 | return Objects.requireNonNull(aggregateId, "Aggregate identifier not available, consider using withAggregateId method"); 86 | } 87 | 88 | 89 | /** 90 | * Request use case . request values. 91 | * 92 | * @return the use case . request values 93 | */ 94 | public UseCase.RequestValues request() { 95 | return request; 96 | } 97 | 98 | /** 99 | * Headers map. 100 | * 101 | * @return the map 102 | */ 103 | public Map headers() { 104 | return Optional.ofNullable(headers).orElse(new HashMap<>()); 105 | } 106 | 107 | /** 108 | * Service builder service builder. 109 | * 110 | * @return the service builder 111 | */ 112 | public ServiceBuilder serviceBuilder() { 113 | return serviceBuilder; 114 | } 115 | 116 | 117 | /** 118 | * Gets service. 119 | * 120 | * @param the type parameter 121 | * @param clasz the clasz 122 | * @return the service 123 | */ 124 | public Optional getService(Class clasz) { 125 | Objects.requireNonNull(serviceBuilder, "the service build cannot be null, you allow use the annotation ExtensionService"); 126 | return serviceBuilder.getService(clasz); 127 | } 128 | 129 | /** 130 | * Use cases set. 131 | * 132 | * @return the set 133 | */ 134 | public Set useCases() { 135 | return useCases; 136 | } 137 | 138 | /** 139 | * With use cases base use case executor. 140 | * 141 | * @param useCases the use cases 142 | * @return the base use case executor 143 | */ 144 | public BaseUseCaseExecutor withUseCases(Set useCases) { 145 | this.useCases = Objects.requireNonNull(useCases, "use cases is required"); 146 | return this; 147 | } 148 | 149 | /** 150 | * With subscriber event base use case executor. 151 | * 152 | * @param subscriberEvent the subscriber event 153 | * @return the base use case executor 154 | */ 155 | public BaseUseCaseExecutor withSubscriberEvent(Flow.Subscriber subscriberEvent) { 156 | this.subscriberEvent = Objects.requireNonNull(subscriberEvent, "subscriber event is required"); 157 | return this; 158 | } 159 | 160 | /** 161 | * With headers base use case executor. 162 | * 163 | * @param headers the headers 164 | * @return the base use case executor 165 | */ 166 | public BaseUseCaseExecutor withHeaders(Map headers) { 167 | this.headers = Objects.requireNonNull(headers, "headers is required"); 168 | return this; 169 | } 170 | 171 | /** 172 | * With domain event repo base use case executor. 173 | * 174 | * @param repository the repository 175 | * @return the base use case executor 176 | */ 177 | public BaseUseCaseExecutor withDomainEventRepo(DomainEventRepository repository) { 178 | this.repository = Objects.requireNonNull(repository, "domain event repository is required"); 179 | return this; 180 | } 181 | 182 | /** 183 | * With aggregate id base use case executor. 184 | * 185 | * @param aggregateId the aggregate id 186 | * @return the base use case executor 187 | */ 188 | public BaseUseCaseExecutor withAggregateId(String aggregateId) { 189 | this.aggregateId = Objects.requireNonNull(aggregateId, "aggregate id required"); 190 | return this; 191 | } 192 | 193 | /** 194 | * With use case handler base use case executor. 195 | * 196 | * @param useCaseHandler the use case handler 197 | * @return the base use case executor 198 | */ 199 | public BaseUseCaseExecutor withUseCaseHandler(UseCaseHandler useCaseHandler) { 200 | this.useCaseHandler = Objects.requireNonNull(useCaseHandler, "handle for the executor use case is required"); 201 | return this; 202 | } 203 | 204 | /** 205 | * With service builder base use case executor. 206 | * 207 | * @param serviceBuilder the service builder 208 | * @return the base use case executor 209 | */ 210 | public BaseUseCaseExecutor withServiceBuilder(ServiceBuilder serviceBuilder) { 211 | this.serviceBuilder = Objects.requireNonNull(serviceBuilder); 212 | return this; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /co.com.sofka.application/src/main/java/co/com/sofka/application/ApplicationEventDrive.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.application; 2 | 3 | import co.com.sofka.business.annotation.EventListener; 4 | import co.com.sofka.business.annotation.ExtensionService; 5 | import co.com.sofka.business.generic.BusinessException; 6 | import co.com.sofka.business.generic.ServiceBuilder; 7 | import co.com.sofka.business.generic.UseCase; 8 | import co.com.sofka.business.generic.UseCaseHandler; 9 | import co.com.sofka.business.repository.DomainEventRepository; 10 | import co.com.sofka.business.support.ResponseEvents; 11 | import co.com.sofka.business.support.TriggeredEvent; 12 | import co.com.sofka.domain.generic.DomainEvent; 13 | import co.com.sofka.infraestructure.asyn.SubscriberEvent; 14 | import co.com.sofka.infraestructure.repository.EventStoreRepository; 15 | import io.github.classgraph.*; 16 | 17 | import java.lang.reflect.InvocationTargetException; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Objects; 21 | import java.util.Optional; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | import java.util.concurrent.ConcurrentMap; 24 | import java.util.logging.Level; 25 | import java.util.logging.Logger; 26 | import java.util.stream.Stream; 27 | 28 | /** 29 | * The type Application event drive. 30 | */ 31 | public class ApplicationEventDrive { 32 | private static final Logger logger = Logger.getLogger(ApplicationEventDrive.class.getName()); 33 | private final ConcurrentMap> useCases; 34 | private final SubscriberEvent subscriberEvent; 35 | private final EventStoreRepository repository; 36 | private final String packageUseCase; 37 | private final ServiceBuilder serviceBuilder; 38 | 39 | /** 40 | * Instantiates a new Application event drive. 41 | * 42 | * @param packageUseCase the package use case 43 | * @param subscriberEvent the subscriber event 44 | */ 45 | public ApplicationEventDrive(String packageUseCase, SubscriberEvent subscriberEvent) { 46 | this(packageUseCase, subscriberEvent, null); 47 | } 48 | 49 | /** 50 | * Instantiates a new Application event drive. 51 | * 52 | * @param packageUseCase the package use case 53 | * @param subscriberEvent the subscriber event 54 | * @param repository the repository 55 | */ 56 | public ApplicationEventDrive(String packageUseCase, SubscriberEvent subscriberEvent, EventStoreRepository repository) { 57 | this.subscriberEvent = subscriberEvent; 58 | this.packageUseCase = packageUseCase; 59 | this.repository = repository; 60 | this.useCases = new ConcurrentHashMap<>(); 61 | this.serviceBuilder = new ServiceBuilder(); 62 | initialize(); 63 | } 64 | 65 | private void initialize() { 66 | logger.info("---- Registered Event Listener Use Case -----"); 67 | try (ScanResult result = new ClassGraph() 68 | .enableAllInfo() 69 | .whitelistPackages(packageUseCase) 70 | .scan()) { 71 | ClassInfoList classInfos = result.getClassesWithAnnotation(EventListener.class.getName()); 72 | classInfos.parallelStream().forEach(handleClassInfo -> { 73 | try { 74 | AnnotationInfo annotationInfo = handleClassInfo.getAnnotationInfo(EventListener.class.getName()); 75 | String type = getEventType(annotationInfo); 76 | var usecase = (UseCase, ResponseEvents>) handleClassInfo 77 | .loadClass() 78 | .getDeclaredConstructor().newInstance(); 79 | usecase.addServiceBuilder(getServiceBuilder(handleClassInfo)); 80 | List list = useCases.getOrDefault(type, new ArrayList<>()); 81 | list.add(new UseCase.UseCaseWrap(type, usecase)); 82 | useCases.put(type, list); 83 | logger.info("@@@@ Registered use case for event lister --> " + usecase.getClass().getSimpleName() + "[" + type + "]"); 84 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) { 85 | } 86 | }); 87 | } 88 | } 89 | 90 | private String getEventType(AnnotationInfo annotationInfo) { 91 | return Optional.ofNullable(annotationInfo).map(annotation -> { 92 | AnnotationParameterValueList paramVals = annotation.getParameterValues(); 93 | return (String) paramVals.getValue("eventType"); 94 | }).orElseThrow(); 95 | } 96 | 97 | /** 98 | * Fire. 99 | * 100 | * @param domainEvent the domain event 101 | */ 102 | public final void fire(DomainEvent domainEvent) { 103 | var event = Objects.requireNonNull(domainEvent); 104 | Optional.ofNullable(useCases.get(domainEvent.type)).ifPresentOrElse(list -> { 105 | list.forEach(useCaseWrap -> { 106 | var useCase = useCaseWrap.usecase(); 107 | useCase.addRepository(new DomainEventRepository() { 108 | @Override 109 | public List getEventsBy(String aggregateRootId) { 110 | return repository.getEventsBy(event.getAggregateName(), aggregateRootId); 111 | } 112 | 113 | @Override 114 | public List getEventsBy(String aggregate, String aggregateRootId) { 115 | return repository.getEventsBy(aggregate, aggregateRootId); 116 | } 117 | }); 118 | useCase.setIdentify(domainEvent.aggregateRootId()); 119 | logger.info("Use case handler to use case--> " + useCase.getClass().getCanonicalName()); 120 | UseCaseHandler.getInstance() 121 | .asyncExecutor( 122 | useCase, new TriggeredEvent<>(event) 123 | ).subscribe(subscriberEvent); 124 | }); 125 | }, () -> { 126 | logger.info("No found use case --> " + event.type); 127 | }); 128 | } 129 | 130 | /** 131 | * Request use case optional. 132 | * 133 | * @param domainEvent the domain event 134 | * @return the optional 135 | */ 136 | public final Optional requestUseCase(DomainEvent domainEvent) { 137 | var event = Objects.requireNonNull(domainEvent); 138 | var wrap = Optional.ofNullable(useCases.get(domainEvent.type)) 139 | .orElseThrow(() -> new BusinessException(domainEvent.aggregateRootId(), "The use case event listener not registered")); 140 | if (!wrap.isEmpty()) { 141 | return UseCaseHandler.getInstance() 142 | .syncExecutor(wrap.get(0).usecase(), new TriggeredEvent<>(event)); 143 | } 144 | throw new BusinessException(domainEvent.aggregateRootId(), "The use case event listener not registered"); 145 | } 146 | 147 | private ServiceBuilder getServiceBuilder(ClassInfo handleClassInfo) { 148 | AnnotationInfo annotationInfo = handleClassInfo.getAnnotationInfo(ExtensionService.class.getName()); 149 | return Optional.ofNullable(annotationInfo).map(annotation -> { 150 | AnnotationParameterValueList paramVals = annotation.getParameterValues(); 151 | var list = (Object[]) paramVals.getValue("value"); 152 | Stream.of(list).forEach(className -> { 153 | try { 154 | var ref = (AnnotationClassRef) className; 155 | if (!serviceBuilder.exist(ref.loadClass())) { 156 | serviceBuilder.addService(ref.loadClass().getDeclaredConstructor().newInstance()); 157 | } 158 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 159 | logger.log(Level.SEVERE, "ERROR over service builder", e); 160 | throw new ServiceBuildException(e); 161 | } 162 | }); 163 | return serviceBuilder; 164 | }).orElse(new ServiceBuilder()); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /co.com.sofka.business/src/main/java/co/com/sofka/business/generic/UseCase.java: -------------------------------------------------------------------------------- 1 | package co.com.sofka.business.generic; 2 | 3 | import co.com.sofka.business.repository.DomainEventRepository; 4 | import co.com.sofka.business.support.ResponseEvents; 5 | import co.com.sofka.business.support.TriggeredEvent; 6 | import co.com.sofka.domain.generic.DomainEvent; 7 | 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | import java.util.Set; 12 | 13 | 14 | /** 15 | * The type Use case. 16 | * 17 | * @param the type parameter 18 | * @param

the type parameter 19 | */ 20 | public abstract class UseCase { 21 | 22 | private Q request; 23 | private String identify; 24 | 25 | private UseCaseFormat

useCaseFormat; 26 | private ServiceBuilder serviceBuilder; 27 | private DomainEventRepository repository; 28 | private Set useCases; 29 | 30 | 31 | /** 32 | * Request q. 33 | * 34 | * @return the q 35 | */ 36 | protected Q request() { 37 | return request; 38 | } 39 | 40 | /** 41 | * Sets request. 42 | * 43 | * @param request the request 44 | */ 45 | public void setRequest(Q request) { 46 | this.request = request; 47 | } 48 | 49 | /** 50 | * Emit use case format. 51 | * 52 | * @return the use case format 53 | */ 54 | public UseCaseFormat

emit() { 55 | return useCaseFormat; 56 | } 57 | 58 | /** 59 | * Sets use case callback. 60 | * 61 | * @param useCaseFormat the use case format 62 | */ 63 | public void setUseCaseCallback(UseCaseFormat

useCaseFormat) { 64 | this.useCaseFormat = useCaseFormat; 65 | } 66 | 67 | 68 | /** 69 | * Run. 70 | */ 71 | protected void run() { 72 | try { 73 | executeUseCase(request); 74 | } catch (BusinessException e) { 75 | useCaseFormat.onError(e); 76 | } catch (RuntimeException e) { 77 | var exception = new UnexpectedException(identify, "There is an unexpected problem in the use case", e); 78 | useCaseFormat.onError(exception); 79 | } 80 | } 81 | 82 | /** 83 | * Add service builder. 84 | * 85 | * @param serviceBuilder the service builder 86 | */ 87 | public void addServiceBuilder(ServiceBuilder serviceBuilder) { 88 | this.serviceBuilder = Objects.requireNonNull(serviceBuilder); 89 | } 90 | 91 | /** 92 | * Add use cases. 93 | * 94 | * @param useCases the use cases 95 | */ 96 | public void addUseCases(Set useCases) { 97 | this.useCases = Objects.requireNonNull(useCases); 98 | } 99 | 100 | /** 101 | * Gets service. 102 | * 103 | * @param the type parameter 104 | * @param clasz the clasz 105 | * @return the service 106 | */ 107 | public Optional getService(Class clasz) { 108 | Objects.requireNonNull(serviceBuilder, "The service build cannot be null, you allow use the annotation ExtensionService"); 109 | return serviceBuilder.getService(clasz); 110 | } 111 | 112 | /** 113 | * Retrieve events list. 114 | * 115 | * @param aggregateId the aggregate id 116 | * @return the list 117 | */ 118 | @SuppressWarnings("unchecked") 119 | public List retrieveEvents(String aggregateId) { 120 | return UseCaseReplyUtil.retry(() -> { 121 | var events = repository().getEventsBy(aggregateId); 122 | if (!events.isEmpty()) { 123 | return events; 124 | } 125 | throw new BusinessException(aggregateId, "Reply event for use case"); 126 | }, 5); 127 | } 128 | 129 | /** 130 | * Retrieve events list. 131 | * 132 | * @return the list 133 | */ 134 | @SuppressWarnings("unchecked") 135 | public List retrieveEvents() { 136 | String aggregateId = identify; 137 | return UseCaseReplyUtil.retry(() -> { 138 | var events = repository().getEventsBy(aggregateId); 139 | if (!events.isEmpty()) { 140 | return events; 141 | } 142 | throw new BusinessException(aggregateId, "Reply event for use case"); 143 | }, 5); 144 | } 145 | 146 | /** 147 | * Request use case optional. 148 | * 149 | * @param domainEvent the domain event 150 | * @return the optional 151 | */ 152 | public final Optional requestUseCase(DomainEvent domainEvent) { 153 | var event = Objects.requireNonNull(domainEvent); 154 | var wrap = useCases.stream() 155 | .filter(useCaseWrap -> useCaseWrap.eventType.equals(domainEvent.type)) 156 | .findFirst().orElseThrow(() -> new BusinessException(identify, "The use case event listener not registered")); 157 | return UseCaseHandler.getInstance() 158 | .setIdentifyExecutor(event.aggregateRootId()) 159 | .syncExecutor(wrap.usecase, new TriggeredEvent<>(event)); 160 | } 161 | 162 | /** 163 | * Execute use case. 164 | * 165 | * @param input the input 166 | */ 167 | public abstract void executeUseCase(Q input); 168 | 169 | /** 170 | * Sets identify. 171 | * 172 | * @param identify the identify 173 | */ 174 | public void setIdentify(String identify) { 175 | this.identify = identify; 176 | } 177 | 178 | /** 179 | * Add repository. 180 | * 181 | * @param repository the repository 182 | */ 183 | public void addRepository(DomainEventRepository repository) { 184 | this.repository = repository; 185 | } 186 | 187 | /** 188 | * Repository domain event repository. 189 | * 190 | * @return the domain event repository 191 | */ 192 | public DomainEventRepository repository() { 193 | return repository; 194 | } 195 | 196 | /** 197 | * The interface Request event. 198 | */ 199 | public interface RequestEvent extends RequestValues { 200 | /** 201 | * Gets domain event. 202 | * 203 | * @return the domain event 204 | */ 205 | DomainEvent getDomainEvent(); 206 | } 207 | 208 | /** 209 | * The interface Response values. 210 | */ 211 | public interface ResponseValues { 212 | } 213 | 214 | /** 215 | * The interface Request values. 216 | */ 217 | public interface RequestValues { 218 | } 219 | 220 | /** 221 | * The interface Use case format. 222 | * 223 | * @param the type parameter 224 | */ 225 | public interface UseCaseFormat { 226 | /** 227 | * On response. 228 | * 229 | * @param output the output 230 | */ 231 | void onResponse(R output); 232 | 233 | /** 234 | * On error. 235 | * 236 | * @param exception the exception 237 | */ 238 | void onError(RuntimeException exception); 239 | } 240 | 241 | /** 242 | * The type Use case wrap. 243 | */ 244 | public static class UseCaseWrap { 245 | private final UseCase, ResponseEvents> usecase; 246 | private final String eventType; 247 | 248 | 249 | /** 250 | * Instantiates a new Use case wrap. 251 | * 252 | * @param eventType the event type 253 | * @param usecase the usecase 254 | */ 255 | public UseCaseWrap(String eventType, UseCase, ResponseEvents> usecase) { 256 | this.usecase = usecase; 257 | this.eventType = eventType; 258 | } 259 | 260 | 261 | /** 262 | * Usecase use case. 263 | * 264 | * @return the use case 265 | */ 266 | public UseCase, ResponseEvents> usecase() { 267 | return usecase; 268 | } 269 | 270 | /** 271 | * Event type string. 272 | * 273 | * @return the string 274 | */ 275 | public String eventType() { 276 | return eventType; 277 | } 278 | } 279 | 280 | } 281 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/cde9499e2ae84e2da69e756b1c53d80c)](https://app.codacy.com/manual/Sofka-XT/ddd-generic-java?utm_source=github.com&utm_medium=referral&utm_content=Sofka-XT/ddd-generic-java&utm_campaign=Badge_Grade_Dashboard) 2 | [![Build Status](https://travis-ci.com/Sofka-XT/ddd-generic-java.svg?branch=master)](https://travis-ci.com/Sofka-XT/ddd-generic-java) 3 | 4 | ## Sofka Domain-Driven Design 5 | Sofka introduce una librería a la comunidad open source para diseñar aplicaciones orientadas al dominio. Esta librería proporciona abstracciones que permiten adoptar el concepto de forma correcta, el estilo que se propone es totalmente discutible para ser mejorado, se espera que al momento de aplicar esta librería se tenga claro los conceptos tácticos de DDD. 6 | 7 | ### ¿Qué es DDD? 8 | 9 | *DDD (Domain-Driven Desing)* es un método de diseño para descubrir el negocio de dominio de forma clara, apoyado de patrones de diseño y estilos de arquitectura centrados en el dominio. 10 | 11 | ### ¿Porqué DDD? 12 | 13 | DDD es necesario cuando hablamos de modelamiento del negocio para grandes organizaciones, con el objetivo de diseñar software al rededor del dominio y no en solucionar un problema en particular. Ahora bien DDD no se aplica bien para el caso de un CRUD, dado que no estaría orientado al dominio organizacional, sino a resolver un problema en particular. 14 | 15 | ### ¿Qué resuelve la librería? 16 | 17 | Desde el punto de vista táctico, se requiere aplicar algunos conceptos fundamentales, para poder aplicar DDD. Todo esos conceptos se tiene en la librería para interfaces o abstracciones, y además de proporcional algunos patrones de diseño que se adaptan a estilos de arquitecturas deferentes. 18 | 19 | #### Patrones 20 | - Commands y Events 21 | - Use Case (Request y Response) 22 | - Handlers 23 | - Publisher y Subscriber 24 | - Repository 25 | - Aggregate 26 | - Event Sourcing 27 | 28 | ### Adoptar las arquitecturas 29 | - Por eventos (EDA) 30 | - Por commands, events y queries (CQRS) 31 | - Por capas 32 | 33 | 34 | #### Motivation 35 | A domain-oriented designer, clean architecture, and clear business domain specs are used. 36 | 37 | The CQRS pattern will be used with Event Sourcing + EDA. It is segregated into two Queries and Commands applications. Commands are executed with a single instruction or entrypoint. Los Queries also has two unique entrypoints (one for lists and one for the unique model). The databases are managed as collections. 38 | 39 | ### Executor Command 40 | ![domain model](docs/executor_command.png) 41 | 42 | ### Queries Handle 43 | ![domain model](docs/queries_handle.png) 44 | 45 | ### Domain model 46 | 47 | ![domain model](docs/domain_model.png) 48 | 49 | ## Instalación 50 | 51 | Generic dependency for ddd Java - [https://mvnrepository.com/artifact/co.com.sofka/domain-driven-design](https://mvnrepository.com/artifact/co.com.sofka/domain-driven-design) 52 | ``` 53 | 54 | co.com.sofka 55 | domain-driven-design 56 | 1.0.0 57 | pom 58 | 59 | ``` 60 | 61 | Si se require dividir los conceptos se puede usar de forma independeiente de la siguiente manera: 62 | 63 | ``` 64 | 65 | co.com.sofka 66 | domain 67 | 1.5.0 68 | 69 | ``` 70 | ``` 71 | 72 | co.com.sofka 73 | business 74 | 1.5.0 75 | 76 | ``` 77 | ``` 78 | 79 | co.com.sofka 80 | infrastructure 81 | 1.5.0 82 | 83 | ``` 84 | ``` 85 | 86 | co.com.sofka 87 | application 88 | 1.5.0 89 | 90 | ``` 91 | 92 | ## Conceptos e implementación 93 | 94 | ### Entidades 95 | Una entidad tiene comportamientos, pero no lanza eventos como son los agregados, si tenemos una entidad suelta sin relación con el agregado entonces solo se aplica para cambiar los estados de la misma. Toda entidad depende de una ID, tal cual como el Agregado Root. 96 | ```java 97 | public class Student extends Entity { 98 | 99 | protected Name name; 100 | protected Gender gender; 101 | protected DateOfBirth dateOfBirth; 102 | protected Score score; 103 | 104 | protected Student(StudentIdentity studentIdentity, Name name, Gender gender, DateOfBirth dateOfBirth) { 105 | super(studentIdentity); 106 | this.name = name; 107 | this.gender =gender; 108 | this.dateOfBirth = dateOfBirth; 109 | this.score = new Score(0); 110 | } 111 | 112 | private Student(StudentIdentity studentIdentity){ 113 | super(studentIdentity); 114 | } 115 | 116 | public static Student form(StudentIdentity studentIdentity, Name name, Gender gender, DateOfBirth dateOfBirth){ 117 | var student = new Student(studentIdentity); 118 | student.name = name; 119 | student.gender = gender; 120 | student.dateOfBirth = dateOfBirth; 121 | return student; 122 | } 123 | 124 | public String name() { 125 | return name.value(); 126 | } 127 | 128 | public String gender() { 129 | return gender.value(); 130 | } 131 | 132 | public String dateOfBirth() { 133 | return dateOfBirth.value(); 134 | } 135 | 136 | public Score.Values score() { 137 | return score.value(); 138 | } 139 | 140 | public void updateScore(Score score){ 141 | this.score = score; 142 | } 143 | 144 | public void updateName(Name name){ 145 | this.name = name; 146 | } 147 | 148 | public void updateDateOfBirth(DateOfBirth dateOfBirth){ 149 | this.dateOfBirth = dateOfBirth; 150 | } 151 | 152 | public void updateGender(Gender gender){ 153 | this.gender = gender; 154 | } 155 | 156 | @Override 157 | public boolean equals(Object o) { 158 | return super.equals(o); 159 | } 160 | 161 | @Override 162 | public int hashCode() { 163 | return super.hashCode(); 164 | } 165 | } 166 | ``` 167 | 168 | > Mas adelante estaríamos realizando un tutorial para aplicarlo en una arquitectura distribuida y con CQRS+ES con una arquitectura EDA. 169 | 170 | ### Agregado orientado a Eventos 171 | ```java 172 | public class Team extends AggregateEvent { 173 | protected Name name; 174 | protected Set students; 175 | public Team(TeamIdentity identity, Name name) { 176 | this(identity); 177 | appendChange(new CreatedTeam(name)).apply(); 178 | } 179 | 180 | private Team(TeamIdentity identity) { 181 | super(identity); 182 | subscribe(new TeamBehavior(this)); 183 | } 184 | 185 | public static Team from(TeamIdentity aggregateId, List list) { 186 | Team team = new Team(aggregateId); 187 | list.forEach(team::applyEvent); 188 | return team; 189 | } 190 | 191 | 192 | public void addNewStudent(Name name, Gender gender, DateOfBirth dateOfBirth) { 193 | StudentIdentity studentIdentity = new StudentIdentity(); 194 | appendChange(new AddedStudent(studentIdentity, name, gender, dateOfBirth)).apply(); 195 | } 196 | 197 | public void removeStudent(StudentIdentity studentIdentity) { 198 | appendChange(new RemovedStudent(studentIdentity)).apply(); 199 | } 200 | 201 | public void updateName(Name newName) { 202 | appendChange(new UpdatedName(newName)).apply(); 203 | } 204 | 205 | public void updateStudentName(StudentIdentity studentIdentity, Name name) { 206 | appendChange(new UpdatedStudent(studentIdentity, name)).apply(); 207 | } 208 | 209 | public void applyScoreToStudent(StudentIdentity studentIdentity, Score score) { 210 | appendChange(new UpdateScoreOfStudent(studentIdentity, score)).apply(); 211 | } 212 | 213 | public Set students() { 214 | return students; 215 | } 216 | 217 | public String name() { 218 | return name.value(); 219 | } 220 | } 221 | ``` 222 | 223 | ### Comportamientos orientado a Eventos 224 | ```java 225 | public class TeamBehavior extends EventChange { 226 | protected TeamBehavior(Team entity) { 227 | apply((CreatedTeam event) -> { 228 | entity.students = new HashSet<>(); 229 | entity.name = event.getName(); 230 | }); 231 | 232 | apply((AddedStudent event) -> { 233 | var student = new Student( 234 | event.getStudentIdentity(), 235 | event.getName(), 236 | event.getGender(), 237 | event.getDateOfBirth() 238 | ); 239 | entity.students.add(student); 240 | }); 241 | 242 | apply((RemovedStudent event) -> entity.students 243 | .removeIf(e -> e.identity().equals(event.getIdentity()))); 244 | 245 | apply((UpdatedName event) -> entity.name = event.getNewName()); 246 | 247 | apply((UpdatedStudent event) -> { 248 | var studentUpdate = getStudentByIdentity(entity, event.getStudentIdentity()); 249 | studentUpdate.updateName(event.getName()); 250 | }); 251 | 252 | apply((UpdateScoreOfStudent event) -> { 253 | var studentUpdate = getStudentByIdentity(entity, event.getStudentIdentity()); 254 | studentUpdate.updateScore(event.getScore()); 255 | }); 256 | } 257 | 258 | private Student getStudentByIdentity(Team entity, Identity identity) { 259 | return entity.students.stream() 260 | .filter(e -> e.identity().equals(identity)) 261 | .findFirst() 262 | .orElseThrow(); 263 | } 264 | } 265 | ``` 266 | 267 | ### Objetos de valor 268 | Un objeto de valor es un objeto inmutable que representa un valor de la entidad. A diferencia de la entidad es que el VO (Value Object) no tiene un identidad que la represente. 269 | ```java 270 | public class DateOfBirth implements ValueObject { 271 | private final LocalDate date; 272 | private final String format; 273 | 274 | public DateOfBirth(int day, int month, int year) { 275 | try { 276 | date = LocalDate.of(year, month, day); 277 | if(date.isAfter(LocalDate.now())){ 278 | throw new IllegalArgumentException("No valid the date of birth"); 279 | } 280 | } catch (DateTimeException e){ 281 | throw new IllegalArgumentException(e.getMessage()); 282 | } 283 | format = generateFormat(); 284 | } 285 | 286 | private String generateFormat(){ 287 | return date.format(DateTimeFormatter.ofPattern("dd-MM-yyyy")); 288 | } 289 | 290 | @Override 291 | public String value() { 292 | return format; 293 | } 294 | 295 | @Override 296 | public boolean equals(Object o) { 297 | if (this == o) return true; 298 | if (o == null || getClass() != o.getClass()) return false; 299 | DateOfBirth that = (DateOfBirth) o; 300 | return Objects.equals(format, that.format); 301 | } 302 | 303 | @Override 304 | public int hashCode() { 305 | return Objects.hash(format); 306 | } 307 | } 308 | ``` 309 | 310 | ### Evento de Dominio 311 | Un evento de dominio esta relacionado con un Agregado Id (que es un VO), este evento es la consecuencia de un comando que ejecuta un comportamiento de un Agregado un Entidad. 312 | ```java 313 | public class AddedStudent extends DomainEvent { 314 | 315 | private final StudentIdentity studentIdentity; 316 | private final Name name; 317 | private final Gender gender; 318 | private final DateOfBirth dateOfBirth; 319 | 320 | public AddedStudent(StudentIdentity studentIdentity, Name name, Gender gender, DateOfBirth dateOfBirth) { 321 | super("training.team.addedstudent"); 322 | this.studentIdentity = studentIdentity; 323 | this.name = name; 324 | this.gender = gender; 325 | this.dateOfBirth = dateOfBirth; 326 | } 327 | 328 | public Name getName() { 329 | return name; 330 | } 331 | 332 | public Gender getGender() { 333 | return gender; 334 | } 335 | 336 | public DateOfBirth getDateOfBirth() { 337 | return dateOfBirth; 338 | } 339 | 340 | public StudentIdentity getStudentIdentity() { 341 | return studentIdentity; 342 | } 343 | 344 | } 345 | ``` 346 | 347 | ### Comandos 348 | ```java 349 | public class CreateTeam extends Command { 350 | private final Name name; 351 | 352 | public CreateTeam(Name name) { 353 | this.name = name; 354 | } 355 | 356 | public Name getName() { 357 | return name; 358 | } 359 | 360 | } 361 | ``` 362 | 363 | ### Aplicar el agregado 364 | Cuando se crear nuevo objeto se instancia el agregado y se le asigna el identificado que relaciona la entidad. Además de los argumentos de creación. Un agregado puede tener múltiples constructores que permiten crear el agregado. 365 | ```java 366 | @CommandHandles 367 | @CommandType(name = "training.team.create", aggregate = "team") 368 | public class CreateTeamHandle extends UseCaseExecutor { 369 | private static Logger logger = Logger.getLogger(CreateTeamHandle.class.getName()); 370 | 371 | private RequestCommand request; 372 | 373 | @Override 374 | public void accept(CreateTeam command) { 375 | logger.info(" ----- Executor CreateTeamHandle ------"); 376 | request = new RequestCommand<>(command); 377 | } 378 | 379 | @Override 380 | public void run() { 381 | runUseCase(new UseCase() { 382 | @Override 383 | public void executeUseCase(RequestValues objectInput) { 384 | var id = new TeamIdentity(); 385 | var command = request.getCommand(); 386 | var team = new Team(id, command.getName()); 387 | emit().onSuccess(new ResponseEvents(team.getUncommittedChanges())); 388 | } 389 | }, request); 390 | } 391 | } 392 | ``` 393 | Ya para el caso de Actualizar un agregado se reconstruye el agregado según los eventos persistidos, si se tiene un agregado no orientado a eventos se reconstruye con los valores necesarios. Cuando se reconstruya se aplica el comportamiento de la entidad. 394 | ```java 395 | @BusinessLogin 396 | public class CreateStudentUseCase extends UseCase, ResponseEvents> { 397 | private final DomainEventRepository repository; 398 | 399 | public CreateStudentUseCase(DomainEventRepository repository) { 400 | this.repository = repository; 401 | } 402 | @Override 403 | protected void executeUseCase(RequestCommand request) { 404 | Team team = reclaimTeam(); 405 | 406 | var students = Optional.ofNullable(team.students()) 407 | .orElseThrow(() -> new NecessaryDependency("the students is need for this use case")); 408 | 409 | if(students.size() > 5){ 410 | emit().onError(new NoMoreStudentAllowed()); 411 | } else { 412 | emit().onResponse(aNewStudentAddedFor(team)); 413 | } 414 | } 415 | 416 | private Team reclaimTeam() { 417 | var aggregateId = request().getCommand().aggregateRootId(); 418 | var events = repository.getEventsBy(aggregateId); 419 | return Team.from(TeamIdentity.of(aggregateId), events); 420 | } 421 | 422 | private ResponseEvents aNewStudentAddedFor(Team team) { 423 | var command = request().getCommand(); 424 | team.addNewStudent( 425 | command.getName(), 426 | command.getGender(), 427 | command.getDateOfBirth() 428 | ); 429 | return new ResponseEvents(team.getUncommittedChanges()); 430 | } 431 | } 432 | ``` 433 | Al final de ejecutar el comportamiento se determina si existe cambios dentro del agregado para emitir los eventos a una capa superior. Después se marca los cambios como confirmados. 434 | 435 | > Se puede aplicar aquí casos de uso, comandos y eventos para aplicar el agregado. 436 | 437 | ### Queries 438 | ```java 439 | @QueryHandles 440 | public class StudentsByTeam extends ViewModelExecutor { 441 | @Override 442 | public ViewModel apply(Map params) { 443 | var query = new ByStudentIdentity(); 444 | query.setAggregateRootId(params.get("teamId")); 445 | 446 | return getDataMapped("teams", StudentViewModel.class) 447 | .applyAsElement(query); 448 | } 449 | } 450 | ``` 451 | ## Contribución 452 | 453 | 1. Realizar el fork 454 | 2. Crea tu rama de características: `git checkout -b feature/my-feat` 455 | 3. Confirma tus cambios: `git commit -am "Agrega alguna característica"` 456 | 4. Empuje a la rama: `git push origin feature/my-feat` 457 | 5. Presentar una Pull Requests 458 | 459 | > También pueden colaborar creando issue para dar evolución del proyecto. 460 | 461 | ## License 462 | 463 | Sofka Domain-Drive Desing is Open Source software released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html). 464 | --------------------------------------------------------------------------------