refactor and extend the example

This commit is contained in:
dziadeusz
2020-05-12 20:48:49 +02:00
committed by GitHub
parent c96b213558
commit 66c7339101
73 changed files with 642 additions and 316 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@
*.iws
*.iml
*.ipr
target/

View File

@@ -1,41 +0,0 @@
package tech.allegro.hexagon.articles.adapters.api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 ArticleController {
private final Logger log = LoggerFactory.getLogger(ArticleController.class);
private final ArticleService service;
ArticleController(final ArticleService service) {
this.service = service;
}
@GetMapping("{articleId}")
ArticleResponse get(@PathVariable("articleId") final String articleId) {
log.info(">>> HTTP GET Request: retrieve an article with id: \"{}\"", articleId);
final ArticleResponse articleResponse = service.get(articleId);
log.info("<<< HTTP GET Response: article: \"{}\", successfully retrieved", articleResponse.title());
return articleResponse;
}
@PostMapping
ArticleIdResponse create(@RequestBody final ArticleRequest articleRequest) {
log.info(">>> HTTP POST Request: create an article: \"{}\"", articleRequest.title().value());
final ArticleIdResponse articleIdResponse = service.create(articleRequest);
log.info("<<< HTTP POST Response: article with id: \"{}\", successfully created", articleIdResponse.id());
return articleIdResponse;
}
}

View File

@@ -0,0 +1,30 @@
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);
}
}

View File

@@ -0,0 +1,26 @@
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);
}
}

View File

@@ -1,22 +1,23 @@
package tech.allegro.hexagon.articles.adapters.api;
import tech.allegro.hexagon.articles.domain.model.ArticleId;
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;
}
@JsonProperty("id")
public String id() {
return id;
}
public static ArticleIdResponse of(final ArticleId articleId) {
static ArticleIdResponse of(final ArticleId articleId) {
return new ArticleIdResponse(articleId.value());
}
@JsonProperty("id")
String id() {
return id;
}
}

View File

@@ -1,9 +1,9 @@
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;
import com.fasterxml.jackson.annotation.JsonProperty;
class ArticleRequest {
private final String title;
@@ -17,15 +17,15 @@ class ArticleRequest {
}
public Title title() {
Title title() {
return Title.of(title);
}
public Content content() {
Content content() {
return Content.of(content);
}
public AuthorId authorId() {
AuthorId authorId() {
return AuthorId.of(authorId);
}

View File

@@ -1,7 +1,7 @@
package tech.allegro.hexagon.articles.adapters.api;
import tech.allegro.hexagon.articles.domain.model.Article;
import com.fasterxml.jackson.annotation.JsonProperty;
import tech.allegro.hexagon.articles.domain.model.Article;
class ArticleResponse {
private final String id;
@@ -24,22 +24,22 @@ class ArticleResponse {
}
@JsonProperty("id")
public String id() {
String id() {
return id;
}
@JsonProperty("title")
public String title() {
String title() {
return title;
}
@JsonProperty("content")
public String content() {
String content() {
return content;
}
@JsonProperty("authorName")
public String authorName() {
String authorName() {
return authorName;
}
}

View File

@@ -1,24 +0,0 @@
package tech.allegro.hexagon.articles.adapters.api;
import tech.allegro.hexagon.articles.domain.ArticleFacade;
import tech.allegro.hexagon.articles.domain.model.ArticleId;
import org.springframework.stereotype.Component;
@Component
class ArticleService {
private final ArticleFacade articleFacade;
ArticleService(final ArticleFacade articleFacade) {
this.articleFacade = articleFacade;
}
ArticleResponse get(final String articleId) {
return ArticleResponse.of(articleFacade.get(ArticleId.of(articleId)));
}
ArticleIdResponse create(final ArticleRequest articleRequest) {
final ArticleId articleId = articleFacade.create(articleRequest.authorId(), articleRequest.title(), articleRequest.content());
return ArticleIdResponse.of(articleId);
}
}

View File

@@ -1,7 +1,125 @@
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 {
/**
* Database model implementation comes here
*/
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);
}
}
}

View File

@@ -1,56 +1,42 @@
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.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 tech.allegro.hexagon.articles.domain.ports.ArticleRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
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 {
private final Logger log = LoggerFactory.getLogger(DbArticleRepository.class);
@Override
public Article save(final Author author, final Title title, final Content content) {
/**
* Database integration implementation using {@link ArticleDatabaseModel} comes here
* Database integration implementation comes here
*/
final String articleId = UUID.randomUUID().toString();
log.info("Article: \"{}\" persisted", title.value());
return Article.article()
.withId(ArticleId.of(articleId))
.withAuthor(author)
.withTitle(title)
.withContent(content)
.build();
final ArticleDatabaseModel entity = of(author, title, content);
return entity.toDomain();
}
@Override
public Article get(final ArticleId id) {
/**
* Database integration implementation using {@link ArticleDatabaseModel} comes here
* Database integration implementation comes here
*/
final Title title = Title.of("Hexagonal Architecture");
log.info("Article \"{}\" fetched", title.value());
return Article.article()
.withId(id)
.withAuthor(Author
.author()
.withId(AuthorId.of(UUID.randomUUID().toString()))
.withName(PersonName.of("William Shakespeare"))
.build())
.withTitle(title)
.withContent(Content.of("Lorem ipsum"))
final ArticleDatabaseModel entity = articleDatabaseModel()
.withId(UUID.fromString(id.value()))
.withAuthorName("William Shakespeare")
.withAuthorId("928467")
.withTitle("Hexagonal Architecture")
.withContent("Lorem ipsum")
.build();
return entity.toDomain();
}
}

View File

@@ -1,7 +1,65 @@
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 {
/**
* External author service model implementation comes here
*/
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);
}
}
}

View File

@@ -1,27 +1,26 @@
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.model.PersonName;
import tech.allegro.hexagon.articles.domain.ports.AuthorRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import static tech.allegro.hexagon.articles.adapters.authorservice.AuthorExternalModel.authorExternalModel;
@Component
class ExternalServiceClientAuthorRepository implements AuthorRepository {
private final Logger log = LoggerFactory.getLogger(ExternalServiceClientAuthorRepository.class);
@Override
public Author get(final AuthorId authorId) {
/**
* external author service integration implementation comes here
*/
log.info("Author: \"William Shakespeare\" fetched", authorId.value());
return Author
.author()
.withId(authorId)
.withName(PersonName.of("William Shakespeare"))
final AuthorExternalModel author = authorExternalModel()
.withId(928467)
.withFirstName("William")
.withLastName("Shakespeare")
.build();
return author.toDomain();
}
}

View File

@@ -1,26 +0,0 @@
package tech.allegro.hexagon.articles.adapters.config;
import tech.allegro.hexagon.articles.domain.ArticleFacade;
import tech.allegro.hexagon.articles.domain.ports.ArticleAuthorNotifier;
import tech.allegro.hexagon.articles.domain.ports.ArticleEventPublisher;
import tech.allegro.hexagon.articles.domain.ports.ArticleRepository;
import tech.allegro.hexagon.articles.domain.ports.AuthorRepository;
import tech.allegro.hexagon.articles.domain.ports.SocialMediaPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
class ArticleConfig {
@Bean
ArticleFacade articleFacade(final ArticleRepository articleRepository,
final AuthorRepository authorRepository,
final ArticleEventPublisher eventPublisher,
final List<SocialMediaPublisher> socialMediaPublishers,
final List<ArticleAuthorNotifier> articleAuthorNotifiers) {
return new ArticleFacade(eventPublisher, articleRepository, authorRepository, socialMediaPublishers, articleAuthorNotifiers);
}
}

View File

@@ -1,7 +0,0 @@
package tech.allegro.hexagon.articles.adapters.eventbus;
class ArticleCreatedEvent {
/**
* Message broker model implementation comes here
*/
}

View File

@@ -1,7 +0,0 @@
package tech.allegro.hexagon.articles.adapters.eventbus;
class ArticleRetrievedEvent {
/**
* Message broker model implementation comes here
*/
}

View File

@@ -1,29 +0,0 @@
package tech.allegro.hexagon.articles.adapters.eventbus;
import tech.allegro.hexagon.articles.domain.model.Article;
import tech.allegro.hexagon.articles.domain.ports.ArticleEventPublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
class MessageBrokerArticleEventPublisher implements ArticleEventPublisher {
private final Logger log = LoggerFactory.getLogger(MessageBrokerArticleEventPublisher.class);
@Override
public void publishArticleCreationEvent(final Article article) {
/**
* message broker integration implementation using {@link ArticleCreatedEvent} comes here
*/
log.info("Article: \"{}\" creation event published on event bus", article.title().value());
}
@Override
public void publishArticleRetrievalEvent(final Article article) {
/**
* message broker integration implementation using {@link ArticleRetrievedEvent} comes here
*/
log.info("Article: \"{}\" retrieval event published on event bus", article.title().value());
}
}

View File

@@ -0,0 +1,25 @@
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());
}
}

View File

@@ -0,0 +1,26 @@
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());
}
}

View File

@@ -0,0 +1,25 @@
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);
}
}

View File

@@ -1,21 +0,0 @@
package tech.allegro.hexagon.articles.adapters.notifications;
import tech.allegro.hexagon.articles.domain.model.Article;
import tech.allegro.hexagon.articles.domain.ports.ArticleAuthorNotifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
class ArticleAuthorMailNotifier implements ArticleAuthorNotifier {
private final Logger log = LoggerFactory.getLogger(ArticleAuthorMailNotifier.class);
@Override
public void notifyAboutArticleCreation(final Article article) {
/**
* mail system integration implementation using {@link ArticleMailModel} comes here
*/
log.info("Mail sent to author: \"{}\"", article.author().name());
}
}

View File

@@ -1,21 +0,0 @@
package tech.allegro.hexagon.articles.adapters.notifications;
import tech.allegro.hexagon.articles.domain.model.Article;
import tech.allegro.hexagon.articles.domain.ports.ArticleAuthorNotifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
class ArticleAuthorSmsNotifier implements ArticleAuthorNotifier {
private final Logger log = LoggerFactory.getLogger(ArticleAuthorSmsNotifier.class);
@Override
public void notifyAboutArticleCreation(final Article article) {
/**
* sms system integration implementation using {@link ArticleSmsModel}comes here
*/
log.info("SMS sent to author: \"{}\"", article.author().name());
}
}

View File

@@ -1,7 +1,32 @@
package tech.allegro.hexagon.articles.adapters.notifications;
import tech.allegro.hexagon.articles.domain.model.Article;
class ArticleMailModel {
/**
* Mail model implementation comes here
*/
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;
}
}

View File

@@ -1,7 +1,27 @@
package tech.allegro.hexagon.articles.adapters.notifications;
import tech.allegro.hexagon.articles.domain.model.Article;
class ArticleSmsModel {
/**
* Sms model implementation comes here
*/
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;
}
}

View File

@@ -0,0 +1,17 @@
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);
}
}

View File

@@ -0,0 +1,17 @@
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);
}
}

View File

@@ -1,7 +1,28 @@
package tech.allegro.hexagon.articles.adapters.socialmedia;
import tech.allegro.hexagon.articles.domain.model.Article;
class ArticleTwitterModel {
/**
* twitter implementation comes here
*/
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;
}
}

View File

@@ -1,22 +1,21 @@
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
class TwitterArticlePublisher implements SocialMediaPublisher {
private final Logger log = LoggerFactory.getLogger(TwitterArticlePublisher.class);
private final TwitterClient twitterClient;
TwitterArticlePublisher(final TwitterClient twitterClient) {
this.twitterClient = twitterClient;
}
@Override
public void publish(final Article article) {
/**
* social media integration implementation using {@link TwitterModel} comes here
*/
log.info("Article: \"{}\" published on twitter", article.title().value());
final ArticleTwitterModel articleTweet = ArticleTwitterModel.of(article);
twitterClient.tweet(articleTweet);
}
}

View File

@@ -0,0 +1,12 @@
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
*/
}
}

View File

@@ -0,0 +1,38 @@
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<SocialMediaPublisher> socialMediaPublishers,
final List<AuthorNotifier> 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);
}
}

View File

@@ -1,47 +0,0 @@
package tech.allegro.hexagon.articles.domain;
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;
import tech.allegro.hexagon.articles.domain.ports.ArticleAuthorNotifier;
import tech.allegro.hexagon.articles.domain.ports.ArticleEventPublisher;
import tech.allegro.hexagon.articles.domain.ports.ArticleRepository;
import tech.allegro.hexagon.articles.domain.ports.AuthorRepository;
import tech.allegro.hexagon.articles.domain.ports.SocialMediaPublisher;
import java.util.List;
public class ArticleFacade {
private final ArticleEventPublisher eventPublisher;
private final ArticleRepository articleRepository;
private final AuthorRepository authorRepository;
private final List<SocialMediaPublisher> socialMediaPublishers;
private final List<ArticleAuthorNotifier> articleAuthorNotifiers;
public ArticleFacade(final ArticleEventPublisher eventPublisher, final ArticleRepository articleRepository, final AuthorRepository authorRepository, final List<SocialMediaPublisher> socialMediaPublishers, final List<ArticleAuthorNotifier> articleAuthorNotifiers) {
this.eventPublisher = eventPublisher;
this.articleRepository = articleRepository;
this.authorRepository = authorRepository;
this.socialMediaPublishers = socialMediaPublishers;
this.articleAuthorNotifiers = articleAuthorNotifiers;
}
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);
eventPublisher.publishArticleCreationEvent(article);
socialMediaPublishers.forEach(socialMediaPublisher -> socialMediaPublisher.publish(article));
articleAuthorNotifiers.forEach(articleAuthorNotifier -> articleAuthorNotifier.notifyAboutArticleCreation(article));
return article.id();
}
public Article get(final ArticleId id) {
final Article article = articleRepository.get(id);
eventPublisher.publishArticleRetrievalEvent(article);
return article;
}
}

View File

@@ -0,0 +1,32 @@
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<SocialMediaPublisher> socialMediaPublishers;
private final List<AuthorNotifier> articleAuthorNotifiers;
public ArticlePublisher(final ArticleMessageSender messageSender,
final List<SocialMediaPublisher> socialMediaPublishers,
final List<AuthorNotifier> 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);
}
}

View File

@@ -1,12 +1,12 @@
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;
@@ -14,6 +14,16 @@ public class Article {
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;
}
@@ -30,6 +40,24 @@ public class Article {
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();
}

View File

@@ -5,7 +5,7 @@ public class Author {
private final PersonName name;
public Author(final AuthorId id, final PersonName name) {
private Author(final AuthorId id, final PersonName name) {
this.id = id;
this.name = name;
}
@@ -18,6 +18,10 @@ public class Author {
return name;
}
public AuthorId id() {
return id;
}
public static final class AuthorBuilder {
private AuthorId id;
private PersonName name;

View File

@@ -1,11 +0,0 @@
package tech.allegro.hexagon.articles.domain.ports;
import tech.allegro.hexagon.articles.domain.model.Article;
public interface ArticleEventPublisher {
void publishArticleCreationEvent(Article article);
void publishArticleRetrievalEvent(Article article);
}

View File

@@ -0,0 +1,11 @@
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);
}

View File

@@ -0,0 +1,41 @@
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;
}
}

View File

@@ -2,8 +2,8 @@ package tech.allegro.hexagon.articles.domain.ports;
import tech.allegro.hexagon.articles.domain.model.Article;
public interface ArticleAuthorNotifier {
public interface AuthorNotifier {
void notifyAboutArticleCreation(Article article);
void notifyAboutCreationOf(Article article);
}