Repository: dziadeusz/hexagonal-architecture-by-example Branch: master Commit: 66c733910127 Files: 43 Total size: 38.0 KB Directory structure: gitextract_5r_na3sw/ ├── .gitignore ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src/ └── main/ ├── java/ │ └── tech/ │ └── allegro/ │ └── hexagon/ │ ├── HexagonalArchitectureExampleApplication.java │ └── articles/ │ ├── adapters/ │ │ ├── api/ │ │ │ ├── ArticleEndpoint.java │ │ │ ├── ArticleFacade.java │ │ │ ├── ArticleIdResponse.java │ │ │ ├── ArticleRequest.java │ │ │ └── ArticleResponse.java │ │ ├── articledb/ │ │ │ ├── ArticleDatabaseModel.java │ │ │ └── DbArticleRepository.java │ │ ├── authorservice/ │ │ │ ├── AuthorExternalModel.java │ │ │ └── ExternalServiceClientAuthorRepository.java │ │ ├── messagebroker/ │ │ │ ├── ArticleCreatedMessage.java │ │ │ ├── ArticleRetrievedMessage.java │ │ │ └── MessageBrokerArticleMessageSender.java │ │ ├── notifications/ │ │ │ ├── ArticleMailModel.java │ │ │ ├── ArticleSmsModel.java │ │ │ ├── AuthorMailNotifier.java │ │ │ └── AuthorSmsNotifier.java │ │ └── socialmedia/ │ │ ├── ArticleTwitterModel.java │ │ ├── TwitterArticlePublisher.java │ │ └── TwitterClient.java │ ├── config/ │ │ └── ArticleConfig.java │ └── domain/ │ ├── ArticlePublisher.java │ ├── model/ │ │ ├── Article.java │ │ ├── ArticleId.java │ │ ├── Author.java │ │ ├── AuthorId.java │ │ ├── Content.java │ │ ├── PersonName.java │ │ └── Title.java │ └── ports/ │ ├── ArticleMessageSender.java │ ├── ArticleRepository.java │ ├── ArticleService.java │ ├── AuthorNotifier.java │ ├── AuthorRepository.java │ └── SocialMediaPublisher.java └── resources/ └── application.properties ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr target/ ================================================ FILE: build.gradle ================================================ plugins { id 'org.springframework.boot' version '2.2.2.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java' } group = 'tech.allegro' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } } test { useJUnitPlatform() } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: settings.gradle ================================================ rootProject.name = 'hexagonal-architecture-by-example' ================================================ FILE: src/main/java/tech/allegro/hexagon/HexagonalArchitectureExampleApplication.java ================================================ package tech.allegro.hexagon; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class HexagonalArchitectureExampleApplication { public static void main(String[] args) { SpringApplication.run(HexagonalArchitectureExampleApplication.class, args); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/api/ArticleEndpoint.java ================================================ package tech.allegro.hexagon.articles.adapters.api; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("articles") class ArticleEndpoint { private final ArticleFacade articles; ArticleEndpoint(ArticleFacade articles) { this.articles = articles; } @GetMapping("{articleId}") ArticleResponse get(@PathVariable("articleId") final String articleId) { return articles.get(articleId); } @PostMapping ArticleIdResponse create(@RequestBody final ArticleRequest articleRequest) { return articles.create(articleRequest); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/api/ArticleFacade.java ================================================ package tech.allegro.hexagon.articles.adapters.api; import org.springframework.stereotype.Component; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.model.ArticleId; import tech.allegro.hexagon.articles.domain.ports.ArticleService; @Component class ArticleFacade { private final ArticleService articleService; ArticleFacade(final ArticleService articleService) { this.articleService = articleService; } ArticleResponse get(final String articleId) { final Article article = articleService.get(ArticleId.of(articleId)); return ArticleResponse.of(article); } ArticleIdResponse create(final ArticleRequest articleRequest) { final ArticleId articleId = articleService.create(articleRequest.authorId(), articleRequest.title(), articleRequest.content()); return ArticleIdResponse.of(articleId); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/api/ArticleIdResponse.java ================================================ package tech.allegro.hexagon.articles.adapters.api; import com.fasterxml.jackson.annotation.JsonProperty; import tech.allegro.hexagon.articles.domain.model.ArticleId; class ArticleIdResponse { private final String id; private ArticleIdResponse(final String id) { this.id = id; } static ArticleIdResponse of(final ArticleId articleId) { return new ArticleIdResponse(articleId.value()); } @JsonProperty("id") String id() { return id; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/api/ArticleRequest.java ================================================ package tech.allegro.hexagon.articles.adapters.api; import com.fasterxml.jackson.annotation.JsonProperty; import tech.allegro.hexagon.articles.domain.model.AuthorId; import tech.allegro.hexagon.articles.domain.model.Content; import tech.allegro.hexagon.articles.domain.model.Title; class ArticleRequest { private final String title; private final String content; private final String authorId; ArticleRequest(@JsonProperty("title") final String title, @JsonProperty("content") final String content, @JsonProperty("authorId") final String authorId) { this.title = title; this.content = content; this.authorId = authorId; } Title title() { return Title.of(title); } Content content() { return Content.of(content); } AuthorId authorId() { return AuthorId.of(authorId); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/api/ArticleResponse.java ================================================ package tech.allegro.hexagon.articles.adapters.api; import com.fasterxml.jackson.annotation.JsonProperty; import tech.allegro.hexagon.articles.domain.model.Article; class ArticleResponse { private final String id; private final String title; private final String content; private final String authorName; private ArticleResponse(final String id, final String title, final String content, final String authorName) { this.id = id; this.title = title; this.content = content; this.authorName = authorName; } static ArticleResponse of(final Article article) { return new ArticleResponse(article.id().value(), article.title().value(), article.content().value(), article.author().name().value()); } @JsonProperty("id") String id() { return id; } @JsonProperty("title") String title() { return title; } @JsonProperty("content") String content() { return content; } @JsonProperty("authorName") String authorName() { return authorName; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/articledb/ArticleDatabaseModel.java ================================================ package tech.allegro.hexagon.articles.adapters.articledb; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.model.ArticleId; import tech.allegro.hexagon.articles.domain.model.Author; import tech.allegro.hexagon.articles.domain.model.AuthorId; import tech.allegro.hexagon.articles.domain.model.Content; import tech.allegro.hexagon.articles.domain.model.PersonName; import tech.allegro.hexagon.articles.domain.model.Title; import java.time.ZonedDateTime; import java.util.UUID; class ArticleDatabaseModel { private final UUID id; private final String title; private final String content; private final long version; private final ZonedDateTime createdAt; private final String authorId; private final String authorName; private ArticleDatabaseModel(final UUID id, final String title, final String content, final String authorId, final long version, final ZonedDateTime createdAt, final String authorName) { this.id = id; this.title = title; this.content = content; this.authorId = authorId; this.version = version; this.createdAt = createdAt; this.authorName = authorName; } @Override public String toString() { return title; } Article toDomain() { return Article.article() .withId(ArticleId.of(id.toString())) .withAuthor(Author .author() .withId(AuthorId.of(authorId)) .withName(PersonName.of(authorName)) .build()) .withTitle(Title.of(title)) .withContent(Content.of(content)) .build(); } static ArticleDatabaseModel of(final Author author, final Title title, final Content content) { return articleDatabaseModel() .withId(UUID.randomUUID()) .withVersion(0) .withCreatedAt(ZonedDateTime.now()) .withAuthorId(author.id().value()) .withAuthorName(author.name().value()) .withTitle(title.value()) .withContent(content.value()) .build(); } static ArticleDatabaseModelBuilder articleDatabaseModel() { return new ArticleDatabaseModelBuilder(); } static final class ArticleDatabaseModelBuilder { private UUID id; private String title; private String content; private long version; private ZonedDateTime createdAt; private String authorId; private String authorName; private ArticleDatabaseModelBuilder() { } ArticleDatabaseModelBuilder withId(UUID id) { this.id = id; return this; } ArticleDatabaseModelBuilder withTitle(String title) { this.title = title; return this; } ArticleDatabaseModelBuilder withContent(String content) { this.content = content; return this; } ArticleDatabaseModelBuilder withVersion(long version) { this.version = version; return this; } ArticleDatabaseModelBuilder withCreatedAt(ZonedDateTime createdAt) { this.createdAt = createdAt; return this; } ArticleDatabaseModelBuilder withAuthorId(String authorId) { this.authorId = authorId; return this; } ArticleDatabaseModelBuilder withAuthorName(String authorName) { this.authorName = authorName; return this; } ArticleDatabaseModel build() { return new ArticleDatabaseModel(id, title, content, authorId, version, createdAt, authorName); } } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/articledb/DbArticleRepository.java ================================================ package tech.allegro.hexagon.articles.adapters.articledb; import org.springframework.stereotype.Component; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.model.ArticleId; import tech.allegro.hexagon.articles.domain.model.Author; import tech.allegro.hexagon.articles.domain.model.Content; import tech.allegro.hexagon.articles.domain.model.Title; import tech.allegro.hexagon.articles.domain.ports.ArticleRepository; import java.util.UUID; import static tech.allegro.hexagon.articles.adapters.articledb.ArticleDatabaseModel.articleDatabaseModel; import static tech.allegro.hexagon.articles.adapters.articledb.ArticleDatabaseModel.of; @Component class DbArticleRepository implements ArticleRepository { @Override public Article save(final Author author, final Title title, final Content content) { /** * Database integration implementation comes here */ final ArticleDatabaseModel entity = of(author, title, content); return entity.toDomain(); } @Override public Article get(final ArticleId id) { /** * Database integration implementation comes here */ final ArticleDatabaseModel entity = articleDatabaseModel() .withId(UUID.fromString(id.value())) .withAuthorName("William Shakespeare") .withAuthorId("928467") .withTitle("Hexagonal Architecture") .withContent("Lorem ipsum") .build(); return entity.toDomain(); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/authorservice/AuthorExternalModel.java ================================================ package tech.allegro.hexagon.articles.adapters.authorservice; import tech.allegro.hexagon.articles.domain.model.Author; import tech.allegro.hexagon.articles.domain.model.AuthorId; import tech.allegro.hexagon.articles.domain.model.PersonName; class AuthorExternalModel { private final long id; private final String firstName; private final String lastName; private AuthorExternalModel(final long id, final String firstName, final String lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName; } Author toDomain() { return Author.author() .withId(AuthorId.of(String.valueOf(id))) .withName(PersonName.of(fullName())) .build(); } private String fullName() { return String.format("%s %s", firstName, lastName); } @Override public String toString() { return fullName(); } static AuthorExternalModelBuilder authorExternalModel() { return new AuthorExternalModelBuilder(); } static final class AuthorExternalModelBuilder { private long id; private String firstName; private String lastName; private AuthorExternalModelBuilder() { } AuthorExternalModelBuilder withId(long id) { this.id = id; return this; } AuthorExternalModelBuilder withFirstName(String firstName) { this.firstName = firstName; return this; } AuthorExternalModelBuilder withLastName(String lastName) { this.lastName = lastName; return this; } AuthorExternalModel build() { return new AuthorExternalModel(id, firstName, lastName); } } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/authorservice/ExternalServiceClientAuthorRepository.java ================================================ package tech.allegro.hexagon.articles.adapters.authorservice; import org.springframework.stereotype.Component; import tech.allegro.hexagon.articles.domain.model.Author; import tech.allegro.hexagon.articles.domain.model.AuthorId; import tech.allegro.hexagon.articles.domain.ports.AuthorRepository; import static tech.allegro.hexagon.articles.adapters.authorservice.AuthorExternalModel.authorExternalModel; @Component class ExternalServiceClientAuthorRepository implements AuthorRepository { @Override public Author get(final AuthorId authorId) { /** * external author service integration implementation comes here */ final AuthorExternalModel author = authorExternalModel() .withId(928467) .withFirstName("William") .withLastName("Shakespeare") .build(); return author.toDomain(); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/messagebroker/ArticleCreatedMessage.java ================================================ package tech.allegro.hexagon.articles.adapters.messagebroker; import tech.allegro.hexagon.articles.domain.model.Article; import java.time.ZonedDateTime; class ArticleCreatedMessage { private final Article article; private final ZonedDateTime sentAt; private ArticleCreatedMessage(final Article article, final ZonedDateTime sentAt) { this.article = article; this.sentAt = sentAt; } static ArticleCreatedMessage of(Article article) { return new ArticleCreatedMessage(article, ZonedDateTime.now()); } @Override public String toString() { return String.format("\"Article >>%s<< created\"", article.title().value()); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/messagebroker/ArticleRetrievedMessage.java ================================================ package tech.allegro.hexagon.articles.adapters.messagebroker; import tech.allegro.hexagon.articles.domain.model.Article; import java.time.ZonedDateTime; class ArticleRetrievedMessage { private final Article article; private final ZonedDateTime sentAt; private ArticleRetrievedMessage(final Article article, final ZonedDateTime sentAt) { this.article = article; this.sentAt = sentAt; } static ArticleRetrievedMessage of(Article article) { return new ArticleRetrievedMessage(article, ZonedDateTime.now()); } @Override public String toString() { return String.format("\"Article >>%s<< retrieved\"", article.title().value()); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/messagebroker/MessageBrokerArticleMessageSender.java ================================================ package tech.allegro.hexagon.articles.adapters.messagebroker; import org.springframework.stereotype.Component; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.ports.ArticleMessageSender; @Component class MessageBrokerArticleMessageSender implements ArticleMessageSender { @Override public void sendMessageForCreated(final Article article) { /** * message broker integration implementation comes here */ ArticleCreatedMessage.of(article); } @Override public void sendMessageForRetrieved(final Article article) { /** * message broker integration implementation comes here */ ArticleRetrievedMessage.of(article); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/notifications/ArticleMailModel.java ================================================ package tech.allegro.hexagon.articles.adapters.notifications; import tech.allegro.hexagon.articles.domain.model.Article; class ArticleMailModel { private static final String SUBJECT = "You have successfully published: >>%s<<"; private static final String CONTENT = "Check if everything is correct: >>%s<<"; private final String recipientId; private final String subject; private final String content; private ArticleMailModel(final String recipientId, final String subject, final String content) { this.recipientId = recipientId; this.subject = subject; this.content = content; } static ArticleMailModel of(final Article article) { return new ArticleMailModel(article.author().name().value(), String.format(SUBJECT, article.title().value()), String.format(CONTENT, article.content().value())); } @Override public String toString() { return subject; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/notifications/ArticleSmsModel.java ================================================ package tech.allegro.hexagon.articles.adapters.notifications; import tech.allegro.hexagon.articles.domain.model.Article; class ArticleSmsModel { public static final String CONTENT = "Please check your email. We have sent you publication details of the article: >>%s<<"; private final String recipientId; private final String text; private ArticleSmsModel(final String recipientId, final String text) { this.recipientId = recipientId; this.text = text; } public static ArticleSmsModel of(Article article) { return new ArticleSmsModel( article.author().name().value(), String.format(CONTENT, article.title().value())); } @Override public String toString() { return text; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/notifications/AuthorMailNotifier.java ================================================ package tech.allegro.hexagon.articles.adapters.notifications; import org.springframework.stereotype.Component; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.ports.AuthorNotifier; @Component class AuthorMailNotifier implements AuthorNotifier { @Override public void notifyAboutCreationOf(final Article article) { /** * Mail system integration implementation comes here */ ArticleMailModel.of(article); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/notifications/AuthorSmsNotifier.java ================================================ package tech.allegro.hexagon.articles.adapters.notifications; import org.springframework.stereotype.Component; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.ports.AuthorNotifier; @Component class AuthorSmsNotifier implements AuthorNotifier { @Override public void notifyAboutCreationOf(final Article article) { /** * SMS system integration implementation comes here */ ArticleSmsModel.of(article); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/socialmedia/ArticleTwitterModel.java ================================================ package tech.allegro.hexagon.articles.adapters.socialmedia; import tech.allegro.hexagon.articles.domain.model.Article; class ArticleTwitterModel { public static final String TWEET = "Check out the new article >>%s<< by %s"; private final String twitterAccountId; private final String tweet; private ArticleTwitterModel(final String twitterAccountId, final String tweet) { this.twitterAccountId = twitterAccountId; this.tweet = tweet; } static ArticleTwitterModel of(Article article) { final String title = article .title() .value(); final String twitterId = article.author().name().value(); return new ArticleTwitterModel(twitterId, String.format(TWEET, title, twitterId)); } @Override public String toString() { return tweet; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/socialmedia/TwitterArticlePublisher.java ================================================ package tech.allegro.hexagon.articles.adapters.socialmedia; import org.springframework.stereotype.Component; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.ports.SocialMediaPublisher; @Component class TwitterArticlePublisher implements SocialMediaPublisher { private final TwitterClient twitterClient; TwitterArticlePublisher(final TwitterClient twitterClient) { this.twitterClient = twitterClient; } @Override public void publish(final Article article) { final ArticleTwitterModel articleTweet = ArticleTwitterModel.of(article); twitterClient.tweet(articleTweet); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/adapters/socialmedia/TwitterClient.java ================================================ package tech.allegro.hexagon.articles.adapters.socialmedia; import org.springframework.stereotype.Component; @Component class TwitterClient { void tweet(final ArticleTwitterModel articleTweet) { /** * social media integration implementation comes here */ } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/config/ArticleConfig.java ================================================ package tech.allegro.hexagon.articles.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import tech.allegro.hexagon.articles.domain.ArticlePublisher; import tech.allegro.hexagon.articles.domain.ports.ArticleMessageSender; import tech.allegro.hexagon.articles.domain.ports.ArticleRepository; import tech.allegro.hexagon.articles.domain.ports.ArticleService; import tech.allegro.hexagon.articles.domain.ports.AuthorNotifier; import tech.allegro.hexagon.articles.domain.ports.AuthorRepository; import tech.allegro.hexagon.articles.domain.ports.SocialMediaPublisher; import java.util.List; @Configuration class ArticleConfig { @Bean ArticlePublisher articleEventPublisher(final ArticleMessageSender eventPublisher, final List socialMediaPublishers, final List articleAuthorNotifiers) { return new ArticlePublisher(eventPublisher, socialMediaPublishers, articleAuthorNotifiers); } @Bean ArticleService articleService(final ArticleRepository articleRepository, final AuthorRepository authorRepository, final ArticlePublisher articleEventPublisher ) { return new ArticleService( articleRepository, authorRepository, articleEventPublisher); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/ArticlePublisher.java ================================================ package tech.allegro.hexagon.articles.domain; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.ports.ArticleMessageSender; import tech.allegro.hexagon.articles.domain.ports.AuthorNotifier; import tech.allegro.hexagon.articles.domain.ports.SocialMediaPublisher; import java.util.List; public class ArticlePublisher { private final ArticleMessageSender messageSender; private final List socialMediaPublishers; private final List articleAuthorNotifiers; public ArticlePublisher(final ArticleMessageSender messageSender, final List socialMediaPublishers, final List articleAuthorNotifiers) { this.messageSender = messageSender; this.socialMediaPublishers = socialMediaPublishers; this.articleAuthorNotifiers = articleAuthorNotifiers; } public void publishCreationOf(final Article article) { messageSender.sendMessageForCreated(article); socialMediaPublishers.forEach(socialMediaPublisher -> socialMediaPublisher.publish(article)); articleAuthorNotifiers.forEach(articleAuthorNotifier -> articleAuthorNotifier.notifyAboutCreationOf(article)); } public void publishRetrievalOf(final Article article) { messageSender.sendMessageForRetrieved(article); } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/model/Article.java ================================================ package tech.allegro.hexagon.articles.domain.model; public class Article { private final ArticleId id; private final Title title; private final Content content; private final Author author; private Article(final ArticleId id, final Title title, final Content content, final Author author) { this.id = id; this.title = title; this.content = content; this.author = author; } public void validateEligibilityForPublication() { verifyForPlagiarism(); validateTitleLength(); validateContentLength(); checkPunctuation(); checkGrammar(); checkStyle(); //TODO: these methods are just placeholders with empty implementation } public ArticleId id() { return id; } public Title title() { return title; } public Content content() { return content; } public Author author() { return author; } private void checkGrammar() { } private void checkStyle() { } private void checkPunctuation() { } private void verifyForPlagiarism() { } private void validateContentLength() { } private void validateTitleLength() { } public static ArticleBuilder article() { return new ArticleBuilder(); } public static final class ArticleBuilder { private ArticleId id; private Title title; private Content content; private Author author; private ArticleBuilder() { } public ArticleBuilder withId(ArticleId id) { this.id = id; return this; } public ArticleBuilder withTitle(Title title) { this.title = title; return this; } public ArticleBuilder withContent(Content content) { this.content = content; return this; } public ArticleBuilder withAuthor(Author author) { this.author = author; return this; } public Article build() { return new Article(id, title, content, author); } } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/model/ArticleId.java ================================================ package tech.allegro.hexagon.articles.domain.model; public class ArticleId { private final String value; private ArticleId(final String value) { this.value = value; } public static ArticleId of(final String articleId) { return new ArticleId(articleId); } public String value() { return value; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/model/Author.java ================================================ package tech.allegro.hexagon.articles.domain.model; public class Author { private final AuthorId id; private final PersonName name; private Author(final AuthorId id, final PersonName name) { this.id = id; this.name = name; } public static AuthorBuilder author() { return new AuthorBuilder(); } public PersonName name() { return name; } public AuthorId id() { return id; } public static final class AuthorBuilder { private AuthorId id; private PersonName name; private AuthorBuilder() { } public AuthorBuilder withId(AuthorId id) { this.id = id; return this; } public AuthorBuilder withName(PersonName name) { this.name = name; return this; } public Author build() { return new Author(id, name); } } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/model/AuthorId.java ================================================ package tech.allegro.hexagon.articles.domain.model; public class AuthorId { private final String value; private AuthorId(final String value) { this.value = value; } public static AuthorId of(final String authorId) { return new AuthorId(authorId); } public String value() { return value; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/model/Content.java ================================================ package tech.allegro.hexagon.articles.domain.model; public class Content { private final String value; private Content(final String value) { this.value = value; } public static Content of(final String content) { return new Content(content); } public String value() { return value; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/model/PersonName.java ================================================ package tech.allegro.hexagon.articles.domain.model; public class PersonName { private final String value; private PersonName(final String value) { this.value = value; } public static PersonName of(final String content) { return new PersonName(content); } public String value() { return value; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/model/Title.java ================================================ package tech.allegro.hexagon.articles.domain.model; public class Title { private final String value; private Title(final String value) { this.value = value; } public static Title of(final String title) { return new Title(title); } public String value() { return value; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/ports/ArticleMessageSender.java ================================================ package tech.allegro.hexagon.articles.domain.ports; import tech.allegro.hexagon.articles.domain.model.Article; public interface ArticleMessageSender { void sendMessageForCreated(Article article); void sendMessageForRetrieved(Article article); } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/ports/ArticleRepository.java ================================================ package tech.allegro.hexagon.articles.domain.ports; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.model.ArticleId; import tech.allegro.hexagon.articles.domain.model.Author; import tech.allegro.hexagon.articles.domain.model.Content; import tech.allegro.hexagon.articles.domain.model.Title; public interface ArticleRepository { Article save(Author author, Title title, Content content); Article get(ArticleId id); } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/ports/ArticleService.java ================================================ package tech.allegro.hexagon.articles.domain.ports; import tech.allegro.hexagon.articles.domain.ArticlePublisher; import tech.allegro.hexagon.articles.domain.model.Article; import tech.allegro.hexagon.articles.domain.model.ArticleId; import tech.allegro.hexagon.articles.domain.model.Author; import tech.allegro.hexagon.articles.domain.model.AuthorId; import tech.allegro.hexagon.articles.domain.model.Content; import tech.allegro.hexagon.articles.domain.model.Title; public final class ArticleService { private final ArticleRepository articleRepository; private final AuthorRepository authorRepository; private final ArticlePublisher eventPublisher; public ArticleService(final ArticleRepository articleRepository, final AuthorRepository authorRepository, final ArticlePublisher eventPublisher) { this.articleRepository = articleRepository; this.authorRepository = authorRepository; this.eventPublisher = eventPublisher; } public ArticleId create(final AuthorId authorId, final Title title, final Content content) { final Author author = authorRepository.get(authorId); final Article article = articleRepository.save(author, title, content); article.validateEligibilityForPublication(); eventPublisher.publishCreationOf(article); return article.id(); } public Article get(final ArticleId id) { final Article article = articleRepository.get(id); eventPublisher.publishRetrievalOf(article); return article; } } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/ports/AuthorNotifier.java ================================================ package tech.allegro.hexagon.articles.domain.ports; import tech.allegro.hexagon.articles.domain.model.Article; public interface AuthorNotifier { void notifyAboutCreationOf(Article article); } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/ports/AuthorRepository.java ================================================ package tech.allegro.hexagon.articles.domain.ports; import tech.allegro.hexagon.articles.domain.model.Author; import tech.allegro.hexagon.articles.domain.model.AuthorId; public interface AuthorRepository { Author get(AuthorId authorId); } ================================================ FILE: src/main/java/tech/allegro/hexagon/articles/domain/ports/SocialMediaPublisher.java ================================================ package tech.allegro.hexagon.articles.domain.ports; import tech.allegro.hexagon.articles.domain.model.Article; public interface SocialMediaPublisher { void publish(Article article); } ================================================ FILE: src/main/resources/application.properties ================================================