refactor and extend the example
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
target/
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package tech.allegro.hexagon.articles.adapters.eventbus;
|
||||
|
||||
class ArticleCreatedEvent {
|
||||
/**
|
||||
* Message broker model implementation comes here
|
||||
*/
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package tech.allegro.hexagon.articles.adapters.eventbus;
|
||||
|
||||
class ArticleRetrievedEvent {
|
||||
/**
|
||||
* Message broker model implementation comes here
|
||||
*/
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user