├── src ├── test │ └── java │ │ └── org │ │ └── mvnsearch │ │ ├── demo │ │ ├── application │ │ │ ├── grpc │ │ │ │ └── .gitkeep │ │ │ ├── rest │ │ │ │ └── .gitkeep │ │ │ ├── common │ │ │ │ └── .gitkeep │ │ │ ├── web │ │ │ │ └── .gitkeep │ │ │ ├── package-info.java │ │ │ └── dubbo │ │ │ │ └── AccountFacade.java │ │ ├── domain │ │ │ ├── package-info.java │ │ │ ├── model │ │ │ │ ├── OrderLineItem.java │ │ │ │ ├── AccountRepository.java │ │ │ │ ├── Order.java │ │ │ │ └── Account.java │ │ │ ├── specification │ │ │ │ └── AccountSpec.java │ │ │ ├── event │ │ │ │ ├── AccountProcessor.java │ │ │ │ ├── LoginEvent.java │ │ │ │ └── AccountEvent.java │ │ │ ├── factory │ │ │ │ └── AccountFactory.java │ │ │ └── service │ │ │ │ └── AccountService.java │ │ ├── DemoApp.java │ │ ├── impl │ │ │ ├── domain │ │ │ │ ├── factory │ │ │ │ │ └── AccountFactoryImpl.java │ │ │ │ ├── specification │ │ │ │ │ └── AccountSpecImpl.java │ │ │ │ ├── event │ │ │ │ │ └── AccountEventProcessor.java │ │ │ │ ├── model │ │ │ │ │ └── AccountRepositoryImpl.java │ │ │ │ └── service │ │ │ │ │ └── AccountServiceImpl.java │ │ │ ├── application │ │ │ │ └── dubbo │ │ │ │ │ └── AccountManagerImpl.java │ │ │ └── infra │ │ │ │ └── CacheServiceImpl.java │ │ └── infra │ │ │ └── CacheService.java │ │ └── ddd │ │ └── domain │ │ └── events │ │ └── DomainEventTest.java └── main │ ├── resources │ └── META-INF │ │ └── services │ │ └── com.fasterxml.jackson.databind.Module │ ├── java │ └── org │ │ └── mvnsearch │ │ └── ddd │ │ ├── cqrs │ │ ├── QueryHandler.java │ │ └── CommandHandler.java │ │ ├── domain │ │ ├── Identifiable.java │ │ ├── events │ │ │ ├── MapDomainEvent.java │ │ │ ├── jackson │ │ │ │ ├── CloudEventsModule.java │ │ │ │ ├── DomainEventDataFieldFilter.java │ │ │ │ ├── ZonedDateTimeSerializer.java │ │ │ │ └── ZonedDateTimeDeserializer.java │ │ │ ├── CloudEventHandler.java │ │ │ ├── ExtensionAttribute.java │ │ │ ├── DomainEventBuilder.java │ │ │ ├── Json.java │ │ │ └── DomainEvent.java │ │ ├── annotations │ │ │ ├── DomainLayer.java │ │ │ ├── DomainEntity.java │ │ │ ├── DomainService.java │ │ │ ├── DomainSpecification.java │ │ │ ├── ValueObject.java │ │ │ ├── DomainAggregate.java │ │ │ ├── DomainFactory.java │ │ │ ├── DomainRepository.java │ │ │ └── Identity.java │ │ ├── BaseDomainEntity.java │ │ └── BaseDomainAggregate.java │ │ ├── application │ │ └── annotations │ │ │ ├── ApplicationLayer.java │ │ │ └── ApplicationService.java │ │ └── infrastructure │ │ └── annotations │ │ ├── InfrastructureLayer.java │ │ └── InfrastructureService.java │ ├── puml │ ├── event-class.puml │ ├── ddd-module.puml │ ├── ddd-components.puml │ └── ddd-components-rpc.puml │ └── proto │ └── cloudevent.proto ├── jitpack.yml ├── .github └── workflows │ └── maven.yml ├── .gitignore ├── README.md └── pom.xml /src/test/java/org/mvnsearch/demo/application/grpc/.gitkeep: -------------------------------------------------------------------------------- 1 | Hexagonal port for gRPC -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/application/rest/.gitkeep: -------------------------------------------------------------------------------- 1 | Hexagonal port for HTTP REST API -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/application/common/.gitkeep: -------------------------------------------------------------------------------- 1 | common package for Hexagonal ports -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/application/web/.gitkeep: -------------------------------------------------------------------------------- 1 | Hexagonal port for Web application with pages -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module: -------------------------------------------------------------------------------- 1 | org.mvnsearch.ddd.domain.events.jackson.CloudEventsModule -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/package-info.java: -------------------------------------------------------------------------------- 1 | @DomainLayer 2 | package org.mvnsearch.demo.domain; 3 | 4 | import org.mvnsearch.ddd.domain.annotations.DomainLayer; -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - oraclejdk8 3 | install: 4 | - echo "Running a custom install command" 5 | - mvn -P release -DskipTests clean install 6 | env: 7 | MYVAR: "custom environment variable" -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/application/package-info.java: -------------------------------------------------------------------------------- 1 | @ApplicationLayer 2 | package org.mvnsearch.demo.application; 3 | 4 | import org.mvnsearch.ddd.application.annotations.ApplicationLayer; -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/cqrs/QueryHandler.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.cqrs; 2 | 3 | /** 4 | * CQRS Query handler 5 | * 6 | * @author linux_china 7 | */ 8 | public interface QueryHandler { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/cqrs/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.cqrs; 2 | 3 | /** 4 | * CQRS Command handler 5 | * 6 | * @author linux_china 7 | */ 8 | public interface CommandHandler { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/Identifiable.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain; 2 | 3 | /** 4 | * identifiable with id 5 | * 6 | * @author linux_china 7 | */ 8 | public interface Identifiable { 9 | ID getId(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/MapDomainEvent.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.events; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * domain event with Map data 7 | * 8 | * @author linux_china 9 | */ 10 | public class MapDomainEvent extends DomainEvent> { 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/model/OrderLineItem.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.model; 2 | 3 | import org.mvnsearch.ddd.domain.annotations.ValueObject; 4 | 5 | /** 6 | * Order's LineItem: Value Object 7 | * 8 | * @author linux_china 9 | */ 10 | @ValueObject 11 | public class OrderLineItem { 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/specification/AccountSpec.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.specification; 2 | 3 | import java.util.Optional; 4 | 5 | /** 6 | * account specification 7 | * 8 | * @author linux_china 9 | */ 10 | public interface AccountSpec { 11 | 12 | Optional isEmailUnique(String email); 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/event/AccountProcessor.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.event; 2 | 3 | import org.springframework.context.event.EventListener; 4 | 5 | /** 6 | * account processor 7 | * 8 | * @author linux_china 9 | */ 10 | public interface AccountProcessor { 11 | 12 | @EventListener 13 | void handleLogin(LoginEvent event); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/annotations/DomainLayer.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * DomainLayer for package-info.java 7 | * 8 | * @author linux_china 9 | */ 10 | @Retention(RetentionPolicy.CLASS) 11 | @Target({ ElementType.PACKAGE, ElementType.TYPE }) 12 | @Documented 13 | public @interface DomainLayer { 14 | } -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/application/annotations/ApplicationLayer.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.application.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * ApplicationLayer for package-info.java 7 | * 8 | * @author linux_china 9 | */ 10 | @Retention(RetentionPolicy.CLASS) 11 | @Target({ElementType.PACKAGE, ElementType.TYPE}) 12 | @Documented 13 | public @interface ApplicationLayer { 14 | } -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/factory/AccountFactory.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.factory; 2 | 3 | import org.mvnsearch.ddd.domain.annotations.DomainFactory; 4 | import org.mvnsearch.demo.domain.model.Account; 5 | 6 | /** 7 | * account factory 8 | * 9 | * @author linux_china 10 | */ 11 | @DomainFactory 12 | public interface AccountFactory { 13 | 14 | Account createFromWechat(String wechatXml); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/infrastructure/annotations/InfrastructureLayer.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.infrastructure.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * InfrastructureLayer for package-info.java 7 | * 8 | * @author linux_china 9 | */ 10 | @Retention(RetentionPolicy.CLASS) 11 | @Target({ElementType.PACKAGE, ElementType.TYPE}) 12 | @Documented 13 | public @interface InfrastructureLayer { 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/application/dubbo/AccountFacade.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.application.dubbo; 2 | 3 | import org.mvnsearch.ddd.application.annotations.ApplicationService; 4 | import org.mvnsearch.demo.domain.model.Account; 5 | 6 | /** 7 | * account facade 8 | * 9 | * @author linux_china 10 | */ 11 | @ApplicationService 12 | public interface AccountFacade { 13 | 14 | Account findByEmail(String email); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/BaseDomainEntity.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain; 2 | 3 | import org.mvnsearch.ddd.domain.annotations.DomainEntity; 4 | 5 | /** 6 | * base domain entity 7 | * 8 | * @author linux_china 9 | */ 10 | @DomainEntity 11 | public interface BaseDomainEntity extends Identifiable { 12 | /** 13 | * get entity id 14 | * 15 | * @return entity id 16 | */ 17 | ID getId(); 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/model/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.model; 2 | 3 | import org.mvnsearch.ddd.domain.annotations.DomainRepository; 4 | 5 | /** 6 | * account repository 7 | * 8 | * @author linux_china 9 | */ 10 | @DomainRepository 11 | public interface AccountRepository { 12 | 13 | void create(Account account); 14 | 15 | void update(Account account); 16 | 17 | Account findOne(long id); 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/DemoApp.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * demo app entrance 8 | * 9 | * @author linux_china 10 | */ 11 | @SpringBootApplication 12 | public class DemoApp { 13 | public static void main(String[] args) { 14 | SpringApplication.run(DemoApp.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/BaseDomainAggregate.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain; 2 | 3 | import org.mvnsearch.ddd.domain.annotations.DomainAggregate; 4 | 5 | /** 6 | * base domain aggregate 7 | * 8 | * @author linux_china 9 | */ 10 | @DomainAggregate 11 | public interface BaseDomainAggregate extends Identifiable { 12 | 13 | /** 14 | * get entity id 15 | * 16 | * @return entity id 17 | */ 18 | ID getId(); 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/event/LoginEvent.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.event; 2 | 3 | import org.mvnsearch.ddd.domain.events.DomainEvent; 4 | import org.mvnsearch.demo.domain.model.Account; 5 | 6 | /** 7 | * login event 8 | * 9 | * @author linux_china 10 | */ 11 | public class LoginEvent extends DomainEvent { 12 | 13 | public LoginEvent(Account account, String ip) { 14 | setData(account); 15 | setExtension("ip", ip); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/annotations/DomainEntity.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * domain entity annotation 10 | * 11 | * @author linux_china 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | public @interface DomainEntity { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/impl/domain/factory/AccountFactoryImpl.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.impl.domain.factory; 2 | 3 | import org.mvnsearch.demo.domain.factory.AccountFactory; 4 | import org.mvnsearch.demo.domain.model.Account; 5 | 6 | /** 7 | * account factory implementation 8 | * 9 | * @author linux_china 10 | */ 11 | public class AccountFactoryImpl implements AccountFactory { 12 | public Account createFromWechat(String wechatXml) { 13 | return null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/infra/CacheService.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.infra; 2 | 3 | import org.mvnsearch.ddd.infrastructure.annotations.InfrastructureService; 4 | 5 | import java.io.Serializable; 6 | import java.util.Optional; 7 | 8 | /** 9 | * cache service 10 | * 11 | * @author linux_china 12 | */ 13 | @InfrastructureService 14 | public interface CacheService { 15 | 16 | void put(String key, Serializable object); 17 | 18 | Optional get(String key); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/annotations/DomainService.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * domain service annotation 10 | * 11 | * @author linux_china 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | public @interface DomainService { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/annotations/DomainSpecification.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * domain specification 10 | * 11 | * @author linux_china 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | public @interface DomainSpecification { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/annotations/ValueObject.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * domain value object annotation 10 | * 11 | * @author linux_china 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | public @interface ValueObject { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/impl/domain/specification/AccountSpecImpl.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.impl.domain.specification; 2 | 3 | import org.mvnsearch.demo.domain.specification.AccountSpec; 4 | 5 | import java.util.Optional; 6 | 7 | /** 8 | * account sepecification implementation 9 | * 10 | * @author linux_china 11 | */ 12 | public class AccountSpecImpl implements AccountSpec { 13 | 14 | public Optional isEmailUnique(String email) { 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/annotations/DomainAggregate.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * domain aggregate annotation 10 | * 11 | * @author linux_china 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | public @interface DomainAggregate { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/annotations/DomainFactory.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.annotations; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * domain factory annotation 11 | * 12 | * @author linux_china 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(ElementType.TYPE) 16 | public @interface DomainFactory { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/impl/application/dubbo/AccountManagerImpl.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.impl.application.dubbo; 2 | 3 | import org.mvnsearch.demo.application.dubbo.AccountFacade; 4 | import org.mvnsearch.demo.domain.model.Account; 5 | 6 | /** 7 | * application manager implementation 8 | * 9 | * @author linux_china 10 | */ 11 | public class AccountManagerImpl implements AccountFacade { 12 | 13 | public Account findByEmail(String email) { 14 | return null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/annotations/DomainRepository.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * domain repository annotation 10 | * 11 | * @author linux_china 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | public @interface DomainRepository { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/application/annotations/ApplicationService.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.application.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * application service annotation, facade pattern 10 | * 11 | * @author linux_china 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | public @interface ApplicationService { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/infrastructure/annotations/InfrastructureService.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.infrastructure.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * infrastructure service annotation 10 | * 11 | * @author linux_china 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | public @interface InfrastructureService { 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/service/AccountService.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.service; 2 | 3 | import org.mvnsearch.ddd.domain.annotations.DomainService; 4 | import org.mvnsearch.demo.domain.model.Account; 5 | 6 | /** 7 | * account service 8 | * 9 | * @author linux_china 10 | */ 11 | @DomainService 12 | public interface AccountService { 13 | 14 | void create(Account account); 15 | 16 | void updatePassword(Long accountId, String oldPassword, String newPassword); 17 | 18 | void resetPassword(Long accountId); 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/impl/domain/event/AccountEventProcessor.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.impl.domain.event; 2 | 3 | import org.mvnsearch.demo.domain.event.AccountEvent; 4 | import org.springframework.context.event.EventListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * account processor implementation 9 | * 10 | * @author linux_china 11 | */ 12 | @Component 13 | public class AccountEventProcessor { 14 | 15 | @EventListener(AccountEvent.class) 16 | public void handleAccountCreate(AccountEvent accountEvent) { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/jackson/CloudEventsModule.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.events.jackson; 2 | 3 | import com.fasterxml.jackson.databind.module.SimpleModule; 4 | 5 | import java.time.ZonedDateTime; 6 | 7 | /** 8 | * Jackson CloudEvents Module 9 | * 10 | * @author linux_china 11 | */ 12 | public class CloudEventsModule extends SimpleModule { 13 | public CloudEventsModule() { 14 | this.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer()); 15 | this.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/event/AccountEvent.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.event; 2 | 3 | import org.mvnsearch.ddd.domain.events.DomainEvent; 4 | import org.mvnsearch.demo.domain.model.Account; 5 | 6 | /** 7 | * account event 8 | * 9 | * @author linux_china 10 | */ 11 | public class AccountEvent extends DomainEvent { 12 | public static String CREATED_TYPE = "created"; 13 | public static String BLOCKED_TYPE = "blocked"; 14 | 15 | public AccountEvent(String type, Account account) { 16 | setDataContentType(type); 17 | setData(account); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/impl/domain/model/AccountRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.impl.domain.model; 2 | 3 | import org.mvnsearch.demo.domain.model.Account; 4 | import org.mvnsearch.demo.domain.model.AccountRepository; 5 | 6 | /** 7 | * account repository implementation 8 | * 9 | * @author linux_china 10 | */ 11 | public class AccountRepositoryImpl implements AccountRepository { 12 | public void create(Account account) { 13 | 14 | } 15 | 16 | public void update(Account account) { 17 | 18 | } 19 | 20 | public Account findOne(long id) { 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/puml/event-class.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | class BaseDomainEvent { 4 | + Object source 5 | + Date occuredAt 6 | + Map context 7 | } 8 | 9 | class EventHandler { 10 | + boolean canHandle(Object event); 11 | + void handle(Object event); 12 | } 13 | 14 | class DomainEventDispatcher { 15 | + void publish(BaseDomainEvent event) 16 | + void register(EventHandler handler) 17 | } 18 | 19 | class DomainEventStore 20 | 21 | DomainEventDispatcher -down-> DomainEventStore: save 22 | DomainEventDispatcher <-up- DomainEventStore: get 23 | 24 | DomainEventDispatcher -up-* EventHandler: register 25 | 26 | DomainEventDispatcher ---> BaseDomainEvent: Publish 27 | 28 | EventHandler --> BaseDomainEvent: handle 29 | 30 | @enduml -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/annotations/Identity.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Declares a field of a class to constitute the identity of the corresponding class. Primarily used in 7 | * {@link DomainAggregate} and {@link DomainEntity} types. 8 | * 9 | * @author Oliver Drotbohm 10 | * @author Stephan Pirnbaum 11 | * @author Henning Schwentner 12 | * @see Domain-Driven Design 13 | * Reference (Evans) - Entities 14 | * @since 1.3 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) 18 | @Documented 19 | public @interface Identity { 20 | } -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | # .github/workflows/maven.yml 4 | 5 | name: Java CI with Maven 6 | 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up JDK 1.8 21 | uses: actions/setup-java@v2 22 | with: 23 | distribution: "adopt" 24 | java-version: "8" 25 | cache: 'maven' 26 | - name: Build with Maven 27 | run: mvn -B -DskipTests package --file pom.xml 28 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/CloudEventHandler.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.events; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 8 | import static java.lang.annotation.ElementType.METHOD; 9 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 10 | 11 | /** 12 | * Cloud Event Handler 13 | * 14 | * @author linux_china 15 | */ 16 | @Target(value = {METHOD, ANNOTATION_TYPE}) 17 | @Retention(value = RUNTIME) 18 | @Documented 19 | public @interface CloudEventHandler { 20 | String type(); 21 | 22 | String source(); 23 | 24 | String subject(); 25 | 26 | String[] extensions(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/proto/cloudevent.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package io.cloudevents.protobuf; 4 | option java_outer_classname = "CloudEventsProto"; 5 | 6 | 7 | // cloud event proto message 8 | message CloudEvent { 9 | string id = 1; 10 | string specversion = 2; 11 | string type = 3; 12 | string source = 4; 13 | string time = 5; 14 | string dataschema = 6; 15 | string datacontenttype = 7; 16 | string subject = 8; 17 | string data_base64 = 18; 18 | CloudEventAnyValue data = 19; 19 | map extensions = 20; 20 | } 21 | 22 | message CloudEventAnyValue { 23 | oneof value { 24 | string string_value = 1; 25 | int32 int_value = 2; 26 | int64 long_value = 3; 27 | double double_value = 4; 28 | bool bool_value = 5; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/impl/infra/CacheServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.impl.infra; 2 | 3 | import org.mvnsearch.demo.infra.CacheService; 4 | 5 | import java.io.Serializable; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * cache service implementation 12 | * 13 | * @author linux_china 14 | */ 15 | public class CacheServiceImpl implements CacheService { 16 | private Map store = new ConcurrentHashMap<>(); 17 | 18 | public void put(String key, Serializable object) { 19 | store.put(key, object); 20 | } 21 | 22 | public Optional get(String key) { 23 | if (store.containsKey(key)) { 24 | return Optional.of(store.get(key)); 25 | } else { 26 | return Optional.empty(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/model/Order.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.model; 2 | 3 | import org.mvnsearch.ddd.domain.BaseDomainAggregate; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Order: aggregate 9 | * 10 | * @author linux_china 11 | */ 12 | public class Order implements BaseDomainAggregate { 13 | private Long id; 14 | private List items; 15 | private Account buyer; 16 | 17 | @Override 18 | public Long getId() { 19 | return null; 20 | } 21 | 22 | public List getItems() { 23 | return items; 24 | } 25 | 26 | public void setItems(List items) { 27 | this.items = items; 28 | } 29 | 30 | public Account getBuyer() { 31 | return buyer; 32 | } 33 | 34 | public void setBuyer(Account buyer) { 35 | this.buyer = buyer; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/puml/ddd-module.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | package "openApi1" { 4 | package idl1 { 5 | 6 | } 7 | } 8 | 9 | package "openApi2" { 10 | package idl2 { 11 | 12 | } 13 | } 14 | 15 | package "client" { 16 | 17 | } 18 | package "application" { 19 | package "appServiceImpl" { 20 | } 21 | } 22 | 23 | package "domain" { 24 | package "model" { 25 | package "account" { 26 | 27 | } 28 | } 29 | package "factory" { 30 | } 31 | 32 | package "event" { 33 | 34 | } 35 | 36 | package "shared" { 37 | } 38 | 39 | package "service" { 40 | } 41 | 42 | package "policy" { 43 | } 44 | 45 | } 46 | 47 | package "infrastructure" { 48 | package "cache" 49 | package "db" 50 | package "email" 51 | package "rpc" 52 | package "domainImpl" 53 | } 54 | 55 | infrastructure --up-> domain 56 | application --down-> domain 57 | application --up-> openApi1 58 | domain --> openApi1 59 | 60 | client ---> openApi1 61 | client ---> openApi2 62 | 63 | @enduml -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/impl/domain/service/AccountServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.impl.domain.service; 2 | 3 | import org.mvnsearch.demo.domain.event.AccountEvent; 4 | import org.mvnsearch.demo.domain.model.Account; 5 | import org.mvnsearch.demo.domain.service.AccountService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.ApplicationEventPublisher; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * account service implementation 12 | * 13 | * @author linux_china 14 | */ 15 | @Service 16 | public class AccountServiceImpl implements AccountService { 17 | @Autowired 18 | private ApplicationEventPublisher eventPublisher; 19 | 20 | public void create(Account account) { 21 | //todo implement create logic 22 | eventPublisher.publishEvent(new AccountEvent(AccountEvent.CREATED_TYPE, account)); 23 | } 24 | 25 | public void updatePassword(Long accountId, String oldPassword, String newPassword) { 26 | 27 | } 28 | 29 | public void resetPassword(Long accountId) { 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ### Java template 3 | *.class 4 | 5 | # Mobile Tools for Java (J2ME) 6 | .mtj.tmp/ 7 | 8 | # Package Files # 9 | *.jar 10 | *.war 11 | *.ear 12 | 13 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 14 | hs_err_pid* 15 | 16 | 17 | ### JetBrains template 18 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 19 | 20 | *.iml 21 | 22 | ## Directory-based project format: 23 | .idea/ 24 | # if you remove the above rule, at least ignore the following: 25 | 26 | # User-specific stuff: 27 | # .idea/workspace.xml 28 | # .idea/tasks.xml 29 | # .idea/dictionaries 30 | 31 | # Sensitive or high-churn files: 32 | # .idea/dataSources.ids 33 | # .idea/dataSources.xml 34 | # .idea/sqlDataSources.xml 35 | # .idea/dynamic.xml 36 | # .idea/uiDesigner.xml 37 | 38 | # Gradle: 39 | # .idea/gradle.xml 40 | # .idea/libraries 41 | 42 | # Mongo Explorer plugin: 43 | # .idea/mongoSettings.xml 44 | 45 | ## File-based project format: 46 | *.ipr 47 | *.iws 48 | 49 | ## Plugin-specific files: 50 | 51 | # IntelliJ 52 | out/ 53 | 54 | # mpeltonen/sbt-idea plugin 55 | .idea_modules/ 56 | 57 | # JIRA plugin 58 | atlassian-ide-plugin.xml 59 | 60 | # Crashlytics plugin (for Android Studio and IntelliJ) 61 | com_crashlytics_export_strings.xml 62 | crashlytics.properties 63 | crashlytics-build.properties 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/jackson/DomainEventDataFieldFilter.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.events.jackson; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.SerializerProvider; 5 | import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; 6 | import com.fasterxml.jackson.databind.ser.PropertyWriter; 7 | import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; 8 | import org.mvnsearch.ddd.domain.events.DomainEvent; 9 | 10 | import java.util.Base64; 11 | 12 | /** 13 | * Domain Event data field filter: change data to data_base64 when data's type is byte[] 14 | * 15 | * @author linux_china 16 | */ 17 | public class DomainEventDataFieldFilter extends SimpleBeanPropertyFilter { 18 | 19 | @Override 20 | protected boolean include(BeanPropertyWriter writer) { 21 | return super.include(writer); 22 | } 23 | 24 | @Override 25 | protected boolean include(PropertyWriter writer) { 26 | return super.include(writer); 27 | } 28 | 29 | public void serializeAsField(Object pojo, JsonGenerator gen, SerializerProvider prov, 30 | PropertyWriter writer) 31 | throws Exception { 32 | if (writer.getName().equals("data")) { 33 | Object data = ((DomainEvent) pojo).getData(); 34 | if (data instanceof byte[]) { 35 | gen.writeStringField("data_base64", Base64.getEncoder().encodeToString((byte[]) data)); 36 | return; 37 | } 38 | } 39 | super.serializeAsField(pojo, gen, prov, writer); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/demo/domain/model/Account.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.demo.domain.model; 2 | 3 | import org.mvnsearch.ddd.domain.BaseDomainEntity; 4 | import org.mvnsearch.ddd.domain.annotations.DomainEntity; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | * account entity 10 | * 11 | * @author linux_china 12 | */ 13 | @DomainEntity 14 | public class Account implements BaseDomainEntity{ 15 | private Long id; 16 | private String name; 17 | private String email; 18 | private String password; 19 | private Integer status; 20 | private Date createdAt; 21 | 22 | public Long getId() { 23 | return id; 24 | } 25 | 26 | public void setId(Long id) { 27 | this.id = id; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | public String getEmail() { 39 | return email; 40 | } 41 | 42 | public void setEmail(String email) { 43 | this.email = email; 44 | } 45 | 46 | public String getPassword() { 47 | return password; 48 | } 49 | 50 | public void setPassword(String password) { 51 | this.password = password; 52 | } 53 | 54 | public Integer getStatus() { 55 | return status; 56 | } 57 | 58 | public void setStatus(Integer status) { 59 | this.status = status; 60 | } 61 | 62 | public Date getCreatedAt() { 63 | return createdAt; 64 | } 65 | 66 | public void setCreatedAt(Date createdAt) { 67 | this.createdAt = createdAt; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/jackson/ZonedDateTimeSerializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The CloudEvents Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mvnsearch.ddd.domain.events.jackson; 17 | 18 | import com.fasterxml.jackson.core.JsonGenerator; 19 | import com.fasterxml.jackson.databind.SerializerProvider; 20 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 21 | 22 | import java.io.IOException; 23 | import java.time.ZonedDateTime; 24 | import java.time.format.DateTimeFormatter; 25 | 26 | public class ZonedDateTimeSerializer extends StdSerializer { 27 | 28 | private static final long serialVersionUID = 6245182835980474796L; 29 | 30 | public ZonedDateTimeSerializer() { 31 | this(null, false); 32 | } 33 | 34 | protected ZonedDateTimeSerializer(Class t, boolean dummy) { 35 | super(t, dummy); 36 | } 37 | 38 | @Override 39 | public void serialize(ZonedDateTime time, JsonGenerator generator, 40 | SerializerProvider provider) throws IOException { 41 | 42 | generator.writeString(time.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/puml/ddd-components.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | 4 | node "Domain Module" { 5 | package "model package" { 6 | [Model] <> 7 | [Repository] <> 8 | [Factory] <> 9 | } 10 | [Service] <> 11 | [Event] <> 12 | [Policy] <> 13 | [Shared] 14 | note right of Shared : XxxSupport\nWrapper\nSpring Integration 15 | 16 | package "application package" { 17 | [XxxFacade] 18 | note left of XxxFacade : facade for external 19 | } 20 | package "infrastructure package" { 21 | [Infrastructure] #Aqua 22 | [DomainImpl] <> 23 | [DomainImpl] -> [Infrastructure] 24 | } 25 | } 26 | 27 | cloud "App Modules" { 28 | [WebApp] <> 29 | [MobileBackend] <> 30 | [Interface] <> 31 | [MobileApp] <> 32 | } 33 | 34 | [Shared] <-- [DomainImpl] 35 | [XxxFacade] <--- [DomainImpl] 36 | [Repository] ---> [Shared] 37 | [Repository] --> [Model] 38 | [Service] -[#0000FF]> [Repository] 39 | [Service] -[#0000FF]> [Factory] 40 | [Service] -[#0000FF]> [Policy] 41 | [Service] <-[#0000FF]> [Event] 42 | [Policy] -[#0000FF]> [Repository] 43 | [Factory] --> [Model] 44 | [Factory] ---> [Shared] 45 | [Event] -[#0000FF]> [Repository] 46 | [Event] -[#0000FF]> [Factory] 47 | [XxxFacade] -[#0000FF]> [Service] 48 | [XxxFacade] -[#0000FF]> [Event] 49 | [XxxFacade] -[#0000FF]> [Policy] 50 | 51 | [WebApp] ...> [XxxFacade] 52 | [Interface] --> [WebApp] 53 | [MobileBackend] ...> [XxxFacade] 54 | [MobileApp] --> [MobileBackend] 55 | 56 | skinparam component { 57 | FontSize 13 58 | BackgroundColor<> gold 59 | BackgroundColor<> DarkKhaki 60 | FontName Courier 61 | } 62 | 63 | @enduml -------------------------------------------------------------------------------- /src/test/java/org/mvnsearch/ddd/domain/events/DomainEventTest.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.events; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.net.URI; 7 | 8 | /** 9 | * Domain Event test 10 | * 11 | * @author linux_china 12 | */ 13 | public class DomainEventTest { 14 | 15 | @Test 16 | public void testEncodeDecode() { 17 | DomainEvent event = DomainEventBuilder.newInstance() 18 | .id("xxx") 19 | .dataContentType("text/plain") 20 | .data("1234") 21 | .source(URI.create("demo")) 22 | .type("demo") 23 | .sequence("1234") 24 | .build(); 25 | String jsonText = Json.encode(event); 26 | System.out.println(jsonText); 27 | DomainEvent event2 = Json.decodeValue(jsonText, DomainEvent.class); 28 | Assertions.assertEquals(event2.getExtension("sequence"), "1234"); 29 | } 30 | 31 | @Test 32 | public void testBinaryEvent() { 33 | DomainEvent event = DomainEventBuilder.newInstance() 34 | .id("xxx") 35 | .dataContentType("text/plain") 36 | .data("good".getBytes()) 37 | .source(URI.create("demo")) 38 | .type("demo") 39 | .sequence("11234") 40 | .build(); 41 | String jsonText = Json.encode(event); 42 | System.out.println(jsonText); 43 | Assertions.assertFalse(jsonText.contains("\"data\":")); 44 | Assertions.assertTrue(jsonText.contains("\"data_base64\":")); 45 | DomainEvent domainEvent = Json.decodeValue(jsonText, DomainEvent.class); 46 | Assertions.assertEquals("good", new String(domainEvent.getData())); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/jackson/ZonedDateTimeDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The CloudEvents Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mvnsearch.ddd.domain.events.jackson; 17 | 18 | import com.fasterxml.jackson.core.JsonParser; 19 | import com.fasterxml.jackson.databind.DeserializationContext; 20 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 21 | 22 | import java.io.IOException; 23 | import java.time.DateTimeException; 24 | import java.time.ZonedDateTime; 25 | import java.time.format.DateTimeFormatter; 26 | 27 | public class ZonedDateTimeDeserializer extends StdDeserializer { 28 | 29 | public ZonedDateTimeDeserializer() { 30 | this(null); 31 | } 32 | 33 | public ZonedDateTimeDeserializer(Class vc) { 34 | super(vc); 35 | } 36 | 37 | @Override 38 | public ZonedDateTime deserialize(JsonParser jsonparser, DeserializationContext ctxt) throws IOException { 39 | // not serializing timezone data yet 40 | try { 41 | return ZonedDateTime.parse(jsonparser.getText(), DateTimeFormatter.ISO_OFFSET_DATE_TIME); 42 | } catch (DateTimeException e) { 43 | throw new IllegalArgumentException("could not parse"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/puml/ddd-components-rpc.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | node "domain module" { 4 | package "application package" { 5 | [XxxFacadeImpl] <> 6 | note right of XxxFacadeImpl : published service implementation 7 | } 8 | 9 | package "domain package" { 10 | [Model] <> 11 | [Repository] <> 12 | [Factory] <> 13 | [Service] <> 14 | [Event] <> 15 | [Policy] <> 16 | } 17 | 18 | package "infrastructure package" { 19 | [InfrastructureService] <> #Aqua 20 | [DomainImpl] <> 21 | [DomainImpl] -> [InfrastructureService] 22 | [DomainImpl] -up-|> [Service]: 逻辑实现 23 | [DomainImpl] -up-|> [Repository]: 逻辑实现 24 | [DomainImpl] -up-|> [Factory]: 逻辑实现 25 | [DomainImpl] -up-|> [Policy]: 逻辑实现 26 | } 27 | } 28 | 29 | package "Client Module" { 30 | package "Exposed API Module: Facade pattern" { 31 | [XxxFacade] 32 | note left of XxxFacade : models\npublished service api 33 | } 34 | [XxxClient] #99FF99 35 | note right of XxxClient : DSL\nSpring Support\nException Handler 36 | [XxxClient] -left-> [XxxFacade] 37 | } 38 | 39 | cloud "App Modules" { 40 | [WebApp] <> 41 | [MobileBackend] <> 42 | [Interface] <> 43 | [MobileApp] <> 44 | } 45 | 46 | [XxxFacadeImpl] <--- [DomainImpl] 47 | [Service] -[#0000FF]> [Factory] 48 | [Service] -[#0000FF]> [Repository] 49 | [Service] .[#0000FF]> [Policy] 50 | [Service] -[#0000FF]up-> [Event] 51 | [Policy] -[#0000FF]> [Repository] 52 | [Repository] -[#0000FF]up-> [Model] 53 | [Factory] -[#0000FF]up-> [Model] 54 | 55 | [XxxFacadeImpl] -[#0000FF]--> [Service] 56 | [XxxFacadeImpl] -[#0000FF]--> [Event] 57 | [XxxFacadeImpl] -[#0000FF]--> [Model] 58 | [XxxFacadeImpl] -[#0000FF]--> [Policy] 59 | [XxxFacadeImpl] -[#0000FF]--> [Repository] 60 | [XxxFacadeImpl] -[#0000FF]----> [InfrastructureService] 61 | 62 | [XxxFacade] ..> [XxxFacadeImpl]: RPC 63 | [XxxFacade] <|-- [XxxFacadeImpl]: Logic Implement 64 | 65 | [WebApp] ..> [XxxClient] 66 | [WebApp] ..> [XxxFacade] 67 | [Interface] --> [WebApp] 68 | [MobileBackend] ..> [XxxClient] 69 | [MobileBackend] ..> [XxxFacade] 70 | [MobileApp] --> [MobileBackend] 71 | 72 | skinparam component { 73 | FontSize 13 74 | BackgroundColor<> gold 75 | BackgroundColor<> DarkKhaki 76 | FontName Courier 77 | } 78 | 79 | @enduml -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/ExtensionAttribute.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.events; 2 | 3 | /** 4 | * CloudEvent Extension Attribute enum 5 | * 6 | * @author linux_china 7 | */ 8 | public enum ExtensionAttribute { 9 | /** 10 | * A reference to a location where the event payload is stored 11 | * https://github.com/cloudevents/spec/blob/master/extensions/dataref.md 12 | */ 13 | DATA_REF("dataref"), 14 | /** 15 | * Contains a version, trace ID, span ID, and trace options 16 | * https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md 17 | */ 18 | TRACE_PARENT("traceparent"), 19 | /** 20 | * a comma-delimited list of key-value pairs 21 | * https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md 22 | */ 23 | TRACE_STATE("tracestate"), 24 | /** 25 | * Globally unique trace id 26 | * https://github.com/opentracing/specification/blob/master/rfc/trace_identifiers.md 27 | */ 28 | TO_TRACE_ID("totraceid"), 29 | /** 30 | * Unique within a trace 31 | * https://github.com/opentracing/specification/blob/master/rfc/trace_identifiers.md 32 | */ 33 | TO_SPAN_ID("tospanid"), 34 | /** 35 | * partition key for the event, typically for the purposes of defining a causal relationship/grouping between multiple events 36 | * https://github.com/cloudevents/spec/blob/master/extensions/partitioning.md 37 | */ 38 | PARTITION_KEY("partitionkey"), 39 | /** 40 | * The rate at which this event has already been sampled 41 | * https://github.com/cloudevents/spec/blob/master/extensions/sampled-rate.md 42 | */ 43 | SAMPLED_RATE("sampledrate"), 44 | /** 45 | * Value expressing the relative order of the event 46 | * https://github.com/cloudevents/spec/blob/master/extensions/sequence.md 47 | */ 48 | SEQUENCE("sequence"), 49 | /** 50 | * Specifies the semantics of the sequence attribute 51 | * https://github.com/cloudevents/spec/blob/master/extensions/sequence.md 52 | */ 53 | SEQUENCE_TYPE("sequencetype"), 54 | /** 55 | * tenant id 56 | */ 57 | TENANT_ID("tenantid"), 58 | /** 59 | * event rouging 60 | */ 61 | ROUTING("routing"), 62 | /** 63 | * event signature, please refer DKIM format 64 | */ 65 | SIGNATURE("signature"); 66 | 67 | private final String key; 68 | 69 | ExtensionAttribute(String key) { 70 | this.key = key; 71 | } 72 | 73 | public String getKey() { 74 | return key; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/DomainEventBuilder.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.events; 2 | 3 | import java.net.URI; 4 | import java.time.ZonedDateTime; 5 | import java.util.Map; 6 | 7 | /** 8 | * domain event builder 9 | * 10 | * @author linux_china 11 | */ 12 | public class DomainEventBuilder { 13 | /** 14 | * domain event 15 | */ 16 | private DomainEvent domainEvent; 17 | 18 | public static DomainEventBuilder newInstance() { 19 | DomainEventBuilder builder = new DomainEventBuilder(); 20 | builder.domainEvent = new DomainEvent(); 21 | return builder; 22 | } 23 | 24 | public static DomainEventBuilder> newInstance(Map data) { 25 | DomainEventBuilder> builder = new DomainEventBuilder>(); 26 | builder.domainEvent = new MapDomainEvent(); 27 | builder.domainEvent.setDataContentType("application/json"); 28 | builder.data(data); 29 | return builder; 30 | } 31 | 32 | public DomainEventBuilder id(String eventId) { 33 | domainEvent.setId(eventId); 34 | return this; 35 | } 36 | 37 | public DomainEventBuilder type(String eventType) { 38 | domainEvent.setType(eventType); 39 | return this; 40 | } 41 | 42 | public DomainEventBuilder source(URI source) { 43 | domainEvent.setSource(source); 44 | return this; 45 | } 46 | 47 | 48 | public DomainEventBuilder dataContentType(String contentType) { 49 | domainEvent.setDataContentType(contentType); 50 | return this; 51 | } 52 | 53 | public DomainEventBuilder data(T data) { 54 | domainEvent.setData(data); 55 | return this; 56 | } 57 | 58 | public DomainEventBuilder dataSchema(URI schemaURL) { 59 | domainEvent.setDataSchema(schemaURL); 60 | return this; 61 | } 62 | 63 | public DomainEventBuilder time(ZonedDateTime eventTime) { 64 | domainEvent.setTime(eventTime); 65 | return this; 66 | } 67 | 68 | public DomainEventBuilder extensions(Map extensions) { 69 | domainEvent.setExtensions(extensions); 70 | return this; 71 | } 72 | 73 | public DomainEventBuilder extension(String name, Object value) { 74 | domainEvent.setExtension(name, value); 75 | return this; 76 | } 77 | 78 | public DomainEventBuilder dataref(URI dataref) { 79 | domainEvent.setExtension(ExtensionAttribute.DATA_REF.getKey(), dataref); 80 | return this; 81 | } 82 | 83 | public DomainEventBuilder traceparent(String traceparent) { 84 | domainEvent.setExtension(ExtensionAttribute.TRACE_PARENT.getKey(), traceparent); 85 | return this; 86 | } 87 | 88 | public DomainEventBuilder tracestate(String tracestate) { 89 | domainEvent.setExtension(ExtensionAttribute.TRACE_STATE.getKey(), tracestate); 90 | return this; 91 | } 92 | 93 | public DomainEventBuilder totraceid(String totraceid) { 94 | domainEvent.setExtension(ExtensionAttribute.TO_TRACE_ID.getKey(), totraceid); 95 | return this; 96 | } 97 | 98 | public DomainEventBuilder toppanid(String toSpanId) { 99 | domainEvent.setExtension(ExtensionAttribute.TO_SPAN_ID.getKey(), toSpanId); 100 | return this; 101 | } 102 | 103 | public DomainEventBuilder partitionkey(String partitionkey) { 104 | domainEvent.setExtension(ExtensionAttribute.PARTITION_KEY.getKey(), partitionkey); 105 | return this; 106 | } 107 | 108 | public DomainEventBuilder sampledrate(Integer sampledrate) { 109 | domainEvent.setExtension(ExtensionAttribute.SAMPLED_RATE.getKey(), sampledrate); 110 | return this; 111 | } 112 | 113 | public DomainEventBuilder sequence(String sequence) { 114 | domainEvent.setExtension(ExtensionAttribute.SEQUENCE.getKey(), sequence); 115 | return this; 116 | } 117 | 118 | public DomainEventBuilder tenantid(String tenantId) { 119 | domainEvent.setExtension(ExtensionAttribute.TENANT_ID.getKey(), tenantId); 120 | return this; 121 | } 122 | 123 | public DomainEventBuilder signature(String signature) { 124 | domainEvent.setExtension(ExtensionAttribute.SIGNATURE.getKey(), signature); 125 | return this; 126 | } 127 | 128 | public DomainEventBuilder routing(String routing) { 129 | domainEvent.setExtension(ExtensionAttribute.ROUTING.getKey(), routing); 130 | return this; 131 | } 132 | 133 | public DomainEventBuilder spec(String specVersion) { 134 | domainEvent.setSpecVersion(specVersion); 135 | return this; 136 | } 137 | 138 | public DomainEvent build() { 139 | return domainEvent; 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DDD Base 2 | ======== 3 | !["Building Status"](https://img.shields.io/github/workflow/status/linux-china/ddd-base/Java%20CI%20with%20Maven) 4 | 5 | Domain Driven Design base package for Java. 6 | 7 | # How to use it in Java? 8 | 9 | Please refer https://jitpack.io/#linux-china/ddd-base/1.1.1 10 | 11 | # Features 12 | 13 | * Annotations 14 | * Base classes for entity, domain event etc 15 | * Domain event: follow CloudEvents specification and CloudEvent convert support 16 | * DDD Reactive: https://www.reactive-streams.org/ 17 | 18 | # Components 19 | 20 | * Data: Entity, VO and Aggregate 21 | * Behaviour: Repository, Service, Factory and Specification 22 | * Event: Follow CloudEvents spec 23 | * Infrastructure 24 | * CQRS interfaces for command & query 25 | 26 | # Reactive 27 | 28 | DDD + Reactive(RSocket) to make context map easy. 29 | 30 | # Code Structure 31 | 32 | Please visit [src/test/java](https://github.com/linux-china/ddd-base/tree/master/src/test/java/org/mvnsearch/demo/domain) for code structure 33 | 34 | If you use Kotlin to develop application, the structure will be different, please add entity, vo and repository in the same kt file. 35 | 36 | # Events 37 | 38 | Please extend DomainEvent or DomainEventBuilder, then use ApplicationEventPublisher to publish the event. please 39 | refer https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2 40 | 41 | Attention: Spring framework 5.2 will add reactive support: https://github.com/spring-projects/spring-framework/issues/21831 42 | 43 | Event extensions(JavaScript Object): 44 | 45 | * Distributed Tracing extension(traceparent, tracestate): embeds context from Distributed Tracing so that distributed systems can include traces that span an event-driven system. 46 | * Dataref(dataref): reference another location where this information is stored 47 | * Partitioning(partitionkey): This extension defines an attribute for use by message brokers and their clients that support partitioning of events, typically for the purpose of 48 | scaling. 49 | * Sequence(sequence): describe the position of an event in the ordered sequence of events produced by a unique event source 50 | * Sampling(sampledrate): Sampling 51 | * Multi-tenant(tenantId): Multi-tenant system support 52 | 53 | CloudEvents JSONSchema: https://github.com/cloudevents/spec/blob/v0.3/spec.json 54 | 55 | ### How to create event class 56 | 57 | * Extend CloudEvent class: 58 | 59 | ```java 60 | public class LoginEvent extends CloudEvent { 61 | 62 | public LoginEvent(String email, String ip) { 63 | setData(email); 64 | setContentType("text/plain"); 65 | setExtension("ip", ip); 66 | } 67 | } 68 | ``` 69 | 70 | * Create an event directly 71 | 72 | ``` 73 | CloudEvent loginEvent = new CloudEvent("text/plain", "linux_china@hotmail.com"); 74 | ``` 75 | 76 | * Event Builder or reactive processor 77 | 78 | ``` 79 | CloudEvent loginEvent = CloudEventBuilder.newInstance().contentType("text/plain").data("linux_china@hotmail.com").build(); 80 | ``` 81 | 82 | * Non-blocking Event Listener: https://github.com/spring-projects/spring-framework/issues/21831 83 | 84 | ```java 85 | 86 | @Component 87 | public class MyListener { 88 | 89 | @EventListener 90 | public Mono handleContextRefresh(ContextRefreshedEvent event) { 91 | //... 92 | } 93 | 94 | @EventListener 95 | public Flux handleBusinessEvent(MyBusinessEvent event) { 96 | //... 97 | } 98 | 99 | @EventListener 100 | public CompletableFuture handleBusinessEvent(MyOtherBusinessEvent event) { 101 | //... 102 | } 103 | } 104 | ``` 105 | 106 | ### ObjectMapper 107 | 108 | * ObjectMapper creation 109 | 110 | ``` 111 | ObjectMapper objectMapper = new ObjectMapper(); 112 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 113 | objectMapper.enable(SerializationFeature.INDENT_OUTPUT); 114 | ``` 115 | 116 | * write as String 117 | 118 | ``` 119 | objectMapper.writeValueAsString(loginEvent); 120 | ``` 121 | 122 | * read from json text 123 | 124 | ``` 125 | objectMapper.readValue(jsonText, new TypeReference>() {}); 126 | ``` 127 | 128 | ### References 129 | 130 | * CloudEvents Specification: https://github.com/cloudevents/spec/blob/master/spec.md 131 | * Reactive Streams: http://www.reactive-streams.org/ 132 | * DDD Reference: https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf 133 | * ddd-4-java: Base classes for Domain Driven Design with Java https://github.com/fuinorg/ddd-4-java 134 | * jMolecules – Architectural abstractions for Java with DDD concepts https://github.com/xmolecules/jmolecules 135 | * Domain-Driven Design Crew: https://github.com/ddd-crew 136 | * What is Event Sourcing? https://www.eventstore.com/blog/what-is-event-sourcing 137 | * Event Sourcing and CQRS: https://www.eventstore.com/blog/event-sourcing-and-cqrs 138 | * CQRS Document: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf 139 | * remesh: a DDD framework for large and complex TypeScript/JavaScript applications - https://github.com/remesh-js/remesh -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/Json.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The CloudEvents Authors 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mvnsearch.ddd.domain.events; 17 | 18 | import com.fasterxml.jackson.annotation.JsonInclude; 19 | import com.fasterxml.jackson.core.type.TypeReference; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import com.fasterxml.jackson.databind.ser.FilterProvider; 22 | import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; 23 | import org.mvnsearch.ddd.domain.events.jackson.DomainEventDataFieldFilter; 24 | 25 | import java.io.InputStream; 26 | 27 | public final class Json { 28 | /** 29 | * Domain Event data field filter 30 | */ 31 | private static FilterProvider domainEventDataFieldFilter = new SimpleFilterProvider().addFilter("DomainEventDataFieldFilter", new DomainEventDataFieldFilter()); 32 | 33 | public static final ObjectMapper MAPPER = new ObjectMapper(); 34 | 35 | static { 36 | MAPPER.findAndRegisterModules(); 37 | MAPPER.setSerializationInclusion(JsonInclude.Include.NON_ABSENT); 38 | } 39 | 40 | /** 41 | * Encode a POJO to JSON using the underlying Jackson mapper. 42 | * 43 | * @param event domain event 44 | * @return a String containing the JSON representation of the given POJO. 45 | * @throws IllegalStateException if a property cannot be encoded. 46 | */ 47 | @SuppressWarnings("unchecked") 48 | public static String encode(final DomainEvent event) throws IllegalStateException { 49 | try { 50 | return MAPPER.writer(domainEventDataFieldFilter).writeValueAsString(event); 51 | } catch (Exception e) { 52 | throw new IllegalStateException("Failed to encode as JSON: " + e.getMessage()); 53 | } 54 | } 55 | 56 | public static DomainEvent fromInputStream(final InputStream inputStream) { 57 | try { 58 | return MAPPER.readValue(inputStream, DomainEvent.class); 59 | } catch (Exception e) { 60 | throw new IllegalStateException("Failed to encode as JSON: " + e.getMessage()); 61 | } 62 | } 63 | 64 | /** 65 | * Decode a given JSON string to a CloudEvent . 66 | * 67 | * @param str the JSON string. 68 | * @return an instance of CloudEvent 69 | * @throws IllegalStateException when there is a parsing or invalid mapping. 70 | */ 71 | public static DomainEvent decodeDomainEvent(final String str) throws IllegalStateException { 72 | return decodeValue(str, DomainEvent.class); 73 | } 74 | 75 | /** 76 | * Decode a given JSON string to a POJO of the given class type. 77 | * 78 | * @param str the JSON string. 79 | * @param clazz the class to map to. 80 | * @param the generic type. 81 | * @return an instance of T 82 | * @throws IllegalStateException when there is a parsing or invalid mapping. 83 | */ 84 | protected static T decodeValue(final String str, final Class clazz) throws IllegalStateException { 85 | try { 86 | return MAPPER.readValue(str, clazz); 87 | } catch (Exception e) { 88 | throw new IllegalStateException("Failed to decode: " + e.getMessage()); 89 | } 90 | } 91 | 92 | 93 | public static byte[] encodePOJOAsBytes(final Object pojo) throws IllegalStateException { 94 | try { 95 | return MAPPER.writeValueAsBytes(pojo); 96 | } catch (Exception e) { 97 | throw new IllegalStateException("Failed to encode as JSON: " + e.getMessage()); 98 | } 99 | } 100 | 101 | public static T decodePOJO(final byte[] bytes, Class clazz) throws IllegalStateException { 102 | try { 103 | return MAPPER.readValue(bytes, clazz); 104 | } catch (Exception e) { 105 | throw new IllegalStateException("Failed to encode as JSON: " + e.getMessage()); 106 | } 107 | } 108 | 109 | /** 110 | * Decode a given JSON string to a POJO of the given type. 111 | * 112 | * @param str the JSON string. 113 | * @param type the type to map to. 114 | * @param the generic type. 115 | * @return an instance of T 116 | * @throws IllegalStateException when there is a parsing or invalid mapping. 117 | */ 118 | public static T decodeValue(final String str, final TypeReference type) throws IllegalStateException { 119 | try { 120 | return MAPPER.readValue(str, type); 121 | } catch (Exception e) { 122 | throw new IllegalStateException("Failed to decode: " + e.getMessage(), e); 123 | } 124 | } 125 | 126 | private Json() { 127 | // no-op 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.mvnsearch 7 | ddd-base 8 | 1.2.0 9 | jar 10 | 11 | DDD Base 12 | DDD Base support with Java annotations, base interfaces and event 13 | https://github.com/linux-china/ddd-base 14 | 15 | 16 | UTF-8 17 | 1.8 18 | ${java.version} 19 | ${java.version} 20 | 2.13.4 21 | 5.9.1 22 | 23 | 24 | 25 | 26 | linux_china 27 | Jacky Chan 28 | libing.chen@gmail.com 29 | https://twitter.com/linux_china 30 | 31 | Developer 32 | 33 | 34 | 35 | 36 | 37 | 38 | The Apache License, Version 2.0 39 | https://www.apache.org/licenses/LICENSE-2.0.txt 40 | 41 | 42 | 43 | 44 | scm:git:ssh://git@github.com:linux-china/ddd-base.git 45 | scm:git:ssh://git@github.com:linux-china/ddd-base.git 46 | https://github.com/linux-china/ddd-base 47 | 48 | 49 | 50 | 51 | ossrh 52 | https://oss.sonatype.org/content/repositories/snapshots 53 | 54 | 55 | ossrh 56 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 57 | 58 | 59 | 60 | 61 | 62 | org.jetbrains 63 | annotations 64 | 23.0.0 65 | 66 | 67 | io.cloudevents 68 | cloudevents-core 69 | 2.4.0 70 | 71 | 72 | com.fasterxml.jackson.core 73 | jackson-annotations 74 | ${jackson.version} 75 | 76 | 77 | com.fasterxml.jackson.core 78 | jackson-databind 79 | ${jackson.version} 80 | true 81 | 82 | 83 | com.fasterxml.jackson.datatype 84 | jackson-datatype-jdk8 85 | ${jackson.version} 86 | true 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-starter-test 91 | 2.7.5 92 | test 93 | 94 | 95 | org.slf4j 96 | slf4j-api 97 | 1.7.36 98 | 99 | 100 | org.junit.jupiter 101 | junit-jupiter 102 | test 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.junit 110 | junit-bom 111 | ${junt5.version} 112 | import 113 | pom 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | maven-compiler-plugin 122 | 3.10.1 123 | 124 | true 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | release 133 | 134 | 135 | 136 | org.sonatype.plugins 137 | nexus-staging-maven-plugin 138 | 1.6.12 139 | true 140 | 141 | ossrh 142 | https://s01.oss.sonatype.org/ 143 | true 144 | 145 | 146 | 147 | org.apache.maven.plugins 148 | maven-source-plugin 149 | 3.2.1 150 | 151 | 152 | attach-sources 153 | 154 | jar-no-fork 155 | 156 | 157 | 158 | 159 | 160 | org.apache.maven.plugins 161 | maven-javadoc-plugin 162 | 3.1.1 163 | 164 | 165 | attach-javadocs 166 | 167 | jar 168 | 169 | 170 | 171 | 172 | 173 | org.apache.maven.plugins 174 | maven-gpg-plugin 175 | 1.6 176 | 177 | 178 | sign-artifacts 179 | verify 180 | 181 | sign 182 | 183 | 184 | 185 | --pinentry-mode 186 | loopback 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /src/main/java/org/mvnsearch/ddd/domain/events/DomainEvent.java: -------------------------------------------------------------------------------- 1 | package org.mvnsearch.ddd.domain.events; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | import io.cloudevents.CloudEvent; 5 | import io.cloudevents.CloudEventData; 6 | import io.cloudevents.core.v1.CloudEventBuilder; 7 | 8 | import java.net.URI; 9 | import java.nio.charset.StandardCharsets; 10 | import java.time.OffsetDateTime; 11 | import java.time.ZonedDateTime; 12 | import java.util.Base64; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.UUID; 16 | 17 | /** 18 | * domain event from CloudEvents: https://github.com/cloudevents/spec/blob/v1.0-rc1/spec.md 19 | * 20 | * @author linux_china 21 | */ 22 | @JsonIgnoreProperties(value = {"$schema"}) 23 | @JsonInclude(JsonInclude.Include.NON_NULL) 24 | @JsonFilter("DomainEventDataFieldFilter") 25 | public class DomainEvent { 26 | /** 27 | * cloud events spec version 28 | */ 29 | @JsonProperty("specversion") 30 | private String specVersion = "1.0"; 31 | /** 32 | * event ID 33 | */ 34 | @JsonProperty("id") 35 | private String id = UUID.randomUUID().toString(); 36 | /** 37 | * event type: com.example.SomeEvent 38 | */ 39 | @JsonProperty("type") 40 | private String type; 41 | /** 42 | * additional metadata 43 | */ 44 | private Map extensions; 45 | /** 46 | * event producer 47 | */ 48 | @JsonProperty("source") 49 | private URI source; 50 | /** 51 | * indicates which resource the event is about 52 | */ 53 | private String subject; 54 | /** 55 | * content type for data, such as text/plain, application/json 56 | */ 57 | @JsonProperty("datacontenttype") 58 | private String dataContentType; 59 | /** 60 | * The event payload 61 | */ 62 | @JsonProperty("data") 63 | private T data; 64 | /** 65 | * A link to the schema that the data attribute adheres to 66 | */ 67 | @JsonProperty("dataschema") 68 | private URI dataSchema; 69 | /** 70 | * Timestamp of when the event happened 71 | */ 72 | @JsonProperty("time") 73 | private ZonedDateTime time; 74 | 75 | public DomainEvent() { 76 | this.time = ZonedDateTime.now(); 77 | } 78 | 79 | public DomainEvent(T data) { 80 | this.data = data; 81 | this.time = ZonedDateTime.now(); 82 | } 83 | 84 | public DomainEvent(String type, T data) { 85 | this.dataContentType = type; 86 | this.data = data; 87 | this.time = ZonedDateTime.now(); 88 | } 89 | 90 | public DomainEvent(String type, T data, URI source) { 91 | this.dataContentType = type; 92 | this.data = data; 93 | this.source = source; 94 | this.time = ZonedDateTime.now(); 95 | } 96 | 97 | public String getSpecVersion() { 98 | return specVersion; 99 | } 100 | 101 | public void setSpecVersion(String specVersion) { 102 | this.specVersion = specVersion; 103 | } 104 | 105 | public String getId() { 106 | return id; 107 | } 108 | 109 | public void setId(String id) { 110 | this.id = id; 111 | } 112 | 113 | public String getType() { 114 | return type; 115 | } 116 | 117 | public void setType(String type) { 118 | this.type = type; 119 | } 120 | 121 | public URI getSource() { 122 | return source; 123 | } 124 | 125 | public void setSource(URI source) { 126 | this.source = source; 127 | } 128 | 129 | public String getSubject() { 130 | return subject; 131 | } 132 | 133 | public void setSubject(String subject) { 134 | this.subject = subject; 135 | } 136 | 137 | public String getDataContentType() { 138 | return dataContentType; 139 | } 140 | 141 | public void setDataContentType(String dataContentType) { 142 | this.dataContentType = dataContentType; 143 | } 144 | 145 | public T getData() { 146 | return data; 147 | } 148 | 149 | public void setData(T data) { 150 | this.data = data; 151 | } 152 | 153 | 154 | @JsonAlias({"data_base64"}) 155 | public void setDataBase64(String dataBase64) { 156 | this.data = (T) Base64.getDecoder().decode(dataBase64); 157 | } 158 | 159 | 160 | public URI getDataSchema() { 161 | return dataSchema; 162 | } 163 | 164 | public void setDataSchema(URI dataSchema) { 165 | this.dataSchema = dataSchema; 166 | } 167 | 168 | public ZonedDateTime getTime() { 169 | return time; 170 | } 171 | 172 | public void setTime(ZonedDateTime time) { 173 | this.time = time; 174 | } 175 | 176 | @JsonAnyGetter 177 | public Map getExtensions() { 178 | return extensions; 179 | } 180 | 181 | public void setExtensions(Map extensions) { 182 | this.extensions = extensions; 183 | } 184 | 185 | public Object getExtension(String name) { 186 | return extensions == null ? null : extensions.get(name); 187 | } 188 | 189 | @JsonAnySetter 190 | public void setExtension(String name, Object value) { 191 | if (extensions == null) { 192 | extensions = new HashMap<>(); 193 | } 194 | this.extensions.put(name, value); 195 | } 196 | 197 | /** 198 | * convert DomainEvent to CloudEvent 199 | * @return CloudEvent 200 | */ 201 | public CloudEvent toCloudEvent() { 202 | CloudEventBuilder cloudEventBuilder = new CloudEventBuilder() 203 | .withId(this.id) 204 | .withType(this.type) 205 | .withSource(this.source) 206 | .withSubject(this.subject) 207 | .withDataContentType(this.dataContentType) 208 | .withDataSchema(this.dataSchema) 209 | .withTime(this.time.toOffsetDateTime()); 210 | if (this.extensions != null) { 211 | for (Map.Entry entry : extensions.entrySet()) { 212 | cloudEventBuilder.withExtension(entry.getKey(), entry.getValue().toString()); 213 | } 214 | } 215 | if (this.data != null) { 216 | if (data instanceof byte[]) { 217 | cloudEventBuilder.withData((byte[]) this.data); 218 | } else { 219 | cloudEventBuilder.withData(Json.encodePOJOAsBytes(this.data)); 220 | } 221 | } 222 | return cloudEventBuilder.build(); 223 | } 224 | 225 | /** 226 | * construct domain event from CloudEvent by data content type: text/* to String, application/json to POJO/Map, other to byte[] 227 | * @param cloudEvent cloudEvent 228 | * @param dataClass data class, such String, POJO, byte[].class 229 | * @param generic type 230 | * @return domain event 231 | */ 232 | public static DomainEvent fromCloudEvent(CloudEvent cloudEvent, Class dataClass) { 233 | DomainEvent domainEvent = new DomainEvent<>(); 234 | domainEvent.setId(cloudEvent.getId()); 235 | domainEvent.setType(cloudEvent.getType()); 236 | domainEvent.setSource(cloudEvent.getSource()); 237 | domainEvent.setSubject(cloudEvent.getSubject()); 238 | domainEvent.setDataSchema(cloudEvent.getDataSchema()); 239 | OffsetDateTime time = cloudEvent.getTime(); 240 | if (time != null) { 241 | domainEvent.setTime(time.toZonedDateTime()); 242 | } 243 | for (String extensionName : cloudEvent.getExtensionNames()) { 244 | domainEvent.setExtension(extensionName, cloudEvent.getExtension(extensionName)); 245 | } 246 | String dataContentType = cloudEvent.getDataContentType(); 247 | CloudEventData cloudEventData = cloudEvent.getData(); 248 | if (dataContentType != null && cloudEventData != null) { 249 | domainEvent.setDataContentType(cloudEvent.getDataContentType()); 250 | if (dataContentType.startsWith("text/")) { 251 | //noinspection unchecked 252 | domainEvent.setData((T) new String(cloudEventData.toBytes(), StandardCharsets.UTF_8)); 253 | } else if (dataContentType.startsWith("application/json")) { 254 | domainEvent.setData(Json.decodePOJO(cloudEventData.toBytes(), dataClass)); 255 | } else { 256 | domainEvent.setData((T)cloudEventData.toBytes()); 257 | } 258 | } 259 | return domainEvent; 260 | } 261 | } 262 | --------------------------------------------------------------------------------