From 8da3fb716747413e250b0864ce71292ec43ca58d Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 12:14:32 +0200 Subject: [PATCH 01/43] First review: cleaning and formating code. --- .../com/scmspain/MsFcTechTestApplication.java | 3 ++- .../InfrastructureConfiguration.java | 2 ++ .../configuration/TweetConfiguration.java | 2 ++ .../scmspain/controller/TweetController.java | 2 ++ .../command/PublishTweetCommand.java | 2 ++ .../java/com/scmspain/entities/Tweet.java | 4 ++++ .../com/scmspain/services/TweetService.java | 2 ++ .../configuration/TestConfiguration.java | 2 ++ .../controller/TweetControllerTest.java | 19 +++++++++++++------ .../scmspain/services/TweetServiceTest.java | 2 ++ 10 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/scmspain/MsFcTechTestApplication.java b/src/main/java/com/scmspain/MsFcTechTestApplication.java index 28b3538..03af06d 100644 --- a/src/main/java/com/scmspain/MsFcTechTestApplication.java +++ b/src/main/java/com/scmspain/MsFcTechTestApplication.java @@ -4,7 +4,6 @@ import com.scmspain.configuration.TweetConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -12,7 +11,9 @@ @EnableAutoConfiguration @Import({TweetConfiguration.class, InfrastructureConfiguration.class}) public class MsFcTechTestApplication { + public static void main(String[] args) { SpringApplication.run(MsFcTechTestApplication.class, args); } + } diff --git a/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java index a0a2e48..91cb57b 100644 --- a/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java @@ -9,8 +9,10 @@ @Configuration public class InfrastructureConfiguration { + @Bean @ExportMetricWriter public MetricWriter getMetricWriter(MBeanExporter exporter) { return new JmxMetricWriter(exporter); } + } diff --git a/src/main/java/com/scmspain/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/configuration/TweetConfiguration.java index fbb0dbd..6a48956 100644 --- a/src/main/java/com/scmspain/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/configuration/TweetConfiguration.java @@ -10,6 +10,7 @@ @Configuration public class TweetConfiguration { + @Bean public TweetService getTweetService(EntityManager entityManager, MetricWriter metricWriter) { return new TweetService(entityManager, metricWriter); @@ -19,4 +20,5 @@ public TweetService getTweetService(EntityManager entityManager, MetricWriter me public TweetController getTweetConfiguration(TweetService tweetService) { return new TweetController(tweetService); } + } diff --git a/src/main/java/com/scmspain/controller/TweetController.java b/src/main/java/com/scmspain/controller/TweetController.java index 55ce7cd..3ad1cac 100644 --- a/src/main/java/com/scmspain/controller/TweetController.java +++ b/src/main/java/com/scmspain/controller/TweetController.java @@ -12,6 +12,7 @@ @RestController public class TweetController { + private TweetService tweetService; public TweetController(TweetService tweetService) { @@ -38,4 +39,5 @@ public Object invalidArgumentException(IllegalArgumentException ex) { public String exceptionClass = ex.getClass().getSimpleName(); }; } + } diff --git a/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java b/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java index 543897b..e253514 100644 --- a/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java +++ b/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java @@ -1,6 +1,7 @@ package com.scmspain.controller.command; public class PublishTweetCommand { + private String publisher; private String tweet; @@ -19,4 +20,5 @@ public String getTweet() { public void setTweet(String tweet) { this.tweet = tweet; } + } diff --git a/src/main/java/com/scmspain/entities/Tweet.java b/src/main/java/com/scmspain/entities/Tweet.java index 3616a94..b80905c 100644 --- a/src/main/java/com/scmspain/entities/Tweet.java +++ b/src/main/java/com/scmspain/entities/Tweet.java @@ -7,13 +7,17 @@ @Entity public class Tweet { + @Id @GeneratedValue private Long id; + @Column(nullable = false) private String publisher; + @Column(nullable = false, length = 140) private String tweet; + @Column (nullable=true) private Long pre2015MigrationStatus = 0L; diff --git a/src/main/java/com/scmspain/services/TweetService.java b/src/main/java/com/scmspain/services/TweetService.java index d61bc9d..77e884a 100644 --- a/src/main/java/com/scmspain/services/TweetService.java +++ b/src/main/java/com/scmspain/services/TweetService.java @@ -14,6 +14,7 @@ @Service @Transactional public class TweetService { + private EntityManager entityManager; private MetricWriter metricWriter; @@ -65,4 +66,5 @@ public List listAllTweets() { } return result; } + } diff --git a/src/test/java/com/scmspain/configuration/TestConfiguration.java b/src/test/java/com/scmspain/configuration/TestConfiguration.java index 28a6657..92d665c 100644 --- a/src/test/java/com/scmspain/configuration/TestConfiguration.java +++ b/src/test/java/com/scmspain/configuration/TestConfiguration.java @@ -11,8 +11,10 @@ @Configuration @Import({MsFcTechTestApplication.class}) public class TestConfiguration { + @Bean public MBeanExporter mockExporter() { return mock(MBeanExporter.class); } + } diff --git a/src/test/java/com/scmspain/controller/TweetControllerTest.java b/src/test/java/com/scmspain/controller/TweetControllerTest.java index 4368add..cd468f6 100644 --- a/src/test/java/com/scmspain/controller/TweetControllerTest.java +++ b/src/test/java/com/scmspain/controller/TweetControllerTest.java @@ -26,8 +26,10 @@ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfiguration.class) public class TweetControllerTest { + @Autowired private WebApplicationContext context; + private MockMvc mockMvc; @Before @@ -37,22 +39,27 @@ public void setUp() { @Test public void shouldReturn200WhenInsertingAValidTweet() throws Exception { - mockMvc.perform(newTweet("Prospect", "Breaking the law")) + mockMvc + .perform(newTweet("Prospect", "Breaking the law")) .andExpect(status().is(201)); } @Test public void shouldReturn400WhenInsertingAnInvalidTweet() throws Exception { - mockMvc.perform(newTweet("Schibsted Spain", "We are Schibsted Spain (look at our home page http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome!")) - .andExpect(status().is(400)); + mockMvc + .perform(newTweet("Schibsted Spain", "We are Schibsted Spain (look at our home page http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome!")) + .andExpect(status().is(400)); } @Test public void shouldReturnAllPublishedTweets() throws Exception { - mockMvc.perform(newTweet("Yo", "How are you?")) - .andExpect(status().is(201)); + mockMvc + .perform(newTweet("Yo", "How are you?")) + .andExpect(status().is(201)); - MvcResult getResult = mockMvc.perform(get("/tweet")) + MvcResult getResult = + mockMvc + .perform(get("/tweet")) .andExpect(status().is(200)) .andReturn(); diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index ac88fe5..39aebff 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.verify; public class TweetServiceTest { + private EntityManager entityManager; private MetricWriter metricWriter; private TweetService tweetService; @@ -35,4 +36,5 @@ public void shouldInsertANewTweet() throws Exception { public void shouldThrowAnExceptionWhenTweetLengthIsInvalid() throws Exception { tweetService.publishTweet("Pirate", "LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to drop dead. So he did. Then things really got ugly."); } + } From 997241e3dcdd4a180fb664cd5e1d240b1ae0062f Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 12:16:19 +0200 Subject: [PATCH 02/43] Removing unnecessary exceptions. --- src/test/java/com/scmspain/services/TweetServiceTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 39aebff..f3d1d4f 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -18,7 +18,7 @@ public class TweetServiceTest { private TweetService tweetService; @Before - public void setUp() throws Exception { + public void setUp() { this.entityManager = mock(EntityManager.class); this.metricWriter = mock(MetricWriter.class); @@ -26,14 +26,14 @@ public void setUp() throws Exception { } @Test - public void shouldInsertANewTweet() throws Exception { + public void shouldInsertANewTweet() { tweetService.publishTweet("Guybrush Threepwood", "I am Guybrush Threepwood, mighty pirate."); verify(entityManager).persist(any(Tweet.class)); } @Test(expected = IllegalArgumentException.class) - public void shouldThrowAnExceptionWhenTweetLengthIsInvalid() throws Exception { + public void shouldThrowAnExceptionWhenTweetLengthIsInvalid() { tweetService.publishTweet("Pirate", "LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to drop dead. So he did. Then things really got ugly."); } From bb5f429c54530ae5b9c27d80c806b8d534dca819 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 12:26:59 +0200 Subject: [PATCH 03/43] Conmverting attribute to local variable. --- src/test/java/com/scmspain/services/TweetServiceTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index f3d1d4f..8ee8b63 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -14,13 +14,12 @@ public class TweetServiceTest { private EntityManager entityManager; - private MetricWriter metricWriter; private TweetService tweetService; @Before public void setUp() { this.entityManager = mock(EntityManager.class); - this.metricWriter = mock(MetricWriter.class); + MetricWriter metricWriter = mock(MetricWriter.class); this.tweetService = new TweetService(entityManager, metricWriter); } From 999042c12735f34123b41aa700682555310c7543 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 12:36:40 +0200 Subject: [PATCH 04/43] Cleaning tweet entity. --- .../java/com/scmspain/entities/Tweet.java | 62 ++++++++++--------- .../com/scmspain/services/TweetService.java | 4 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/scmspain/entities/Tweet.java b/src/main/java/com/scmspain/entities/Tweet.java index b80905c..5ea53f4 100644 --- a/src/main/java/com/scmspain/entities/Tweet.java +++ b/src/main/java/com/scmspain/entities/Tweet.java @@ -5,6 +5,9 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; +/** + * Tweet entity. + */ @Entity public class Tweet { @@ -15,45 +18,44 @@ public class Tweet { @Column(nullable = false) private String publisher; - @Column(nullable = false, length = 140) - private String tweet; + @Column(name = "tweet", nullable = false, length = 140) + private String text; - @Column (nullable=true) + @Column private Long pre2015MigrationStatus = 0L; - public Tweet() { - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; + /** + * Constructor to help the persistence framework to instantiate the entity. + */ + private Tweet() { } + + /** + * Constructor with parameters. + * + * @param publisher Tweet's publisher. + * @param text Tweet's text. + */ + public Tweet(final String publisher, final String text) { + this.publisher = publisher; + this.text = text; } + /** + * Gets the publisher of the tweet. + * + * @return Publisher. + */ public String getPublisher() { return publisher; } - public void setPublisher(String publisher) { - this.publisher = publisher; - } - - public String getTweet() { - return tweet; - } - - public void setTweet(String tweet) { - this.tweet = tweet; - } - - public Long getPre2015MigrationStatus() { - return pre2015MigrationStatus; - } - - public void setPre2015MigrationStatus(Long pre2015MigrationStatus) { - this.pre2015MigrationStatus = pre2015MigrationStatus; + /** + * Gets the text of the tweet. + * + * @return Text. + */ + public String getText() { + return text; } } diff --git a/src/main/java/com/scmspain/services/TweetService.java b/src/main/java/com/scmspain/services/TweetService.java index 77e884a..262fcc4 100644 --- a/src/main/java/com/scmspain/services/TweetService.java +++ b/src/main/java/com/scmspain/services/TweetService.java @@ -31,9 +31,7 @@ public TweetService(EntityManager entityManager, MetricWriter metricWriter) { */ public void publishTweet(String publisher, String text) { if (publisher != null && publisher.length() > 0 && text != null && text.length() > 0 && text.length() < 140) { - Tweet tweet = new Tweet(); - tweet.setTweet(text); - tweet.setPublisher(publisher); + Tweet tweet = new Tweet(publisher, text); this.metricWriter.increment(new Delta("published-tweets", 1)); this.entityManager.persist(tweet); From cf7597a00fcd1ed09c5d8ea43ee2e932aac66a15 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 12:40:03 +0200 Subject: [PATCH 05/43] Minor format change. --- .../scmspain/configuration/InfrastructureConfiguration.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java index 91cb57b..032bafa 100644 --- a/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java @@ -10,7 +10,8 @@ @Configuration public class InfrastructureConfiguration { - @Bean @ExportMetricWriter + @Bean + @ExportMetricWriter public MetricWriter getMetricWriter(MBeanExporter exporter) { return new JmxMetricWriter(exporter); } From 43896a6256c4fc3954a703e4db959f11a27cad4e Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 12:40:52 +0200 Subject: [PATCH 06/43] Updating to final some attributes. --- .../java/com/scmspain/controller/TweetController.java | 2 +- src/main/java/com/scmspain/services/TweetService.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/scmspain/controller/TweetController.java b/src/main/java/com/scmspain/controller/TweetController.java index 3ad1cac..289ecfc 100644 --- a/src/main/java/com/scmspain/controller/TweetController.java +++ b/src/main/java/com/scmspain/controller/TweetController.java @@ -13,7 +13,7 @@ @RestController public class TweetController { - private TweetService tweetService; + private final TweetService tweetService; public TweetController(TweetService tweetService) { this.tweetService = tweetService; diff --git a/src/main/java/com/scmspain/services/TweetService.java b/src/main/java/com/scmspain/services/TweetService.java index 262fcc4..c863056 100644 --- a/src/main/java/com/scmspain/services/TweetService.java +++ b/src/main/java/com/scmspain/services/TweetService.java @@ -15,8 +15,8 @@ @Transactional public class TweetService { - private EntityManager entityManager; - private MetricWriter metricWriter; + private final EntityManager entityManager; + private final MetricWriter metricWriter; public TweetService(EntityManager entityManager, MetricWriter metricWriter) { this.entityManager = entityManager; @@ -45,8 +45,8 @@ public void publishTweet(String publisher, String text) { Parameter - id - id of the Tweet to retrieve Result - retrieved Tweet */ - public Tweet getTweet(Long id) { - return this.entityManager.find(Tweet.class, id); + private Tweet getTweet(Long id) { + return this.entityManager.find(Tweet.class, id); } /** @@ -55,7 +55,7 @@ public Tweet getTweet(Long id) { Result - retrieved Tweet */ public List listAllTweets() { - List result = new ArrayList(); + List result = new ArrayList<>(); this.metricWriter.increment(new Delta("times-queried-tweets", 1)); TypedQuery query = this.entityManager.createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); List ids = query.getResultList(); From 6ad91e0be6bcaf5a8898a24e157254867a9bbd5f Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 12:44:15 +0200 Subject: [PATCH 07/43] Cleaning the publish tweet command. --- .../command/PublishTweetCommand.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java b/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java index e253514..77a44b9 100644 --- a/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java +++ b/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java @@ -1,24 +1,29 @@ package com.scmspain.controller.command; +/** + * Command for publish a tweet. + */ public class PublishTweetCommand { private String publisher; private String tweet; + /** + * Gets the publisher of the tweet to be published. + * + * @return Publisher. + */ public String getPublisher() { return publisher; } - public void setPublisher(String publisher) { - this.publisher = publisher; - } - + /** + * Gets the tweet text of the tweet to be published. + * + * @return Tweet text. + */ public String getTweet() { return tweet; } - public void setTweet(String tweet) { - this.tweet = tweet; - } - } From e18738d70c473fdee5fb02f7a8eb05eb04344d99 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 12:48:59 +0200 Subject: [PATCH 08/43] First refactor to port-adapter design. --- src/main/java/com/scmspain/MsFcTechTestApplication.java | 4 ++-- .../scmspain/{ => application}/services/TweetService.java | 4 ++-- .../command/PublishTweetCommand.java | 2 +- .../configuration/InfrastructureConfiguration.java | 2 +- .../configuration/TweetConfiguration.java | 6 +++--- .../configuration}/entities/Tweet.java | 2 +- .../{ => infrastructure}/controller/TweetController.java | 8 ++++---- .../configuration/TestConfiguration.java | 2 +- .../controller/TweetControllerTest.java | 4 ++-- src/test/java/com/scmspain/services/TweetServiceTest.java | 3 ++- 10 files changed, 19 insertions(+), 18 deletions(-) rename src/main/java/com/scmspain/{ => application}/services/TweetService.java (95%) rename src/main/java/com/scmspain/{controller => domain}/command/PublishTweetCommand.java (92%) rename src/main/java/com/scmspain/{ => infrastructure}/configuration/InfrastructureConfiguration.java (92%) rename src/main/java/com/scmspain/{ => infrastructure}/configuration/TweetConfiguration.java (77%) rename src/main/java/com/scmspain/{ => infrastructure/configuration}/entities/Tweet.java (94%) rename src/main/java/com/scmspain/{ => infrastructure}/controller/TweetController.java (83%) rename src/test/java/com/scmspain/{ => infrastructure}/configuration/TestConfiguration.java (90%) rename src/test/java/com/scmspain/{ => infrastructure}/controller/TweetControllerTest.java (96%) diff --git a/src/main/java/com/scmspain/MsFcTechTestApplication.java b/src/main/java/com/scmspain/MsFcTechTestApplication.java index 03af06d..e40fb18 100644 --- a/src/main/java/com/scmspain/MsFcTechTestApplication.java +++ b/src/main/java/com/scmspain/MsFcTechTestApplication.java @@ -1,7 +1,7 @@ package com.scmspain; -import com.scmspain.configuration.InfrastructureConfiguration; -import com.scmspain.configuration.TweetConfiguration; +import com.scmspain.infrastructure.configuration.InfrastructureConfiguration; +import com.scmspain.infrastructure.configuration.TweetConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/scmspain/services/TweetService.java b/src/main/java/com/scmspain/application/services/TweetService.java similarity index 95% rename from src/main/java/com/scmspain/services/TweetService.java rename to src/main/java/com/scmspain/application/services/TweetService.java index c863056..62a6e40 100644 --- a/src/main/java/com/scmspain/services/TweetService.java +++ b/src/main/java/com/scmspain/application/services/TweetService.java @@ -1,6 +1,6 @@ -package com.scmspain.services; +package com.scmspain.application.services; -import com.scmspain.entities.Tweet; +import com.scmspain.infrastructure.configuration.entities.Tweet; import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java b/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java similarity index 92% rename from src/main/java/com/scmspain/controller/command/PublishTweetCommand.java rename to src/main/java/com/scmspain/domain/command/PublishTweetCommand.java index 77a44b9..c43a082 100644 --- a/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java +++ b/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java @@ -1,4 +1,4 @@ -package com.scmspain.controller.command; +package com.scmspain.domain.command; /** * Command for publish a tweet. diff --git a/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java similarity index 92% rename from src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java rename to src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java index 032bafa..f242220 100644 --- a/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java @@ -1,4 +1,4 @@ -package com.scmspain.configuration; +package com.scmspain.infrastructure.configuration; import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter; import org.springframework.boot.actuate.metrics.jmx.JmxMetricWriter; diff --git a/src/main/java/com/scmspain/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java similarity index 77% rename from src/main/java/com/scmspain/configuration/TweetConfiguration.java rename to src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 6a48956..f326cc6 100644 --- a/src/main/java/com/scmspain/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -1,7 +1,7 @@ -package com.scmspain.configuration; +package com.scmspain.infrastructure.configuration; -import com.scmspain.controller.TweetController; -import com.scmspain.services.TweetService; +import com.scmspain.infrastructure.controller.TweetController; +import com.scmspain.application.services.TweetService; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/scmspain/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/configuration/entities/Tweet.java similarity index 94% rename from src/main/java/com/scmspain/entities/Tweet.java rename to src/main/java/com/scmspain/infrastructure/configuration/entities/Tweet.java index 5ea53f4..d9bfa3b 100644 --- a/src/main/java/com/scmspain/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/entities/Tweet.java @@ -1,4 +1,4 @@ -package com.scmspain.entities; +package com.scmspain.infrastructure.configuration.entities; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/src/main/java/com/scmspain/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java similarity index 83% rename from src/main/java/com/scmspain/controller/TweetController.java rename to src/main/java/com/scmspain/infrastructure/controller/TweetController.java index 289ecfc..78463e5 100644 --- a/src/main/java/com/scmspain/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -1,8 +1,8 @@ -package com.scmspain.controller; +package com.scmspain.infrastructure.controller; -import com.scmspain.controller.command.PublishTweetCommand; -import com.scmspain.entities.Tweet; -import com.scmspain.services.TweetService; +import com.scmspain.domain.command.PublishTweetCommand; +import com.scmspain.infrastructure.configuration.entities.Tweet; +import com.scmspain.application.services.TweetService; import org.springframework.web.bind.annotation.*; import java.util.List; diff --git a/src/test/java/com/scmspain/configuration/TestConfiguration.java b/src/test/java/com/scmspain/infrastructure/configuration/TestConfiguration.java similarity index 90% rename from src/test/java/com/scmspain/configuration/TestConfiguration.java rename to src/test/java/com/scmspain/infrastructure/configuration/TestConfiguration.java index 92d665c..bb1ef9a 100644 --- a/src/test/java/com/scmspain/configuration/TestConfiguration.java +++ b/src/test/java/com/scmspain/infrastructure/configuration/TestConfiguration.java @@ -1,4 +1,4 @@ -package com.scmspain.configuration; +package com.scmspain.infrastructure.configuration; import com.scmspain.MsFcTechTestApplication; import org.springframework.context.annotation.Bean; diff --git a/src/test/java/com/scmspain/controller/TweetControllerTest.java b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java similarity index 96% rename from src/test/java/com/scmspain/controller/TweetControllerTest.java rename to src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java index cd468f6..e4d06b4 100644 --- a/src/test/java/com/scmspain/controller/TweetControllerTest.java +++ b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java @@ -1,7 +1,7 @@ -package com.scmspain.controller; +package com.scmspain.infrastructure.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.scmspain.configuration.TestConfiguration; +import com.scmspain.infrastructure.configuration.TestConfiguration; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 8ee8b63..716276e 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,6 +1,7 @@ package com.scmspain.services; -import com.scmspain.entities.Tweet; +import com.scmspain.application.services.TweetService; +import com.scmspain.infrastructure.configuration.entities.Tweet; import org.junit.Before; import org.junit.Test; import org.springframework.boot.actuate.metrics.writer.MetricWriter; From 2eea920cd652bd9dce1d5ee37a5a03c516d4af3e Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 12:50:34 +0200 Subject: [PATCH 09/43] First refactor to port-adapter design. --- .../java/com/scmspain/application/services/TweetService.java | 2 +- .../com/scmspain/infrastructure/controller/TweetController.java | 2 +- .../infrastructure/{configuration => }/entities/Tweet.java | 2 +- src/test/java/com/scmspain/services/TweetServiceTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/com/scmspain/infrastructure/{configuration => }/entities/Tweet.java (94%) diff --git a/src/main/java/com/scmspain/application/services/TweetService.java b/src/main/java/com/scmspain/application/services/TweetService.java index 62a6e40..b5e3b48 100644 --- a/src/main/java/com/scmspain/application/services/TweetService.java +++ b/src/main/java/com/scmspain/application/services/TweetService.java @@ -1,6 +1,6 @@ package com.scmspain.application.services; -import com.scmspain.infrastructure.configuration.entities.Tweet; +import com.scmspain.infrastructure.entities.Tweet; import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index 78463e5..e8c190b 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -1,7 +1,7 @@ package com.scmspain.infrastructure.controller; import com.scmspain.domain.command.PublishTweetCommand; -import com.scmspain.infrastructure.configuration.entities.Tweet; +import com.scmspain.infrastructure.entities.Tweet; import com.scmspain.application.services.TweetService; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/scmspain/infrastructure/configuration/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/entities/Tweet.java similarity index 94% rename from src/main/java/com/scmspain/infrastructure/configuration/entities/Tweet.java rename to src/main/java/com/scmspain/infrastructure/entities/Tweet.java index d9bfa3b..15f3318 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/entities/Tweet.java @@ -1,4 +1,4 @@ -package com.scmspain.infrastructure.configuration.entities; +package com.scmspain.infrastructure.entities; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 716276e..99ba691 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,7 +1,7 @@ package com.scmspain.services; import com.scmspain.application.services.TweetService; -import com.scmspain.infrastructure.configuration.entities.Tweet; +import com.scmspain.infrastructure.entities.Tweet; import org.junit.Before; import org.junit.Test; import org.springframework.boot.actuate.metrics.writer.MetricWriter; From cbed6097816dc3de2ecc21589fd8ed224f571d31 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 13:18:38 +0200 Subject: [PATCH 10/43] Moving persistence logic to infrastructure package and improving test. --- .../application/services/TweetService.java | 46 ++----------- .../InfrastructureConfiguration.java | 9 +++ .../configuration/TweetConfiguration.java | 8 +-- .../controller/TweetController.java | 2 +- .../database/TweetRepository.java | 66 +++++++++++++++++++ .../{ => database}/entities/Tweet.java | 2 +- .../scmspain/services/TweetServiceTest.java | 23 ++++--- 7 files changed, 103 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/scmspain/infrastructure/database/TweetRepository.java rename src/main/java/com/scmspain/infrastructure/{ => database}/entities/Tweet.java (95%) diff --git a/src/main/java/com/scmspain/application/services/TweetService.java b/src/main/java/com/scmspain/application/services/TweetService.java index b5e3b48..db110fa 100644 --- a/src/main/java/com/scmspain/application/services/TweetService.java +++ b/src/main/java/com/scmspain/application/services/TweetService.java @@ -1,68 +1,36 @@ package com.scmspain.application.services; -import com.scmspain.infrastructure.entities.Tweet; +import com.scmspain.infrastructure.database.TweetRepository; +import com.scmspain.infrastructure.database.entities.Tweet; import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.stereotype.Service; -import javax.persistence.EntityManager; -import javax.persistence.TypedQuery; -import javax.transaction.Transactional; -import java.util.ArrayList; import java.util.List; @Service -@Transactional public class TweetService { - private final EntityManager entityManager; + private final TweetRepository tweetRepository; private final MetricWriter metricWriter; - public TweetService(EntityManager entityManager, MetricWriter metricWriter) { - this.entityManager = entityManager; + public TweetService(TweetRepository tweetRepository, MetricWriter metricWriter) { + this.tweetRepository = tweetRepository; this.metricWriter = metricWriter; } - /** - Push tweet to repository - Parameter - publisher - creator of the Tweet - Parameter - text - Content of the Tweet - Result - recovered Tweet - */ public void publishTweet(String publisher, String text) { if (publisher != null && publisher.length() > 0 && text != null && text.length() > 0 && text.length() < 140) { - Tweet tweet = new Tweet(publisher, text); - this.metricWriter.increment(new Delta("published-tweets", 1)); - this.entityManager.persist(tweet); + this.tweetRepository.publishTweet(publisher, text); } else { throw new IllegalArgumentException("Tweet must not be greater than 140 characters"); } } - /** - Recover tweet from repository - Parameter - id - id of the Tweet to retrieve - Result - retrieved Tweet - */ - private Tweet getTweet(Long id) { - return this.entityManager.find(Tweet.class, id); - } - - /** - Recover tweet from repository - Parameter - id - id of the Tweet to retrieve - Result - retrieved Tweet - */ public List listAllTweets() { - List result = new ArrayList<>(); this.metricWriter.increment(new Delta("times-queried-tweets", 1)); - TypedQuery query = this.entityManager.createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); - List ids = query.getResultList(); - for (Long id : ids) { - result.add(getTweet(id)); - } - return result; + return this.tweetRepository.listAllTweets(); } } diff --git a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java index f242220..f77bf04 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java @@ -1,5 +1,7 @@ package com.scmspain.infrastructure.configuration; +import javax.persistence.EntityManager; + import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter; import org.springframework.boot.actuate.metrics.jmx.JmxMetricWriter; import org.springframework.boot.actuate.metrics.writer.MetricWriter; @@ -7,9 +9,16 @@ import org.springframework.context.annotation.Configuration; import org.springframework.jmx.export.MBeanExporter; +import com.scmspain.infrastructure.database.TweetRepository; + @Configuration public class InfrastructureConfiguration { + @Bean + public TweetRepository getTweetRepository(final EntityManager entityManager) { + return new TweetRepository(entityManager); + } + @Bean @ExportMetricWriter public MetricWriter getMetricWriter(MBeanExporter exporter) { diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index f326cc6..7bdc9a8 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -2,18 +2,18 @@ import com.scmspain.infrastructure.controller.TweetController; import com.scmspain.application.services.TweetService; +import com.scmspain.infrastructure.database.TweetRepository; + import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import javax.persistence.EntityManager; - @Configuration public class TweetConfiguration { @Bean - public TweetService getTweetService(EntityManager entityManager, MetricWriter metricWriter) { - return new TweetService(entityManager, metricWriter); + public TweetService getTweetService(TweetRepository tweetRepository, MetricWriter metricWriter) { + return new TweetService(tweetRepository, metricWriter); } @Bean diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index e8c190b..c2e23a5 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -1,7 +1,7 @@ package com.scmspain.infrastructure.controller; import com.scmspain.domain.command.PublishTweetCommand; -import com.scmspain.infrastructure.entities.Tweet; +import com.scmspain.infrastructure.database.entities.Tweet; import com.scmspain.application.services.TweetService; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java new file mode 100644 index 0000000..3da027d --- /dev/null +++ b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java @@ -0,0 +1,66 @@ +package com.scmspain.infrastructure.database; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.transaction.Transactional; + +import org.springframework.stereotype.Repository; + +import com.scmspain.infrastructure.database.entities.Tweet; + +/** + * Repository implementation for tweets with an entity manager. + */ +@Repository +@Transactional +public class TweetRepository { + + private final EntityManager entityManager; + + /** + * Constructor. + * + * @param entityManager Entity manager to access the persistence context. + */ + public TweetRepository(EntityManager entityManager) { + this.entityManager = entityManager; + } + + /** + * Push tweet to repository. + * + * @param publisher Creator of the tweet. + * @param text Content of the tweet. + */ + public void publishTweet(String publisher, String text) { + Tweet tweet = new Tweet(publisher, text); + this.entityManager.persist(tweet); + } + + /** + * Recover tweet from repository. + * @param id Identifier of the Tweet to retrieve + * @return Retrieved tweet. + */ + private Tweet getTweet(Long id) { + return this.entityManager.find(Tweet.class, id); + } + + /** + * Recover a list with all the tweets from repository. + * @return Retrieved list of tweets. + */ + public List listAllTweets() { + List result = new ArrayList<>(); + TypedQuery query = this.entityManager.createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); + List ids = query.getResultList(); + for (Long id : ids) { + result.add(getTweet(id)); + } + return result; + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java similarity index 95% rename from src/main/java/com/scmspain/infrastructure/entities/Tweet.java rename to src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java index 15f3318..363e923 100644 --- a/src/main/java/com/scmspain/infrastructure/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java @@ -1,4 +1,4 @@ -package com.scmspain.infrastructure.entities; +package com.scmspain.infrastructure.database.entities; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 99ba691..9fe4250 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,35 +1,42 @@ package com.scmspain.services; import com.scmspain.application.services.TweetService; -import com.scmspain.infrastructure.entities.Tweet; +import com.scmspain.infrastructure.database.TweetRepository; +import com.scmspain.infrastructure.database.entities.Tweet; import org.junit.Before; import org.junit.Test; +import org.mockito.InOrder; +import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import javax.persistence.EntityManager; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; public class TweetServiceTest { private EntityManager entityManager; + private MetricWriter metricWriter; private TweetService tweetService; @Before public void setUp() { this.entityManager = mock(EntityManager.class); - MetricWriter metricWriter = mock(MetricWriter.class); - - this.tweetService = new TweetService(entityManager, metricWriter); + TweetRepository tweetRepository = new TweetRepository(entityManager); + this.metricWriter = mock(MetricWriter.class); + this.tweetService = new TweetService(tweetRepository, metricWriter); } @Test public void shouldInsertANewTweet() { - tweetService.publishTweet("Guybrush Threepwood", "I am Guybrush Threepwood, mighty pirate."); - - verify(entityManager).persist(any(Tweet.class)); + String publisher = "Guybrush Threepwood"; + String text = "I am Guybrush Threepwood, mighty pirate."; + tweetService.publishTweet(publisher, text); + InOrder inOrder = inOrder(metricWriter, entityManager); + inOrder.verify(metricWriter).increment(any(Delta.class)); + inOrder.verify(entityManager).persist(any(Tweet.class)); } @Test(expected = IllegalArgumentException.class) From 6570bec98a04e423991ef515cbd4ab0ad30bf2ea Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 13:24:12 +0200 Subject: [PATCH 11/43] Extracting interface (port) for tweet repository. --- .../application/services/TweetRepository.java | 26 +++++++++++++++++++ .../application/services/TweetService.java | 1 - .../InfrastructureConfiguration.java | 5 ++-- .../configuration/TweetConfiguration.java | 2 +- ...java => TweetEntityManagerRepository.java} | 25 ++++++++++-------- .../scmspain/services/TweetServiceTest.java | 5 ++-- 6 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/scmspain/application/services/TweetRepository.java rename src/main/java/com/scmspain/infrastructure/database/{TweetRepository.java => TweetEntityManagerRepository.java} (88%) diff --git a/src/main/java/com/scmspain/application/services/TweetRepository.java b/src/main/java/com/scmspain/application/services/TweetRepository.java new file mode 100644 index 0000000..3f326f4 --- /dev/null +++ b/src/main/java/com/scmspain/application/services/TweetRepository.java @@ -0,0 +1,26 @@ +package com.scmspain.application.services; + +import java.util.List; + +import com.scmspain.infrastructure.database.entities.Tweet; + +/** + * Repository for tweets. + */ +public interface TweetRepository { + + /** + * Push tweet to repository. + * + * @param publisher Creator of the tweet. + * @param text Content of the tweet. + */ + void publishTweet(String publisher, String text); + + /** + * Recover a list with all the tweets from repository. + * @return Retrieved list of tweets. + */ + List listAllTweets(); + +} diff --git a/src/main/java/com/scmspain/application/services/TweetService.java b/src/main/java/com/scmspain/application/services/TweetService.java index db110fa..e1cf9fb 100644 --- a/src/main/java/com/scmspain/application/services/TweetService.java +++ b/src/main/java/com/scmspain/application/services/TweetService.java @@ -1,6 +1,5 @@ package com.scmspain.application.services; -import com.scmspain.infrastructure.database.TweetRepository; import com.scmspain.infrastructure.database.entities.Tweet; import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; diff --git a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java index f77bf04..3b5f47b 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java @@ -9,14 +9,15 @@ import org.springframework.context.annotation.Configuration; import org.springframework.jmx.export.MBeanExporter; -import com.scmspain.infrastructure.database.TweetRepository; +import com.scmspain.application.services.TweetRepository; +import com.scmspain.infrastructure.database.TweetEntityManagerRepository; @Configuration public class InfrastructureConfiguration { @Bean public TweetRepository getTweetRepository(final EntityManager entityManager) { - return new TweetRepository(entityManager); + return new TweetEntityManagerRepository(entityManager); } @Bean diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 7bdc9a8..dc8799d 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -2,7 +2,7 @@ import com.scmspain.infrastructure.controller.TweetController; import com.scmspain.application.services.TweetService; -import com.scmspain.infrastructure.database.TweetRepository; +import com.scmspain.application.services.TweetRepository; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java similarity index 88% rename from src/main/java/com/scmspain/infrastructure/database/TweetRepository.java rename to src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java index 3da027d..d557293 100644 --- a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java +++ b/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Repository; +import com.scmspain.application.services.TweetRepository; import com.scmspain.infrastructure.database.entities.Tweet; /** @@ -16,7 +17,7 @@ */ @Repository @Transactional -public class TweetRepository { +public class TweetEntityManagerRepository implements TweetRepository { private final EntityManager entityManager; @@ -25,7 +26,7 @@ public class TweetRepository { * * @param entityManager Entity manager to access the persistence context. */ - public TweetRepository(EntityManager entityManager) { + public TweetEntityManagerRepository(EntityManager entityManager) { this.entityManager = entityManager; } @@ -35,24 +36,17 @@ public TweetRepository(EntityManager entityManager) { * @param publisher Creator of the tweet. * @param text Content of the tweet. */ + @Override public void publishTweet(String publisher, String text) { Tweet tweet = new Tweet(publisher, text); this.entityManager.persist(tweet); } - /** - * Recover tweet from repository. - * @param id Identifier of the Tweet to retrieve - * @return Retrieved tweet. - */ - private Tweet getTweet(Long id) { - return this.entityManager.find(Tweet.class, id); - } - /** * Recover a list with all the tweets from repository. * @return Retrieved list of tweets. */ + @Override public List listAllTweets() { List result = new ArrayList<>(); TypedQuery query = this.entityManager.createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); @@ -63,4 +57,13 @@ public List listAllTweets() { return result; } + /** + * Recover tweet from repository. + * @param id Identifier of the Tweet to retrieve + * @return Retrieved tweet. + */ + private Tweet getTweet(Long id) { + return this.entityManager.find(Tweet.class, id); + } + } diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 9fe4250..c6a6057 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,7 +1,8 @@ package com.scmspain.services; import com.scmspain.application.services.TweetService; -import com.scmspain.infrastructure.database.TweetRepository; +import com.scmspain.application.services.TweetRepository; +import com.scmspain.infrastructure.database.TweetEntityManagerRepository; import com.scmspain.infrastructure.database.entities.Tweet; import org.junit.Before; import org.junit.Test; @@ -24,7 +25,7 @@ public class TweetServiceTest { @Before public void setUp() { this.entityManager = mock(EntityManager.class); - TweetRepository tweetRepository = new TweetRepository(entityManager); + TweetRepository tweetRepository = new TweetEntityManagerRepository(entityManager); this.metricWriter = mock(MetricWriter.class); this.tweetService = new TweetService(tweetRepository, metricWriter); } From 83aca5133ca4bd38d9bcb1790a953adf4b97e94c Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 13:32:30 +0200 Subject: [PATCH 12/43] Use streams for list all tweets implementation. --- .../database/TweetEntityManagerRepository.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java b/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java index d557293..9a0f7d1 100644 --- a/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java +++ b/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java @@ -1,7 +1,7 @@ package com.scmspain.infrastructure.database; -import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -48,13 +48,15 @@ public void publishTweet(String publisher, String text) { */ @Override public List listAllTweets() { - List result = new ArrayList<>(); - TypedQuery query = this.entityManager.createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); - List ids = query.getResultList(); - for (Long id : ids) { - result.add(getTweet(id)); - } - return result; + TypedQuery query = + this.entityManager + .createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); + + return query + .getResultList() + .stream() + .map(this::getTweet) + .collect(Collectors.toList()); } /** From a7d5c38e89ba40f047dd6ac04ecbefc5243a8ec5 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 14:04:24 +0200 Subject: [PATCH 13/43] Removing from application dependency on Tweet entity at infrastructure. --- .../application/services/TweetRepository.java | 5 ++- .../application/services/TweetService.java | 4 +- .../scmspain/domain/model/TweetResponse.java | 40 +++++++++++++++++++ .../controller/TweetController.java | 4 +- .../TweetEntityManagerRepository.java | 13 ++++-- 5 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/scmspain/domain/model/TweetResponse.java diff --git a/src/main/java/com/scmspain/application/services/TweetRepository.java b/src/main/java/com/scmspain/application/services/TweetRepository.java index 3f326f4..554ffd3 100644 --- a/src/main/java/com/scmspain/application/services/TweetRepository.java +++ b/src/main/java/com/scmspain/application/services/TweetRepository.java @@ -2,7 +2,7 @@ import java.util.List; -import com.scmspain.infrastructure.database.entities.Tweet; +import com.scmspain.domain.model.TweetResponse; /** * Repository for tweets. @@ -19,8 +19,9 @@ public interface TweetRepository { /** * Recover a list with all the tweets from repository. + * * @return Retrieved list of tweets. */ - List listAllTweets(); + List listAllTweets(); } diff --git a/src/main/java/com/scmspain/application/services/TweetService.java b/src/main/java/com/scmspain/application/services/TweetService.java index e1cf9fb..57bc85f 100644 --- a/src/main/java/com/scmspain/application/services/TweetService.java +++ b/src/main/java/com/scmspain/application/services/TweetService.java @@ -1,6 +1,6 @@ package com.scmspain.application.services; -import com.scmspain.infrastructure.database.entities.Tweet; +import com.scmspain.domain.model.TweetResponse; import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.stereotype.Service; @@ -27,7 +27,7 @@ public void publishTweet(String publisher, String text) { } } - public List listAllTweets() { + public List listAllTweets() { this.metricWriter.increment(new Delta("times-queried-tweets", 1)); return this.tweetRepository.listAllTweets(); } diff --git a/src/main/java/com/scmspain/domain/model/TweetResponse.java b/src/main/java/com/scmspain/domain/model/TweetResponse.java new file mode 100644 index 0000000..492138e --- /dev/null +++ b/src/main/java/com/scmspain/domain/model/TweetResponse.java @@ -0,0 +1,40 @@ +package com.scmspain.domain.model; + +/** + * Tweet response. + */ +public class TweetResponse { + + private final String publisher; + private final String tweet; + + /** + * Constructor. + * + * @param publisher Creator of the tweet. + * @param tweet Content of the tweet. + */ + public TweetResponse(final String publisher, final String tweet) { + this.publisher = publisher; + this.tweet = tweet; + } + + /** + * Gets the creator of the tweet. + * + * @return Creator of the tweet. + */ + public String getPublisher() { + return publisher; + } + + /** + * Gets the content of the tweet. + * + * @return Content of the tweet. + */ + public String getTweet() { + return tweet; + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index c2e23a5..3ff4c53 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -1,7 +1,7 @@ package com.scmspain.infrastructure.controller; import com.scmspain.domain.command.PublishTweetCommand; -import com.scmspain.infrastructure.database.entities.Tweet; +import com.scmspain.domain.model.TweetResponse; import com.scmspain.application.services.TweetService; import org.springframework.web.bind.annotation.*; @@ -20,7 +20,7 @@ public TweetController(TweetService tweetService) { } @GetMapping("/tweet") - public List listAllTweets() { + public List listAllTweets() { return this.tweetService.listAllTweets(); } diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java b/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java index 9a0f7d1..f1becdd 100644 --- a/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java +++ b/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Repository; import com.scmspain.application.services.TweetRepository; +import com.scmspain.domain.model.TweetResponse; import com.scmspain.infrastructure.database.entities.Tweet; /** @@ -44,15 +45,17 @@ public void publishTweet(String publisher, String text) { /** * Recover a list with all the tweets from repository. + * * @return Retrieved list of tweets. */ @Override - public List listAllTweets() { + public List listAllTweets() { TypedQuery query = this.entityManager .createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); - return query + return + query .getResultList() .stream() .map(this::getTweet) @@ -61,11 +64,13 @@ public List listAllTweets() { /** * Recover tweet from repository. + * * @param id Identifier of the Tweet to retrieve * @return Retrieved tweet. */ - private Tweet getTweet(Long id) { - return this.entityManager.find(Tweet.class, id); + private TweetResponse getTweet(final Long id) { + final Tweet tweet = this.entityManager.find(Tweet.class, id); + return new TweetResponse(tweet.getPublisher(), tweet.getText()); } } From 01e1fb2d9038514b6c63ec597cdce45e296beb5c Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 14:19:08 +0200 Subject: [PATCH 14/43] Extracting interface (port) for metric service. --- .../application/services/MetricService.java | 18 ++++++++ .../application/services/TweetService.java | 12 +++--- .../InfrastructureConfiguration.java | 20 ++++++--- .../configuration/TweetConfiguration.java | 9 ++-- .../metrics/SpringActuatorMetricService.java | 41 +++++++++++++++++++ .../scmspain/services/TweetServiceTest.java | 8 +++- 6 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/scmspain/application/services/MetricService.java create mode 100644 src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java diff --git a/src/main/java/com/scmspain/application/services/MetricService.java b/src/main/java/com/scmspain/application/services/MetricService.java new file mode 100644 index 0000000..2e84dce --- /dev/null +++ b/src/main/java/com/scmspain/application/services/MetricService.java @@ -0,0 +1,18 @@ +package com.scmspain.application.services; + +/** + * Metric service. + */ +public interface MetricService { + + /** + * Increment by one the published tweets metrics. + */ + void incrementPublishedTweets(); + + /** + * Increment by one the times queried tweets metrics. + */ + void incrementTimesQueriedTweets(); + +} diff --git a/src/main/java/com/scmspain/application/services/TweetService.java b/src/main/java/com/scmspain/application/services/TweetService.java index 57bc85f..bb07779 100644 --- a/src/main/java/com/scmspain/application/services/TweetService.java +++ b/src/main/java/com/scmspain/application/services/TweetService.java @@ -1,8 +1,6 @@ package com.scmspain.application.services; import com.scmspain.domain.model.TweetResponse; -import org.springframework.boot.actuate.metrics.writer.Delta; -import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.stereotype.Service; import java.util.List; @@ -11,16 +9,16 @@ public class TweetService { private final TweetRepository tweetRepository; - private final MetricWriter metricWriter; + private final MetricService metricService; - public TweetService(TweetRepository tweetRepository, MetricWriter metricWriter) { + public TweetService(TweetRepository tweetRepository, MetricService metricService) { this.tweetRepository = tweetRepository; - this.metricWriter = metricWriter; + this.metricService = metricService; } public void publishTweet(String publisher, String text) { if (publisher != null && publisher.length() > 0 && text != null && text.length() > 0 && text.length() < 140) { - this.metricWriter.increment(new Delta("published-tweets", 1)); + this.metricService.incrementPublishedTweets(); this.tweetRepository.publishTweet(publisher, text); } else { throw new IllegalArgumentException("Tweet must not be greater than 140 characters"); @@ -28,7 +26,7 @@ public void publishTweet(String publisher, String text) { } public List listAllTweets() { - this.metricWriter.increment(new Delta("times-queried-tweets", 1)); + this.metricService.incrementTimesQueriedTweets(); return this.tweetRepository.listAllTweets(); } diff --git a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java index 3b5f47b..85e0aa7 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java @@ -9,21 +9,31 @@ import org.springframework.context.annotation.Configuration; import org.springframework.jmx.export.MBeanExporter; +import com.scmspain.application.services.MetricService; import com.scmspain.application.services.TweetRepository; import com.scmspain.infrastructure.database.TweetEntityManagerRepository; +import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; +/** + * Configuration for the infrastructure. + */ @Configuration public class InfrastructureConfiguration { @Bean - public TweetRepository getTweetRepository(final EntityManager entityManager) { - return new TweetEntityManagerRepository(entityManager); + @ExportMetricWriter + public MetricWriter getMetricWriter(final MBeanExporter exporter) { + return new JmxMetricWriter(exporter); } @Bean - @ExportMetricWriter - public MetricWriter getMetricWriter(MBeanExporter exporter) { - return new JmxMetricWriter(exporter); + public MetricService getMetricService(final MetricWriter metricWriter) { + return new SpringActuatorMetricService(metricWriter); + } + + @Bean + public TweetRepository getTweetRepository(final EntityManager entityManager) { + return new TweetEntityManagerRepository(entityManager); } } diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index dc8799d..b8a2dd6 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -1,19 +1,22 @@ package com.scmspain.infrastructure.configuration; +import com.scmspain.application.services.MetricService; import com.scmspain.infrastructure.controller.TweetController; import com.scmspain.application.services.TweetService; import com.scmspain.application.services.TweetRepository; -import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * Configuration for the tweet application. + */ @Configuration public class TweetConfiguration { @Bean - public TweetService getTweetService(TweetRepository tweetRepository, MetricWriter metricWriter) { - return new TweetService(tweetRepository, metricWriter); + public TweetService getTweetService(TweetRepository tweetRepository, MetricService metricService) { + return new TweetService(tweetRepository, metricService); } @Bean diff --git a/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java b/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java new file mode 100644 index 0000000..7648395 --- /dev/null +++ b/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java @@ -0,0 +1,41 @@ +package com.scmspain.infrastructure.metrics; + +import org.springframework.boot.actuate.metrics.writer.Delta; +import org.springframework.boot.actuate.metrics.writer.MetricWriter; + +import com.scmspain.application.services.MetricService; + +/** + * Metric service implementation based on spring actuator metric writer. + */ +public class SpringActuatorMetricService implements MetricService { + + private static final String PUBLISHED_TWEETS = "published-tweets"; + private static final String TIMES_QUERIED_TWEETS = "times-queried-tweets"; + + private final MetricWriter metricWriter; + + /** + * Constructor. + * + * @param metricWriter Metric writer. + */ + public SpringActuatorMetricService(MetricWriter metricWriter) { + this.metricWriter = metricWriter; + } + + @Override + public void incrementPublishedTweets() { + metricWriter.increment(deltaOne(PUBLISHED_TWEETS)); + } + + @Override + public void incrementTimesQueriedTweets() { + metricWriter.increment(deltaOne(TIMES_QUERIED_TWEETS)); + } + + private Delta deltaOne(String name) { + return new Delta<>(name, 1); + } + +} diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index c6a6057..142303b 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,9 +1,12 @@ package com.scmspain.services; +import com.scmspain.application.services.MetricService; import com.scmspain.application.services.TweetService; import com.scmspain.application.services.TweetRepository; import com.scmspain.infrastructure.database.TweetEntityManagerRepository; import com.scmspain.infrastructure.database.entities.Tweet; +import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; + import org.junit.Before; import org.junit.Test; import org.mockito.InOrder; @@ -26,8 +29,11 @@ public class TweetServiceTest { public void setUp() { this.entityManager = mock(EntityManager.class); TweetRepository tweetRepository = new TweetEntityManagerRepository(entityManager); + this.metricWriter = mock(MetricWriter.class); - this.tweetService = new TweetService(tweetRepository, metricWriter); + MetricService metricService = new SpringActuatorMetricService(metricWriter); + + this.tweetService = new TweetService(tweetRepository, metricService); } @Test From 239b248e6f11bf67248d3b3a3630372c1770f4f6 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 14:29:28 +0200 Subject: [PATCH 15/43] Extracting interface (port) for tweet service. --- ...eetService.java => BasicTweetService.java} | 36 ++++++++++++------- .../application/services/TweetRepository.java | 4 +-- .../com/scmspain/domain/TweetService.java | 27 ++++++++++++++ .../configuration/TweetConfiguration.java | 5 +-- .../controller/TweetController.java | 6 ++-- .../TweetEntityManagerRepository.java | 4 +-- .../scmspain/services/TweetServiceTest.java | 9 ++--- 7 files changed, 65 insertions(+), 26 deletions(-) rename src/main/java/com/scmspain/application/services/{TweetService.java => BasicTweetService.java} (52%) create mode 100644 src/main/java/com/scmspain/domain/TweetService.java diff --git a/src/main/java/com/scmspain/application/services/TweetService.java b/src/main/java/com/scmspain/application/services/BasicTweetService.java similarity index 52% rename from src/main/java/com/scmspain/application/services/TweetService.java rename to src/main/java/com/scmspain/application/services/BasicTweetService.java index bb07779..c06b708 100644 --- a/src/main/java/com/scmspain/application/services/TweetService.java +++ b/src/main/java/com/scmspain/application/services/BasicTweetService.java @@ -1,33 +1,43 @@ package com.scmspain.application.services; -import com.scmspain.domain.model.TweetResponse; -import org.springframework.stereotype.Service; - import java.util.List; -@Service -public class TweetService { +import com.scmspain.domain.TweetService; +import com.scmspain.domain.model.TweetResponse; + +/** + * Basic tweet service implementation. + */ +public class BasicTweetService implements TweetService { private final TweetRepository tweetRepository; private final MetricService metricService; - public TweetService(TweetRepository tweetRepository, MetricService metricService) { + /** + * Constructor. + * + * @param tweetRepository Tweet repository. + * @param metricService Metric service. + */ + public BasicTweetService(final TweetRepository tweetRepository, final MetricService metricService) { this.tweetRepository = tweetRepository; this.metricService = metricService; } - public void publishTweet(String publisher, String text) { + @Override + public List listAll() { + this.metricService.incrementTimesQueriedTweets(); + return tweetRepository.findAll(); + } + + @Override + public void publish(final String publisher, final String text) { if (publisher != null && publisher.length() > 0 && text != null && text.length() > 0 && text.length() < 140) { this.metricService.incrementPublishedTweets(); - this.tweetRepository.publishTweet(publisher, text); + this.tweetRepository.save(publisher, text); } else { throw new IllegalArgumentException("Tweet must not be greater than 140 characters"); } } - public List listAllTweets() { - this.metricService.incrementTimesQueriedTweets(); - return this.tweetRepository.listAllTweets(); - } - } diff --git a/src/main/java/com/scmspain/application/services/TweetRepository.java b/src/main/java/com/scmspain/application/services/TweetRepository.java index 554ffd3..5ceaebb 100644 --- a/src/main/java/com/scmspain/application/services/TweetRepository.java +++ b/src/main/java/com/scmspain/application/services/TweetRepository.java @@ -15,13 +15,13 @@ public interface TweetRepository { * @param publisher Creator of the tweet. * @param text Content of the tweet. */ - void publishTweet(String publisher, String text); + void save(String publisher, String text); /** * Recover a list with all the tweets from repository. * * @return Retrieved list of tweets. */ - List listAllTweets(); + List findAll(); } diff --git a/src/main/java/com/scmspain/domain/TweetService.java b/src/main/java/com/scmspain/domain/TweetService.java new file mode 100644 index 0000000..38ceb91 --- /dev/null +++ b/src/main/java/com/scmspain/domain/TweetService.java @@ -0,0 +1,27 @@ +package com.scmspain.domain; + +import java.util.List; + +import com.scmspain.domain.model.TweetResponse; + +/** + * Tweet service. + */ +public interface TweetService { + + /** + * List all available tweets. + * + * @return List of tweets. + */ + List listAll(); + + /** + * Publish a new tweet. + * + * @param publisher Creator of the tweet. + * @param text Content of the tweet. + */ + void publish(String publisher, String text); + +} diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index b8a2dd6..981a82d 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -1,8 +1,9 @@ package com.scmspain.infrastructure.configuration; +import com.scmspain.application.services.BasicTweetService; import com.scmspain.application.services.MetricService; +import com.scmspain.domain.TweetService; import com.scmspain.infrastructure.controller.TweetController; -import com.scmspain.application.services.TweetService; import com.scmspain.application.services.TweetRepository; import org.springframework.context.annotation.Bean; @@ -16,7 +17,7 @@ public class TweetConfiguration { @Bean public TweetService getTweetService(TweetRepository tweetRepository, MetricService metricService) { - return new TweetService(tweetRepository, metricService); + return new BasicTweetService(tweetRepository, metricService); } @Bean diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index 3ff4c53..1594331 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -1,8 +1,8 @@ package com.scmspain.infrastructure.controller; +import com.scmspain.domain.TweetService; import com.scmspain.domain.command.PublishTweetCommand; import com.scmspain.domain.model.TweetResponse; -import com.scmspain.application.services.TweetService; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -21,13 +21,13 @@ public TweetController(TweetService tweetService) { @GetMapping("/tweet") public List listAllTweets() { - return this.tweetService.listAllTweets(); + return this.tweetService.listAll(); } @PostMapping("/tweet") @ResponseStatus(CREATED) public void publishTweet(@RequestBody PublishTweetCommand publishTweetCommand) { - this.tweetService.publishTweet(publishTweetCommand.getPublisher(), publishTweetCommand.getTweet()); + this.tweetService.publish(publishTweetCommand.getPublisher(), publishTweetCommand.getTweet()); } @ExceptionHandler(IllegalArgumentException.class) diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java b/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java index f1becdd..e5f13b8 100644 --- a/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java +++ b/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java @@ -38,7 +38,7 @@ public TweetEntityManagerRepository(EntityManager entityManager) { * @param text Content of the tweet. */ @Override - public void publishTweet(String publisher, String text) { + public void save(String publisher, String text) { Tweet tweet = new Tweet(publisher, text); this.entityManager.persist(tweet); } @@ -49,7 +49,7 @@ public void publishTweet(String publisher, String text) { * @return Retrieved list of tweets. */ @Override - public List listAllTweets() { + public List findAll() { TypedQuery query = this.entityManager .createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 142303b..2c671a5 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,8 +1,9 @@ package com.scmspain.services; +import com.scmspain.application.services.BasicTweetService; import com.scmspain.application.services.MetricService; -import com.scmspain.application.services.TweetService; import com.scmspain.application.services.TweetRepository; +import com.scmspain.domain.TweetService; import com.scmspain.infrastructure.database.TweetEntityManagerRepository; import com.scmspain.infrastructure.database.entities.Tweet; import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; @@ -33,14 +34,14 @@ public void setUp() { this.metricWriter = mock(MetricWriter.class); MetricService metricService = new SpringActuatorMetricService(metricWriter); - this.tweetService = new TweetService(tweetRepository, metricService); + this.tweetService = new BasicTweetService(tweetRepository, metricService); } @Test public void shouldInsertANewTweet() { String publisher = "Guybrush Threepwood"; String text = "I am Guybrush Threepwood, mighty pirate."; - tweetService.publishTweet(publisher, text); + tweetService.publish(publisher, text); InOrder inOrder = inOrder(metricWriter, entityManager); inOrder.verify(metricWriter).increment(any(Delta.class)); inOrder.verify(entityManager).persist(any(Tweet.class)); @@ -48,7 +49,7 @@ public void shouldInsertANewTweet() { @Test(expected = IllegalArgumentException.class) public void shouldThrowAnExceptionWhenTweetLengthIsInvalid() { - tweetService.publishTweet("Pirate", "LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to drop dead. So he did. Then things really got ugly."); + tweetService.publish("Pirate", "LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to drop dead. So he did. Then things really got ugly."); } } From 507821f9c3e89f74dbde781e8d380f6b9ca94fac Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 14:35:45 +0200 Subject: [PATCH 16/43] Refactor of publish tweet method, improve logic and add tests. --- .../services/BasicTweetService.java | 27 +++++++++++---- .../scmspain/services/TweetServiceTest.java | 33 ++++++++++++++++--- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/scmspain/application/services/BasicTweetService.java b/src/main/java/com/scmspain/application/services/BasicTweetService.java index c06b708..26d6602 100644 --- a/src/main/java/com/scmspain/application/services/BasicTweetService.java +++ b/src/main/java/com/scmspain/application/services/BasicTweetService.java @@ -2,6 +2,8 @@ import java.util.List; +import org.springframework.util.Assert; + import com.scmspain.domain.TweetService; import com.scmspain.domain.model.TweetResponse; @@ -10,6 +12,8 @@ */ public class BasicTweetService implements TweetService { + private static final int MAX_CHARACTERS = 140; + private final TweetRepository tweetRepository; private final MetricService metricService; @@ -32,12 +36,23 @@ public List listAll() { @Override public void publish(final String publisher, final String text) { - if (publisher != null && publisher.length() > 0 && text != null && text.length() > 0 && text.length() < 140) { - this.metricService.incrementPublishedTweets(); - this.tweetRepository.save(publisher, text); - } else { - throw new IllegalArgumentException("Tweet must not be greater than 140 characters"); - } + this.metricService.incrementPublishedTweets(); + Assert.isTrue(publisherIsNotEmpty(publisher), "Publisher must not be empty"); + Assert.isTrue(textIsNotEmpty(text), "Tweet must not be empty"); + Assert.isTrue(textNotTooLong(text), "Tweet must not be greater than " + MAX_CHARACTERS + " characters"); + this.tweetRepository.save(publisher, text); + } + + private boolean textNotTooLong(String text) { + return text.length() <= MAX_CHARACTERS; + } + + private boolean textIsNotEmpty(String text) { + return text != null && text.length() > 0; + } + + private boolean publisherIsNotEmpty(String publisher) { + return publisher != null && publisher.length() > 0; } } diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 2c671a5..25dc82e 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -22,6 +22,11 @@ public class TweetServiceTest { + private static final String GUYBRUSH = "Guybrush Threepwood"; + private static final String PIRATE = "Pirate"; + private static final String VALID_MESSAGE = "I am Guybrush Threepwood, mighty pirate."; + private static final String TOO_LONG_MESSAGE = "LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to drop dead. So he did. Then things really got ugly."; + private EntityManager entityManager; private MetricWriter metricWriter; private TweetService tweetService; @@ -39,17 +44,35 @@ public void setUp() { @Test public void shouldInsertANewTweet() { - String publisher = "Guybrush Threepwood"; - String text = "I am Guybrush Threepwood, mighty pirate."; - tweetService.publish(publisher, text); + tweetService.publish(GUYBRUSH, VALID_MESSAGE); InOrder inOrder = inOrder(metricWriter, entityManager); inOrder.verify(metricWriter).increment(any(Delta.class)); inOrder.verify(entityManager).persist(any(Tweet.class)); } @Test(expected = IllegalArgumentException.class) - public void shouldThrowAnExceptionWhenTweetLengthIsInvalid() { - tweetService.publish("Pirate", "LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to drop dead. So he did. Then things really got ugly."); + public void shouldThrowAnExceptionWhenTweetPublisherIsNull() { + this.tweetService.publish(null, VALID_MESSAGE); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowAnExceptionWhenTweetPublisherIsEmpty() { + this.tweetService.publish("", VALID_MESSAGE); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowAnExceptionWhenTweetTextIsNull() { + this.tweetService.publish(PIRATE, null); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowAnExceptionWhenTweetTextIsEmpty() { + this.tweetService.publish(PIRATE, ""); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowAnExceptionWhenTweetTextLengthIsInvalid() { + this.tweetService.publish(PIRATE, TOO_LONG_MESSAGE); } } From 0d65d0406c40cffa977281f9728888f66d4c4c5f Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 14:42:39 +0200 Subject: [PATCH 17/43] Refactor to use wrapper pattern at tweet service implementation. --- ...eetService.java => TweetBasicService.java} | 9 +---- .../services/TweetMetricService.java | 39 +++++++++++++++++++ .../configuration/TweetConfiguration.java | 6 ++- .../scmspain/services/TweetServiceTest.java | 7 +++- 4 files changed, 50 insertions(+), 11 deletions(-) rename src/main/java/com/scmspain/application/services/{BasicTweetService.java => TweetBasicService.java} (76%) create mode 100644 src/main/java/com/scmspain/application/services/TweetMetricService.java diff --git a/src/main/java/com/scmspain/application/services/BasicTweetService.java b/src/main/java/com/scmspain/application/services/TweetBasicService.java similarity index 76% rename from src/main/java/com/scmspain/application/services/BasicTweetService.java rename to src/main/java/com/scmspain/application/services/TweetBasicService.java index 26d6602..7e3d4c7 100644 --- a/src/main/java/com/scmspain/application/services/BasicTweetService.java +++ b/src/main/java/com/scmspain/application/services/TweetBasicService.java @@ -10,33 +10,28 @@ /** * Basic tweet service implementation. */ -public class BasicTweetService implements TweetService { +public class TweetBasicService implements TweetService { private static final int MAX_CHARACTERS = 140; private final TweetRepository tweetRepository; - private final MetricService metricService; /** * Constructor. * * @param tweetRepository Tweet repository. - * @param metricService Metric service. */ - public BasicTweetService(final TweetRepository tweetRepository, final MetricService metricService) { + public TweetBasicService(final TweetRepository tweetRepository) { this.tweetRepository = tweetRepository; - this.metricService = metricService; } @Override public List listAll() { - this.metricService.incrementTimesQueriedTweets(); return tweetRepository.findAll(); } @Override public void publish(final String publisher, final String text) { - this.metricService.incrementPublishedTweets(); Assert.isTrue(publisherIsNotEmpty(publisher), "Publisher must not be empty"); Assert.isTrue(textIsNotEmpty(text), "Tweet must not be empty"); Assert.isTrue(textNotTooLong(text), "Tweet must not be greater than " + MAX_CHARACTERS + " characters"); diff --git a/src/main/java/com/scmspain/application/services/TweetMetricService.java b/src/main/java/com/scmspain/application/services/TweetMetricService.java new file mode 100644 index 0000000..d78c0c3 --- /dev/null +++ b/src/main/java/com/scmspain/application/services/TweetMetricService.java @@ -0,0 +1,39 @@ +package com.scmspain.application.services; + +import java.util.List; + +import com.scmspain.domain.TweetService; +import com.scmspain.domain.model.TweetResponse; + +/** + * Tweet service implementation with basic functionality and metrics. + */ +public class TweetMetricService implements TweetService { + + private final MetricService metricService; + private final TweetService tweetService; + + /** + * Constructor. + * + * @param tweetService Tweet service. + * @param metricService Metrics service. + */ + public TweetMetricService(final TweetService tweetService, final MetricService metricService) { + this.tweetService = tweetService; + this.metricService = metricService; + } + + @Override + public List listAll() { + metricService.incrementTimesQueriedTweets(); + return tweetService.listAll(); + } + + @Override + public void publish(String publisher, String text) { + metricService.incrementPublishedTweets(); + tweetService.publish(publisher, text); + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 981a82d..947561a 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -1,7 +1,8 @@ package com.scmspain.infrastructure.configuration; -import com.scmspain.application.services.BasicTweetService; +import com.scmspain.application.services.TweetBasicService; import com.scmspain.application.services.MetricService; +import com.scmspain.application.services.TweetMetricService; import com.scmspain.domain.TweetService; import com.scmspain.infrastructure.controller.TweetController; import com.scmspain.application.services.TweetRepository; @@ -17,7 +18,8 @@ public class TweetConfiguration { @Bean public TweetService getTweetService(TweetRepository tweetRepository, MetricService metricService) { - return new BasicTweetService(tweetRepository, metricService); + TweetService tweetBasicService = new TweetBasicService(tweetRepository); + return new TweetMetricService(tweetBasicService, metricService); } @Bean diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 25dc82e..cf8c854 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,7 +1,8 @@ package com.scmspain.services; -import com.scmspain.application.services.BasicTweetService; +import com.scmspain.application.services.TweetBasicService; import com.scmspain.application.services.MetricService; +import com.scmspain.application.services.TweetMetricService; import com.scmspain.application.services.TweetRepository; import com.scmspain.domain.TweetService; import com.scmspain.infrastructure.database.TweetEntityManagerRepository; @@ -39,7 +40,9 @@ public void setUp() { this.metricWriter = mock(MetricWriter.class); MetricService metricService = new SpringActuatorMetricService(metricWriter); - this.tweetService = new BasicTweetService(tweetRepository, metricService); + TweetService tweetBasicService = new TweetBasicService(tweetRepository); + + this.tweetService = new TweetMetricService(tweetBasicService, metricService); } @Test From 9e0664f54ea612c45df889a4b0173d5dd94bd3e4 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 15:02:04 +0200 Subject: [PATCH 18/43] Refactor to replace tweet repository with another wrapped tweet service. --- .../services/TweetMetricService.java | 4 +-- .../application/services/TweetRepository.java | 27 ------------------- ...rvice.java => TweetValidationService.java} | 16 +++++------ .../InfrastructureConfiguration.java | 10 +++---- .../configuration/TweetConfiguration.java | 14 +++++----- ...erRepository.java => TweetRepository.java} | 21 ++++----------- .../scmspain/services/TweetServiceTest.java | 11 ++++---- 7 files changed, 32 insertions(+), 71 deletions(-) delete mode 100644 src/main/java/com/scmspain/application/services/TweetRepository.java rename src/main/java/com/scmspain/application/services/{TweetBasicService.java => TweetValidationService.java} (70%) rename src/main/java/com/scmspain/infrastructure/database/{TweetEntityManagerRepository.java => TweetRepository.java} (73%) diff --git a/src/main/java/com/scmspain/application/services/TweetMetricService.java b/src/main/java/com/scmspain/application/services/TweetMetricService.java index d78c0c3..496f753 100644 --- a/src/main/java/com/scmspain/application/services/TweetMetricService.java +++ b/src/main/java/com/scmspain/application/services/TweetMetricService.java @@ -6,7 +6,7 @@ import com.scmspain.domain.model.TweetResponse; /** - * Tweet service implementation with basic functionality and metrics. + * Tweet service implementation that adds metric to another wrapped tweet service. */ public class TweetMetricService implements TweetService { @@ -16,7 +16,7 @@ public class TweetMetricService implements TweetService { /** * Constructor. * - * @param tweetService Tweet service. + * @param tweetService Tweet service to wrap. * @param metricService Metrics service. */ public TweetMetricService(final TweetService tweetService, final MetricService metricService) { diff --git a/src/main/java/com/scmspain/application/services/TweetRepository.java b/src/main/java/com/scmspain/application/services/TweetRepository.java deleted file mode 100644 index 5ceaebb..0000000 --- a/src/main/java/com/scmspain/application/services/TweetRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.scmspain.application.services; - -import java.util.List; - -import com.scmspain.domain.model.TweetResponse; - -/** - * Repository for tweets. - */ -public interface TweetRepository { - - /** - * Push tweet to repository. - * - * @param publisher Creator of the tweet. - * @param text Content of the tweet. - */ - void save(String publisher, String text); - - /** - * Recover a list with all the tweets from repository. - * - * @return Retrieved list of tweets. - */ - List findAll(); - -} diff --git a/src/main/java/com/scmspain/application/services/TweetBasicService.java b/src/main/java/com/scmspain/application/services/TweetValidationService.java similarity index 70% rename from src/main/java/com/scmspain/application/services/TweetBasicService.java rename to src/main/java/com/scmspain/application/services/TweetValidationService.java index 7e3d4c7..89ffafe 100644 --- a/src/main/java/com/scmspain/application/services/TweetBasicService.java +++ b/src/main/java/com/scmspain/application/services/TweetValidationService.java @@ -8,26 +8,26 @@ import com.scmspain.domain.model.TweetResponse; /** - * Basic tweet service implementation. + * Tweet service implementation that adds validation to another wrapped tweet service. */ -public class TweetBasicService implements TweetService { +public class TweetValidationService implements TweetService { private static final int MAX_CHARACTERS = 140; - private final TweetRepository tweetRepository; + private final TweetService tweetService; /** * Constructor. * - * @param tweetRepository Tweet repository. + * @param tweetService Tweet service to wrap. */ - public TweetBasicService(final TweetRepository tweetRepository) { - this.tweetRepository = tweetRepository; + public TweetValidationService(final TweetService tweetService) { + this.tweetService = tweetService; } @Override public List listAll() { - return tweetRepository.findAll(); + return tweetService.listAll(); } @Override @@ -35,7 +35,7 @@ public void publish(final String publisher, final String text) { Assert.isTrue(publisherIsNotEmpty(publisher), "Publisher must not be empty"); Assert.isTrue(textIsNotEmpty(text), "Tweet must not be empty"); Assert.isTrue(textNotTooLong(text), "Tweet must not be greater than " + MAX_CHARACTERS + " characters"); - this.tweetRepository.save(publisher, text); + this.tweetService.publish(publisher, text); } private boolean textNotTooLong(String text) { diff --git a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java index 85e0aa7..1e87389 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java @@ -10,8 +10,8 @@ import org.springframework.jmx.export.MBeanExporter; import com.scmspain.application.services.MetricService; -import com.scmspain.application.services.TweetRepository; -import com.scmspain.infrastructure.database.TweetEntityManagerRepository; +import com.scmspain.domain.TweetService; +import com.scmspain.infrastructure.database.TweetRepository; import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; /** @@ -31,9 +31,9 @@ public MetricService getMetricService(final MetricWriter metricWriter) { return new SpringActuatorMetricService(metricWriter); } - @Bean - public TweetRepository getTweetRepository(final EntityManager entityManager) { - return new TweetEntityManagerRepository(entityManager); + @Bean("tweetRepository") + public TweetService getTweetRepository(final EntityManager entityManager) { + return new TweetRepository(entityManager); } } diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 947561a..4476a8f 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -1,12 +1,12 @@ package com.scmspain.infrastructure.configuration; -import com.scmspain.application.services.TweetBasicService; import com.scmspain.application.services.MetricService; import com.scmspain.application.services.TweetMetricService; +import com.scmspain.application.services.TweetValidationService; import com.scmspain.domain.TweetService; import com.scmspain.infrastructure.controller.TweetController; -import com.scmspain.application.services.TweetRepository; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -16,14 +16,14 @@ @Configuration public class TweetConfiguration { - @Bean - public TweetService getTweetService(TweetRepository tweetRepository, MetricService metricService) { - TweetService tweetBasicService = new TweetBasicService(tweetRepository); - return new TweetMetricService(tweetBasicService, metricService); + @Bean("mainTweetService") + public TweetService getTweetService(@Qualifier("tweetRepository") TweetService tweetService, MetricService metricService) { + TweetService tweetValidationService = new TweetValidationService(tweetService); + return new TweetMetricService(tweetValidationService, metricService); } @Bean - public TweetController getTweetConfiguration(TweetService tweetService) { + public TweetController getTweetConfiguration(@Qualifier("mainTweetService") TweetService tweetService) { return new TweetController(tweetService); } diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java similarity index 73% rename from src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java rename to src/main/java/com/scmspain/infrastructure/database/TweetRepository.java index e5f13b8..5fa4fd1 100644 --- a/src/main/java/com/scmspain/infrastructure/database/TweetEntityManagerRepository.java +++ b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Repository; -import com.scmspain.application.services.TweetRepository; +import com.scmspain.domain.TweetService; import com.scmspain.domain.model.TweetResponse; import com.scmspain.infrastructure.database.entities.Tweet; @@ -18,7 +18,7 @@ */ @Repository @Transactional -public class TweetEntityManagerRepository implements TweetRepository { +public class TweetRepository implements TweetService { private final EntityManager entityManager; @@ -27,29 +27,18 @@ public class TweetEntityManagerRepository implements TweetRepository { * * @param entityManager Entity manager to access the persistence context. */ - public TweetEntityManagerRepository(EntityManager entityManager) { + public TweetRepository(EntityManager entityManager) { this.entityManager = entityManager; } - /** - * Push tweet to repository. - * - * @param publisher Creator of the tweet. - * @param text Content of the tweet. - */ @Override - public void save(String publisher, String text) { + public void publish(String publisher, String text) { Tweet tweet = new Tweet(publisher, text); this.entityManager.persist(tweet); } - /** - * Recover a list with all the tweets from repository. - * - * @return Retrieved list of tweets. - */ @Override - public List findAll() { + public List listAll() { TypedQuery query = this.entityManager .createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index cf8c854..010eab4 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,11 +1,10 @@ package com.scmspain.services; -import com.scmspain.application.services.TweetBasicService; import com.scmspain.application.services.MetricService; import com.scmspain.application.services.TweetMetricService; -import com.scmspain.application.services.TweetRepository; +import com.scmspain.application.services.TweetValidationService; import com.scmspain.domain.TweetService; -import com.scmspain.infrastructure.database.TweetEntityManagerRepository; +import com.scmspain.infrastructure.database.TweetRepository; import com.scmspain.infrastructure.database.entities.Tweet; import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; @@ -35,14 +34,14 @@ public class TweetServiceTest { @Before public void setUp() { this.entityManager = mock(EntityManager.class); - TweetRepository tweetRepository = new TweetEntityManagerRepository(entityManager); + TweetService tweetRepository = new TweetRepository(entityManager); this.metricWriter = mock(MetricWriter.class); MetricService metricService = new SpringActuatorMetricService(metricWriter); - TweetService tweetBasicService = new TweetBasicService(tweetRepository); + TweetService tweetValidationService = new TweetValidationService(tweetRepository); - this.tweetService = new TweetMetricService(tweetBasicService, metricService); + this.tweetService = new TweetMetricService(tweetValidationService, metricService); } @Test From 7fb7c9b3e49fc0415ddb52896ac03bff4871d08d Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 15:56:48 +0200 Subject: [PATCH 19/43] Removing Spring dependence from application layer. --- .../services/TweetValidationService.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/scmspain/application/services/TweetValidationService.java b/src/main/java/com/scmspain/application/services/TweetValidationService.java index 89ffafe..372e357 100644 --- a/src/main/java/com/scmspain/application/services/TweetValidationService.java +++ b/src/main/java/com/scmspain/application/services/TweetValidationService.java @@ -2,8 +2,6 @@ import java.util.List; -import org.springframework.util.Assert; - import com.scmspain.domain.TweetService; import com.scmspain.domain.model.TweetResponse; @@ -32,22 +30,28 @@ public List listAll() { @Override public void publish(final String publisher, final String text) { - Assert.isTrue(publisherIsNotEmpty(publisher), "Publisher must not be empty"); - Assert.isTrue(textIsNotEmpty(text), "Tweet must not be empty"); - Assert.isTrue(textNotTooLong(text), "Tweet must not be greater than " + MAX_CHARACTERS + " characters"); + if (publisherEmpty(publisher)) { + throw new IllegalArgumentException("Publisher must not be empty"); + } + if (textEmpty(text)) { + throw new IllegalArgumentException("Tweet must not be empty"); + } + if (textTooLong(text)) { + throw new IllegalArgumentException("Tweet must not be greater than " + MAX_CHARACTERS + " characters"); + } this.tweetService.publish(publisher, text); } - private boolean textNotTooLong(String text) { - return text.length() <= MAX_CHARACTERS; + private boolean textTooLong(String text) { + return text.length() > MAX_CHARACTERS; } - private boolean textIsNotEmpty(String text) { - return text != null && text.length() > 0; + private boolean textEmpty(String text) { + return text == null || text.isEmpty(); } - private boolean publisherIsNotEmpty(String publisher) { - return publisher != null && publisher.length() > 0; + private boolean publisherEmpty(String publisher) { + return publisher == null || publisher.isEmpty(); } } From 530ef5b113b1bec151a8792ac2b3b7fe6f593d54 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 16:02:15 +0200 Subject: [PATCH 20/43] Moving services interfaces (ports) to domain layer. --- .../com/scmspain/application/services/TweetMetricService.java | 1 + .../{application/services => domain}/MetricService.java | 2 +- .../configuration/InfrastructureConfiguration.java | 2 +- .../infrastructure/configuration/TweetConfiguration.java | 2 +- .../infrastructure/metrics/SpringActuatorMetricService.java | 2 +- src/test/java/com/scmspain/services/TweetServiceTest.java | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) rename src/main/java/com/scmspain/{application/services => domain}/MetricService.java (86%) diff --git a/src/main/java/com/scmspain/application/services/TweetMetricService.java b/src/main/java/com/scmspain/application/services/TweetMetricService.java index 496f753..c74ed1f 100644 --- a/src/main/java/com/scmspain/application/services/TweetMetricService.java +++ b/src/main/java/com/scmspain/application/services/TweetMetricService.java @@ -2,6 +2,7 @@ import java.util.List; +import com.scmspain.domain.MetricService; import com.scmspain.domain.TweetService; import com.scmspain.domain.model.TweetResponse; diff --git a/src/main/java/com/scmspain/application/services/MetricService.java b/src/main/java/com/scmspain/domain/MetricService.java similarity index 86% rename from src/main/java/com/scmspain/application/services/MetricService.java rename to src/main/java/com/scmspain/domain/MetricService.java index 2e84dce..62743d5 100644 --- a/src/main/java/com/scmspain/application/services/MetricService.java +++ b/src/main/java/com/scmspain/domain/MetricService.java @@ -1,4 +1,4 @@ -package com.scmspain.application.services; +package com.scmspain.domain; /** * Metric service. diff --git a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java index 1e87389..5ab9c6b 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java @@ -9,7 +9,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.jmx.export.MBeanExporter; -import com.scmspain.application.services.MetricService; +import com.scmspain.domain.MetricService; import com.scmspain.domain.TweetService; import com.scmspain.infrastructure.database.TweetRepository; import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 4476a8f..b453ca5 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -1,6 +1,6 @@ package com.scmspain.infrastructure.configuration; -import com.scmspain.application.services.MetricService; +import com.scmspain.domain.MetricService; import com.scmspain.application.services.TweetMetricService; import com.scmspain.application.services.TweetValidationService; import com.scmspain.domain.TweetService; diff --git a/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java b/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java index 7648395..9ed50ce 100644 --- a/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java +++ b/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java @@ -3,7 +3,7 @@ import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; -import com.scmspain.application.services.MetricService; +import com.scmspain.domain.MetricService; /** * Metric service implementation based on spring actuator metric writer. diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 010eab4..6e846a7 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,6 +1,6 @@ package com.scmspain.services; -import com.scmspain.application.services.MetricService; +import com.scmspain.domain.MetricService; import com.scmspain.application.services.TweetMetricService; import com.scmspain.application.services.TweetValidationService; import com.scmspain.domain.TweetService; From cda1382daac71d59985d829381172f237e7160a7 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 17:06:58 +0200 Subject: [PATCH 21/43] Add basic spring command bus. --- .../com/scmspain/domain/command/Command.java | 8 +++ .../scmspain/domain/command/CommandBus.java | 18 ++++++ .../domain/command/CommandHandler.java | 20 +++++++ .../commandbus/CommandProvider.java | 37 +++++++++++++ .../infrastructure/commandbus/Registry.java | 55 +++++++++++++++++++ .../commandbus/SpringCommandBus.java | 33 +++++++++++ .../commandbus/CommandBusITCase.java | 47 ++++++++++++++++ .../CommandBusITCaseConfiguration.java | 25 +++++++++ .../commandbus/RegistryTest.java | 43 +++++++++++++++ .../commandbus/SpringCommandBusTest.java | 37 +++++++++++++ .../commandbus/command/ByeCommand.java | 17 ++++++ .../commandbus/command/HelloCommand.java | 17 ++++++ .../commandbus/handler/ByeCommandHandler.java | 23 ++++++++ .../handler/HelloCommandHandler.java | 26 +++++++++ .../commandbus/handler/MessageCollector.java | 18 ++++++ 15 files changed, 424 insertions(+) create mode 100644 src/main/java/com/scmspain/domain/command/Command.java create mode 100644 src/main/java/com/scmspain/domain/command/CommandBus.java create mode 100644 src/main/java/com/scmspain/domain/command/CommandHandler.java create mode 100644 src/main/java/com/scmspain/infrastructure/commandbus/CommandProvider.java create mode 100644 src/main/java/com/scmspain/infrastructure/commandbus/Registry.java create mode 100644 src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java create mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java create mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCaseConfiguration.java create mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/RegistryTest.java create mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/SpringCommandBusTest.java create mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/command/ByeCommand.java create mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/command/HelloCommand.java create mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/handler/ByeCommandHandler.java create mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/handler/HelloCommandHandler.java create mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/handler/MessageCollector.java diff --git a/src/main/java/com/scmspain/domain/command/Command.java b/src/main/java/com/scmspain/domain/command/Command.java new file mode 100644 index 0000000..7effc54 --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/Command.java @@ -0,0 +1,8 @@ +package com.scmspain.domain.command; + +/** + * Command interface to be implemented by domain commands. + * + * @param type of the return value. + */ +public interface Command { } diff --git a/src/main/java/com/scmspain/domain/command/CommandBus.java b/src/main/java/com/scmspain/domain/command/CommandBus.java new file mode 100644 index 0000000..63c561a --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/CommandBus.java @@ -0,0 +1,18 @@ +package com.scmspain.domain.command; + +/** + * Command bus able to execute commands. + * Given a command will pass it to the proper handler. + */ +public interface CommandBus { + + /** + * Look for the proper handler of the given command. + * + * @param command Command to execute. + * @param type of return value. + * @param type of the command. + */ + > R execute(C command); + +} diff --git a/src/main/java/com/scmspain/domain/command/CommandHandler.java b/src/main/java/com/scmspain/domain/command/CommandHandler.java new file mode 100644 index 0000000..3136ac1 --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/CommandHandler.java @@ -0,0 +1,20 @@ +package com.scmspain.domain.command; + +/** + * A handler for a {@link Command}. + * + * @param type of return value. + * @param type of the command. + */ +public interface CommandHandler> { + + /** + * Handles the command. + * + * @param command Command to handle. + * @return Return value as specified in {@link Command}. + * {@link Void} if none value returned. + */ + R handle(C command); + +} diff --git a/src/main/java/com/scmspain/infrastructure/commandbus/CommandProvider.java b/src/main/java/com/scmspain/infrastructure/commandbus/CommandProvider.java new file mode 100644 index 0000000..1a0e3cd --- /dev/null +++ b/src/main/java/com/scmspain/infrastructure/commandbus/CommandProvider.java @@ -0,0 +1,37 @@ +package com.scmspain.infrastructure.commandbus; + +import org.springframework.context.ApplicationContext; + +import com.scmspain.domain.command.CommandHandler; + +/** + * Basic implementation of a command handler provider. + * Returns a registered bean handler from the Spring framework application context. + * + * @param type of handler + */ +class CommandProvider> { + + private final ApplicationContext applicationContext; + private final Class type; + + /** + * Constructor. + * + * @param applicationContext Spring framework application context. + * @param type Type of handler. + */ + CommandProvider(ApplicationContext applicationContext, Class type) { + this.applicationContext = applicationContext; + this.type = type; + } + + /** + * Gets a bean of the proper type from the Spring framework application context. + * @return Spring bean. + */ + public H get() { + return applicationContext.getBean(type); + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/commandbus/Registry.java b/src/main/java/com/scmspain/infrastructure/commandbus/Registry.java new file mode 100644 index 0000000..d5fa6c3 --- /dev/null +++ b/src/main/java/com/scmspain/infrastructure/commandbus/Registry.java @@ -0,0 +1,55 @@ +package com.scmspain.infrastructure.commandbus; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.core.GenericTypeResolver; + +import com.scmspain.domain.command.Command; +import com.scmspain.domain.command.CommandHandler; + +/** + * Basic implementation of a registry with the mapping between commands and handlers based on the Spring framework + * application context. + */ +public class Registry { + + private final Map, CommandProvider> providerMap = new HashMap<>(); + + /** + * Constructor. + * + * @param applicationContext Spring framework application context. + */ + @Autowired + public Registry(ApplicationContext applicationContext) { + String[] names = applicationContext.getBeanNamesForType(CommandHandler.class); + for (String name : names) { + register(applicationContext, name); + } + } + + @SuppressWarnings("unchecked") + private void register(ApplicationContext applicationContext, String name){ + Class> handlerClass = (Class>) applicationContext.getType(name); + Class[] generics = GenericTypeResolver.resolveTypeArguments(handlerClass, CommandHandler.class); + Class commandType = (Class) generics[1]; + providerMap.put(commandType, new CommandProvider(applicationContext, handlerClass)); + } + + /** + * Gets the proper handler for the given command class. + * + * @param commandClass Command class, should implement {@link Command}. + * @param type of return value. + * @param type of the command. + * @return Handler for the command. + */ + @SuppressWarnings("unchecked") + > CommandHandler get(Class commandClass) { + return providerMap.get(commandClass).get(); + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java b/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java new file mode 100644 index 0000000..c98af88 --- /dev/null +++ b/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java @@ -0,0 +1,33 @@ +package com.scmspain.infrastructure.commandbus; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.scmspain.domain.command.Command; +import com.scmspain.domain.command.CommandBus; +import com.scmspain.domain.command.CommandHandler; + +/** + * Basic implementation of a command bus based on Spring framework application context. + */ +public class SpringCommandBus implements CommandBus { + + private final Registry registry; + + /** + * Creates a new instance with the given registry. + * + * @param registry registry + */ + @Autowired + public SpringCommandBus(Registry registry) { + this.registry = registry; + } + + @Override + @SuppressWarnings("unchecked") + public > R execute(C command) { + CommandHandler commandHandler = (CommandHandler) registry.get(command.getClass()); + return commandHandler.handle(command); + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java b/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java new file mode 100644 index 0000000..9c21dbb --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java @@ -0,0 +1,47 @@ +package com.scmspain.infrastructure.commandbus; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.scmspain.domain.command.CommandBus; +import com.scmspain.infrastructure.commandbus.command.ByeCommand; +import com.scmspain.infrastructure.commandbus.command.HelloCommand; +import com.scmspain.infrastructure.commandbus.handler.ByeCommandHandler; +import com.scmspain.infrastructure.commandbus.handler.HelloCommandHandler; +import com.scmspain.infrastructure.commandbus.handler.MessageCollector; + +import static org.assertj.core.api.Assertions.assertThat; + +@ContextConfiguration(classes = { + HelloCommandHandler.class, + ByeCommandHandler.class, + MessageCollector.class, + Registry.class +}) +@Import(CommandBusITCaseConfiguration.class) +@RunWith(SpringRunner.class) +public class CommandBusITCase { + + @Autowired + private CommandBus commandBus; + + @Autowired + private MessageCollector messageCollector; + + @Test + public void executeHandlersForGivenCommands() { + String actualStringReturnValue = commandBus.execute(new HelloCommand("Schibsted")); + Void actualVoidReturnValue = commandBus.execute(new ByeCommand("AEM")); + + Assertions.assertThat(messageCollector.getMessages()).contains("Hello Schibsted", "Bye AEM"); + + assertThat(actualStringReturnValue).isEqualTo("Hello Schibsted"); + assertThat(actualVoidReturnValue).isNull(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCaseConfiguration.java b/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCaseConfiguration.java new file mode 100644 index 0000000..96b0dbc --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCaseConfiguration.java @@ -0,0 +1,25 @@ +package com.scmspain.infrastructure.commandbus; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jmx.export.MBeanExporter; +import org.springframework.test.context.ContextConfiguration; + +import com.scmspain.MsFcTechTestApplication; +import com.scmspain.domain.command.CommandBus; +import com.scmspain.infrastructure.commandbus.handler.ByeCommandHandler; +import com.scmspain.infrastructure.commandbus.handler.HelloCommandHandler; +import com.scmspain.infrastructure.commandbus.handler.MessageCollector; + +import static org.mockito.Mockito.mock; + +@Configuration +public class CommandBusITCaseConfiguration { + + @Bean + public CommandBus getCommandBus(Registry registry) { + return new SpringCommandBus(registry); + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/RegistryTest.java b/src/test/java/com/scmspain/infrastructure/commandbus/RegistryTest.java new file mode 100644 index 0000000..427146f --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/commandbus/RegistryTest.java @@ -0,0 +1,43 @@ +package com.scmspain.infrastructure.commandbus; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.context.ApplicationContext; + +import com.scmspain.domain.command.CommandHandler; +import com.scmspain.infrastructure.commandbus.command.HelloCommand; +import com.scmspain.infrastructure.commandbus.handler.HelloCommandHandler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class RegistryTest { + + private static final String HELLO_COMMAND_HANDLER = "helloCommandHandler"; + + @Mock + private ApplicationContext applicationContext; + + @Mock + private HelloCommandHandler helloCommandHandler; + + @Test + public void shouldReturnRegisteredHandlerForCommands() { + String[] commandHandlers = new String[] { HELLO_COMMAND_HANDLER }; + when(applicationContext.getBeanNamesForType(CommandHandler.class)).thenReturn(commandHandlers); + + Class type = HelloCommandHandler.class; + when(applicationContext.getType(HELLO_COMMAND_HANDLER)).thenReturn(type); + + when(applicationContext.getBean(HelloCommandHandler.class)).thenReturn(helloCommandHandler); + + Registry registry = new Registry(applicationContext); + CommandHandler handler = registry.get(HelloCommand.class); + + assertThat(handler).isInstanceOf(HelloCommandHandler.class); + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/SpringCommandBusTest.java b/src/test/java/com/scmspain/infrastructure/commandbus/SpringCommandBusTest.java new file mode 100644 index 0000000..8f4acf4 --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/commandbus/SpringCommandBusTest.java @@ -0,0 +1,37 @@ +package com.scmspain.infrastructure.commandbus; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import com.scmspain.domain.command.CommandHandler; +import com.scmspain.infrastructure.commandbus.command.HelloCommand; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class SpringCommandBusTest { + + @Mock + private Registry registry; + + @Mock + private CommandHandler handler; + + @InjectMocks + private SpringCommandBus commandBus; + + @Test + public void shouldExecuteHandlerForCommand() { + when(registry.get(HelloCommand.class)).thenReturn(handler); + + HelloCommand command = new HelloCommand("Schibsted"); + commandBus.execute(command); + + verify(handler).handle(command); + } + +} \ No newline at end of file diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/command/ByeCommand.java b/src/test/java/com/scmspain/infrastructure/commandbus/command/ByeCommand.java new file mode 100644 index 0000000..323acce --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/commandbus/command/ByeCommand.java @@ -0,0 +1,17 @@ +package com.scmspain.infrastructure.commandbus.command; + +import com.scmspain.domain.command.Command; + +public class ByeCommand implements Command { + + private final String name; + + public ByeCommand(String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/command/HelloCommand.java b/src/test/java/com/scmspain/infrastructure/commandbus/command/HelloCommand.java new file mode 100644 index 0000000..3cdbd74 --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/commandbus/command/HelloCommand.java @@ -0,0 +1,17 @@ +package com.scmspain.infrastructure.commandbus.command; + +import com.scmspain.domain.command.Command; + +public class HelloCommand implements Command { + + private final String name; + + public HelloCommand(String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/handler/ByeCommandHandler.java b/src/test/java/com/scmspain/infrastructure/commandbus/handler/ByeCommandHandler.java new file mode 100644 index 0000000..806d40d --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/commandbus/handler/ByeCommandHandler.java @@ -0,0 +1,23 @@ +package com.scmspain.infrastructure.commandbus.handler; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.scmspain.domain.command.CommandHandler; +import com.scmspain.infrastructure.commandbus.command.ByeCommand; + +public class ByeCommandHandler implements CommandHandler { + + private MessageCollector messageCollector; + + @Autowired + public ByeCommandHandler(MessageCollector messageCollector) { + this.messageCollector = messageCollector; + } + + @Override + public Void handle(ByeCommand command) { + messageCollector.add("Bye " + command.getName()); + return null; + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/handler/HelloCommandHandler.java b/src/test/java/com/scmspain/infrastructure/commandbus/handler/HelloCommandHandler.java new file mode 100644 index 0000000..c5a873e --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/commandbus/handler/HelloCommandHandler.java @@ -0,0 +1,26 @@ +package com.scmspain.infrastructure.commandbus.handler; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.scmspain.domain.command.CommandHandler; +import com.scmspain.infrastructure.commandbus.command.HelloCommand; + +public class HelloCommandHandler implements CommandHandler { + + private MessageCollector messageCollector; + + @Autowired + public HelloCommandHandler(MessageCollector messageCollector) { + this.messageCollector = messageCollector; + } + + @Override + public String handle(HelloCommand command) { + String message = "Hello " + command.getName(); + if (messageCollector != null) { + messageCollector.add(message); + } + return message; + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/handler/MessageCollector.java b/src/test/java/com/scmspain/infrastructure/commandbus/handler/MessageCollector.java new file mode 100644 index 0000000..d3693f4 --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/commandbus/handler/MessageCollector.java @@ -0,0 +1,18 @@ +package com.scmspain.infrastructure.commandbus.handler; + +import java.util.ArrayList; +import java.util.List; + +public class MessageCollector { + + private List messages = new ArrayList<>(); + + void add(String message) { + messages.add(message); + } + + public List getMessages() { + return messages; + } + +} From 8e4db0ec42fedcb4f198c569c05eff2a0bc930ab Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 17:23:19 +0200 Subject: [PATCH 22/43] Add publish tweet command handler. --- .../com/scmspain/MsFcTechTestApplication.java | 3 +- .../domain/command/PublishTweetCommand.java | 2 +- .../command/PublishTweetCommandHandler.java | 27 +++++++++++++++++ .../CommandBusConfiguration.java | 30 +++++++++++++++++++ .../configuration/TweetConfiguration.java | 5 ++-- .../controller/TweetController.java | 15 ++++++++-- 6 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/scmspain/domain/command/PublishTweetCommandHandler.java create mode 100644 src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java diff --git a/src/main/java/com/scmspain/MsFcTechTestApplication.java b/src/main/java/com/scmspain/MsFcTechTestApplication.java index e40fb18..fb93138 100644 --- a/src/main/java/com/scmspain/MsFcTechTestApplication.java +++ b/src/main/java/com/scmspain/MsFcTechTestApplication.java @@ -1,5 +1,6 @@ package com.scmspain; +import com.scmspain.infrastructure.configuration.CommandBusConfiguration; import com.scmspain.infrastructure.configuration.InfrastructureConfiguration; import com.scmspain.infrastructure.configuration.TweetConfiguration; import org.springframework.boot.SpringApplication; @@ -9,7 +10,7 @@ @Configuration @EnableAutoConfiguration -@Import({TweetConfiguration.class, InfrastructureConfiguration.class}) +@Import({TweetConfiguration.class, InfrastructureConfiguration.class, CommandBusConfiguration.class}) public class MsFcTechTestApplication { public static void main(String[] args) { diff --git a/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java b/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java index c43a082..1b297ee 100644 --- a/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java +++ b/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java @@ -3,7 +3,7 @@ /** * Command for publish a tweet. */ -public class PublishTweetCommand { +public class PublishTweetCommand implements Command { private String publisher; private String tweet; diff --git a/src/main/java/com/scmspain/domain/command/PublishTweetCommandHandler.java b/src/main/java/com/scmspain/domain/command/PublishTweetCommandHandler.java new file mode 100644 index 0000000..7af234e --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/PublishTweetCommandHandler.java @@ -0,0 +1,27 @@ +package com.scmspain.domain.command; + +import com.scmspain.domain.TweetService; + +/** + * Handler for the publish tweet command. + */ +public class PublishTweetCommandHandler implements CommandHandler { + + private final TweetService tweetService; + + /** + * Constructor. + * + * @param tweetService Tweet service. + */ + public PublishTweetCommandHandler(final TweetService tweetService) { + this.tweetService = tweetService; + } + + @Override + public Void handle(PublishTweetCommand command) { + tweetService.publish(command.getPublisher(), command.getTweet()); + return null; + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java new file mode 100644 index 0000000..0f75b77 --- /dev/null +++ b/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java @@ -0,0 +1,30 @@ +package com.scmspain.infrastructure.configuration; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.scmspain.domain.TweetService; +import com.scmspain.domain.command.CommandBus; +import com.scmspain.domain.command.PublishTweetCommandHandler; +import com.scmspain.infrastructure.commandbus.Registry; +import com.scmspain.infrastructure.commandbus.SpringCommandBus; + +/** + * Command bus configuration. + */ +@Configuration +public class CommandBusConfiguration { + + @Bean + public CommandBus getCommandBus(final ApplicationContext applicationContext) { + return new SpringCommandBus(new Registry(applicationContext)); + } + + @Bean + public PublishTweetCommandHandler getPublishTweetCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { + return new PublishTweetCommandHandler(tweetService); + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index b453ca5..5ff8383 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -4,6 +4,7 @@ import com.scmspain.application.services.TweetMetricService; import com.scmspain.application.services.TweetValidationService; import com.scmspain.domain.TweetService; +import com.scmspain.domain.command.CommandBus; import com.scmspain.infrastructure.controller.TweetController; import org.springframework.beans.factory.annotation.Qualifier; @@ -23,8 +24,8 @@ public TweetService getTweetService(@Qualifier("tweetRepository") TweetService t } @Bean - public TweetController getTweetConfiguration(@Qualifier("mainTweetService") TweetService tweetService) { - return new TweetController(tweetService); + public TweetController getTweetConfiguration(final CommandBus commandBus, final @Qualifier("mainTweetService") TweetService tweetService) { + return new TweetController(commandBus, tweetService); } } diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index 1594331..b1b23ed 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -1,6 +1,7 @@ package com.scmspain.infrastructure.controller; import com.scmspain.domain.TweetService; +import com.scmspain.domain.command.CommandBus; import com.scmspain.domain.command.PublishTweetCommand; import com.scmspain.domain.model.TweetResponse; import org.springframework.web.bind.annotation.*; @@ -10,12 +11,22 @@ import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CREATED; +/** + * Rest controller for tweet API. + */ @RestController public class TweetController { + private final CommandBus commandBus; private final TweetService tweetService; - public TweetController(TweetService tweetService) { + /** + * Constructor. + * + * @param commandBus Command bus. + */ + public TweetController(final CommandBus commandBus, final TweetService tweetService) { + this.commandBus = commandBus; this.tweetService = tweetService; } @@ -27,7 +38,7 @@ public List listAllTweets() { @PostMapping("/tweet") @ResponseStatus(CREATED) public void publishTweet(@RequestBody PublishTweetCommand publishTweetCommand) { - this.tweetService.publish(publishTweetCommand.getPublisher(), publishTweetCommand.getTweet()); + this.commandBus.execute(publishTweetCommand); } @ExceptionHandler(IllegalArgumentException.class) From 613b27d54ea23f8c6610d8ccd760a7736ac113a1 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 17:29:30 +0200 Subject: [PATCH 23/43] Add lists all tweets command and handler. --- .../domain/command/ListAllTweetsCommand.java | 10 +++++++ .../command/ListAllTweetsCommandHandler.java | 29 +++++++++++++++++++ .../CommandBusConfiguration.java | 6 ++++ .../configuration/TweetConfiguration.java | 4 +-- .../controller/TweetController.java | 8 ++--- 5 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/scmspain/domain/command/ListAllTweetsCommand.java create mode 100644 src/main/java/com/scmspain/domain/command/ListAllTweetsCommandHandler.java diff --git a/src/main/java/com/scmspain/domain/command/ListAllTweetsCommand.java b/src/main/java/com/scmspain/domain/command/ListAllTweetsCommand.java new file mode 100644 index 0000000..57b8bf2 --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/ListAllTweetsCommand.java @@ -0,0 +1,10 @@ +package com.scmspain.domain.command; + +import java.util.List; + +import com.scmspain.domain.model.TweetResponse; + +/** + * Command for list all available tweets. + */ +public class ListAllTweetsCommand implements Command> { } diff --git a/src/main/java/com/scmspain/domain/command/ListAllTweetsCommandHandler.java b/src/main/java/com/scmspain/domain/command/ListAllTweetsCommandHandler.java new file mode 100644 index 0000000..7d4a83e --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/ListAllTweetsCommandHandler.java @@ -0,0 +1,29 @@ +package com.scmspain.domain.command; + +import java.util.List; + +import com.scmspain.domain.TweetService; +import com.scmspain.domain.model.TweetResponse; + +/** + * Handler for the list all tweets command. + */ +public class ListAllTweetsCommandHandler implements CommandHandler, ListAllTweetsCommand> { + + private final TweetService tweetService; + + /** + * Constructor. + * + * @param tweetService Tweet service. + */ + public ListAllTweetsCommandHandler(final TweetService tweetService) { + this.tweetService = tweetService; + } + + @Override + public List handle(ListAllTweetsCommand command) { + return this.tweetService.listAll(); + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java index 0f75b77..e75c1c9 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java @@ -7,6 +7,7 @@ import com.scmspain.domain.TweetService; import com.scmspain.domain.command.CommandBus; +import com.scmspain.domain.command.ListAllTweetsCommandHandler; import com.scmspain.domain.command.PublishTweetCommandHandler; import com.scmspain.infrastructure.commandbus.Registry; import com.scmspain.infrastructure.commandbus.SpringCommandBus; @@ -27,4 +28,9 @@ public PublishTweetCommandHandler getPublishTweetCommandHandler(@Qualifier("main return new PublishTweetCommandHandler(tweetService); } + @Bean + public ListAllTweetsCommandHandler getListAllTweetsCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { + return new ListAllTweetsCommandHandler(tweetService); + } + } diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 5ff8383..77f76fa 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -24,8 +24,8 @@ public TweetService getTweetService(@Qualifier("tweetRepository") TweetService t } @Bean - public TweetController getTweetConfiguration(final CommandBus commandBus, final @Qualifier("mainTweetService") TweetService tweetService) { - return new TweetController(commandBus, tweetService); + public TweetController getTweetConfiguration(final CommandBus commandBus) { + return new TweetController(commandBus); } } diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index b1b23ed..ccaaa3d 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -1,7 +1,7 @@ package com.scmspain.infrastructure.controller; -import com.scmspain.domain.TweetService; import com.scmspain.domain.command.CommandBus; +import com.scmspain.domain.command.ListAllTweetsCommand; import com.scmspain.domain.command.PublishTweetCommand; import com.scmspain.domain.model.TweetResponse; import org.springframework.web.bind.annotation.*; @@ -18,21 +18,19 @@ public class TweetController { private final CommandBus commandBus; - private final TweetService tweetService; /** * Constructor. * * @param commandBus Command bus. */ - public TweetController(final CommandBus commandBus, final TweetService tweetService) { + public TweetController(final CommandBus commandBus) { this.commandBus = commandBus; - this.tweetService = tweetService; } @GetMapping("/tweet") public List listAllTweets() { - return this.tweetService.listAll(); + return this.commandBus.execute(new ListAllTweetsCommand()); } @PostMapping("/tweet") From 4743de1ebfe8677afcfffc085695c0bf510903f0 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 17:38:39 +0200 Subject: [PATCH 24/43] Refactor command bus configuration, split between tweet and infrastructure. --- .../com/scmspain/MsFcTechTestApplication.java | 3 +- .../CommandBusConfiguration.java | 36 ------------------- .../InfrastructureConfiguration.java | 9 +++++ .../configuration/TweetConfiguration.java | 16 +++++++-- 4 files changed, 24 insertions(+), 40 deletions(-) delete mode 100644 src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java diff --git a/src/main/java/com/scmspain/MsFcTechTestApplication.java b/src/main/java/com/scmspain/MsFcTechTestApplication.java index fb93138..e40fb18 100644 --- a/src/main/java/com/scmspain/MsFcTechTestApplication.java +++ b/src/main/java/com/scmspain/MsFcTechTestApplication.java @@ -1,6 +1,5 @@ package com.scmspain; -import com.scmspain.infrastructure.configuration.CommandBusConfiguration; import com.scmspain.infrastructure.configuration.InfrastructureConfiguration; import com.scmspain.infrastructure.configuration.TweetConfiguration; import org.springframework.boot.SpringApplication; @@ -10,7 +9,7 @@ @Configuration @EnableAutoConfiguration -@Import({TweetConfiguration.class, InfrastructureConfiguration.class, CommandBusConfiguration.class}) +@Import({TweetConfiguration.class, InfrastructureConfiguration.class}) public class MsFcTechTestApplication { public static void main(String[] args) { diff --git a/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java deleted file mode 100644 index e75c1c9..0000000 --- a/src/main/java/com/scmspain/infrastructure/configuration/CommandBusConfiguration.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.scmspain.infrastructure.configuration; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.scmspain.domain.TweetService; -import com.scmspain.domain.command.CommandBus; -import com.scmspain.domain.command.ListAllTweetsCommandHandler; -import com.scmspain.domain.command.PublishTweetCommandHandler; -import com.scmspain.infrastructure.commandbus.Registry; -import com.scmspain.infrastructure.commandbus.SpringCommandBus; - -/** - * Command bus configuration. - */ -@Configuration -public class CommandBusConfiguration { - - @Bean - public CommandBus getCommandBus(final ApplicationContext applicationContext) { - return new SpringCommandBus(new Registry(applicationContext)); - } - - @Bean - public PublishTweetCommandHandler getPublishTweetCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { - return new PublishTweetCommandHandler(tweetService); - } - - @Bean - public ListAllTweetsCommandHandler getListAllTweetsCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { - return new ListAllTweetsCommandHandler(tweetService); - } - -} diff --git a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java index 5ab9c6b..7c9fa5f 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java @@ -5,12 +5,16 @@ import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter; import org.springframework.boot.actuate.metrics.jmx.JmxMetricWriter; import org.springframework.boot.actuate.metrics.writer.MetricWriter; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jmx.export.MBeanExporter; import com.scmspain.domain.MetricService; import com.scmspain.domain.TweetService; +import com.scmspain.domain.command.CommandBus; +import com.scmspain.infrastructure.commandbus.Registry; +import com.scmspain.infrastructure.commandbus.SpringCommandBus; import com.scmspain.infrastructure.database.TweetRepository; import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; @@ -20,6 +24,11 @@ @Configuration public class InfrastructureConfiguration { + @Bean + public CommandBus getCommandBus(final ApplicationContext applicationContext) { + return new SpringCommandBus(new Registry(applicationContext)); + } + @Bean @ExportMetricWriter public MetricWriter getMetricWriter(final MBeanExporter exporter) { diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 77f76fa..92b8970 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -5,6 +5,8 @@ import com.scmspain.application.services.TweetValidationService; import com.scmspain.domain.TweetService; import com.scmspain.domain.command.CommandBus; +import com.scmspain.domain.command.ListAllTweetsCommandHandler; +import com.scmspain.domain.command.PublishTweetCommandHandler; import com.scmspain.infrastructure.controller.TweetController; import org.springframework.beans.factory.annotation.Qualifier; @@ -18,14 +20,24 @@ public class TweetConfiguration { @Bean("mainTweetService") - public TweetService getTweetService(@Qualifier("tweetRepository") TweetService tweetService, MetricService metricService) { + public TweetService getTweetService(final @Qualifier("tweetRepository") TweetService tweetService, final MetricService metricService) { TweetService tweetValidationService = new TweetValidationService(tweetService); return new TweetMetricService(tweetValidationService, metricService); } @Bean - public TweetController getTweetConfiguration(final CommandBus commandBus) { + public TweetController getTweetController(final CommandBus commandBus) { return new TweetController(commandBus); } + @Bean + public PublishTweetCommandHandler getPublishTweetCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { + return new PublishTweetCommandHandler(tweetService); + } + + @Bean + public ListAllTweetsCommandHandler getListAllTweetsCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { + return new ListAllTweetsCommandHandler(tweetService); + } + } From 609f1136110e945e5930cb99c9d030e6ddcbead7 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 18:27:50 +0200 Subject: [PATCH 25/43] The list must be sorted by publication date in descending order. --- .../scmspain/domain/model/TweetResponse.java | 16 ++++++++++- .../database/TweetRepository.java | 7 +++-- .../database/entities/Tweet.java | 22 +++++++++++++-- .../controller/TweetControllerTest.java | 28 +++++++++++++++++++ .../scmspain/services/TweetServiceTest.java | 2 +- 5 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/scmspain/domain/model/TweetResponse.java b/src/main/java/com/scmspain/domain/model/TweetResponse.java index 492138e..e7847b4 100644 --- a/src/main/java/com/scmspain/domain/model/TweetResponse.java +++ b/src/main/java/com/scmspain/domain/model/TweetResponse.java @@ -1,5 +1,7 @@ package com.scmspain.domain.model; +import java.util.Date; + /** * Tweet response. */ @@ -7,16 +9,19 @@ public class TweetResponse { private final String publisher; private final String tweet; + private final Date publicationDate; /** * Constructor. * * @param publisher Creator of the tweet. * @param tweet Content of the tweet. + * @param publicationDate Publication date of the tweet. */ - public TweetResponse(final String publisher, final String tweet) { + public TweetResponse(final String publisher, final String tweet, final Date publicationDate) { this.publisher = publisher; this.tweet = tweet; + this.publicationDate = publicationDate; } /** @@ -37,4 +42,13 @@ public String getTweet() { return tweet; } + /** + * Gets the publication date. + * + * @return Publication date of the tweet. + */ + public Date getPublicationDate() { + return publicationDate; + } + } diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java index 5fa4fd1..409bf42 100644 --- a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java +++ b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java @@ -1,5 +1,6 @@ package com.scmspain.infrastructure.database; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; @@ -33,7 +34,7 @@ public TweetRepository(EntityManager entityManager) { @Override public void publish(String publisher, String text) { - Tweet tweet = new Tweet(publisher, text); + Tweet tweet = new Tweet(publisher, text, new Date()); this.entityManager.persist(tweet); } @@ -41,7 +42,7 @@ public void publish(String publisher, String text) { public List listAll() { TypedQuery query = this.entityManager - .createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); + .createQuery("SELECT id FROM Tweet WHERE pre2015MigrationStatus<>99 ORDER BY publicationDate DESC", Long.class); return query @@ -59,7 +60,7 @@ public List listAll() { */ private TweetResponse getTweet(final Long id) { final Tweet tweet = this.entityManager.find(Tweet.class, id); - return new TweetResponse(tweet.getPublisher(), tweet.getText()); + return new TweetResponse(tweet.getPublisher(), tweet.getText(), tweet.getPublicationDate()); } } diff --git a/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java index 363e923..c1104ec 100644 --- a/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java @@ -1,5 +1,7 @@ package com.scmspain.infrastructure.database.entities; +import java.util.Date; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -24,6 +26,9 @@ public class Tweet { @Column private Long pre2015MigrationStatus = 0L; + @Column + private Date publicationDate; + /** * Constructor to help the persistence framework to instantiate the entity. */ @@ -32,12 +37,14 @@ private Tweet() { } /** * Constructor with parameters. * - * @param publisher Tweet's publisher. - * @param text Tweet's text. + * @param publisher Creator of the tweet. + * @param text Content of the tweet. + * @param publicationDate Publication date of the tweet. */ - public Tweet(final String publisher, final String text) { + public Tweet(final String publisher, final String text, final Date publicationDate) { this.publisher = publisher; this.text = text; + this.publicationDate = publicationDate; } /** @@ -58,4 +65,13 @@ public String getText() { return text; } + /** + * Gets the publication date of the tweet. + * + * @return Publidation date. + */ + public Date getPublicationDate() { + return publicationDate; + } + } diff --git a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java index e4d06b4..03664a4 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java +++ b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.scmspain.infrastructure.configuration.TestConfiguration; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -14,7 +15,9 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.context.WebApplicationContext; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; @@ -73,4 +76,29 @@ private MockHttpServletRequestBuilder newTweet(String publisher, String tweet) { .content(format("{\"publisher\": \"%s\", \"tweet\": \"%s\"}", publisher, tweet)); } + @Test + public void shouldListAllTweetsSortedByPublicationDateDescendingOrder() throws Exception { + mockMvc.perform(newTweet("First", "This is the first and older tweet.")).andExpect(status().is(201)); + mockMvc.perform(newTweet("Second", "This is the second tweet.")).andExpect(status().is(201)); + mockMvc.perform(newTweet("Third", "This is the third tweet.")).andExpect(status().is(201)); + mockMvc.perform(newTweet("Fourth", "This is the first and newer tweet.")).andExpect(status().is(201)); + + MvcResult getResult = + mockMvc + .perform(get("/tweet")) + .andExpect(status().is(200)) + .andReturn(); + + String content = getResult.getResponse().getContentAsString(); + List tweets = new ObjectMapper().readValue(content, List.class); + + assertThat(tweets).isSortedAccordingTo(this::decreasingPublicationDateTweetComparator); + } + + private int decreasingPublicationDateTweetComparator(Map tweet1, Map tweet2) { + Long object1 = (Long) tweet1.get("publicationDate"); + Long object2 = (Long) tweet2.get("publicationDate"); + return -object1.compareTo(object2); + } + } diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 6e846a7..62f3aa8 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -46,7 +46,7 @@ public void setUp() { @Test public void shouldInsertANewTweet() { - tweetService.publish(GUYBRUSH, VALID_MESSAGE); + this.tweetService.publish(GUYBRUSH, VALID_MESSAGE); InOrder inOrder = inOrder(metricWriter, entityManager); inOrder.verify(metricWriter).increment(any(Delta.class)); inOrder.verify(entityManager).persist(any(Tweet.class)); From 43f0f58f62c0466e8d09d9202a2a0d55a1de5f75 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 19:22:43 +0200 Subject: [PATCH 26/43] Ignoring URLs at tweet's content. --- .../services/TweetValidationService.java | 14 ++++--- .../application/services/UrlExtractor.java | 39 +++++++++++++++++++ .../controller/TweetControllerTest.java | 2 +- .../scmspain/services/TweetServiceTest.java | 9 +++++ 4 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/scmspain/application/services/UrlExtractor.java diff --git a/src/main/java/com/scmspain/application/services/TweetValidationService.java b/src/main/java/com/scmspain/application/services/TweetValidationService.java index 372e357..5f68b20 100644 --- a/src/main/java/com/scmspain/application/services/TweetValidationService.java +++ b/src/main/java/com/scmspain/application/services/TweetValidationService.java @@ -13,6 +13,7 @@ public class TweetValidationService implements TweetService { private static final int MAX_CHARACTERS = 140; private final TweetService tweetService; + private final UrlExtractor urlExtractor; /** * Constructor. @@ -21,6 +22,7 @@ public class TweetValidationService implements TweetService { */ public TweetValidationService(final TweetService tweetService) { this.tweetService = tweetService; + this.urlExtractor = new UrlExtractor(); } @Override @@ -42,16 +44,16 @@ public void publish(final String publisher, final String text) { this.tweetService.publish(publisher, text); } - private boolean textTooLong(String text) { - return text.length() > MAX_CHARACTERS; - } - - private boolean textEmpty(String text) { + private boolean textEmpty(final String text) { return text == null || text.isEmpty(); } - private boolean publisherEmpty(String publisher) { + private boolean publisherEmpty(final String publisher) { return publisher == null || publisher.isEmpty(); } + private boolean textTooLong(final String text) { + return urlExtractor.extract(text).length() > MAX_CHARACTERS; + } + } diff --git a/src/main/java/com/scmspain/application/services/UrlExtractor.java b/src/main/java/com/scmspain/application/services/UrlExtractor.java new file mode 100644 index 0000000..8e7c5aa --- /dev/null +++ b/src/main/java/com/scmspain/application/services/UrlExtractor.java @@ -0,0 +1,39 @@ +package com.scmspain.application.services; + +/** + * + */ +public class UrlExtractor { + + private static final String WHITE_SPACE = " "; + private static final String HTTP_PROTOCOL = "http://"; + private static final String HTTPS_PROTOCOL = "https://"; + + /** + * + * @param text + * @return + */ + public String extract(final String text) { + String cleanText = extract(text, HTTP_PROTOCOL); + return extract(cleanText, HTTPS_PROTOCOL); + } + + private String extract(final String text, final String protocol) { + if (text.contains(protocol)) { + String newText = extractUrl(text, protocol); + return extract(newText, protocol); + } + return text; + } + + private String extractUrl(String text, String protocol) { + int urlStart = text.indexOf(protocol); + int urlEnd = text.indexOf(WHITE_SPACE, urlStart); + if (urlEnd == -1) { + return text.substring(0, urlStart); + } + return text.substring(0, urlStart) + text.substring(urlEnd + 1); + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java index 03664a4..ab1122b 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java +++ b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java @@ -50,7 +50,7 @@ public void shouldReturn200WhenInsertingAValidTweet() throws Exception { @Test public void shouldReturn400WhenInsertingAnInvalidTweet() throws Exception { mockMvc - .perform(newTweet("Schibsted Spain", "We are Schibsted Spain (look at our home page http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome!")) + .perform(newTweet("Schibsted Spain", "We are Schibsted Spain (look at our home page http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome! Text added to make it fail.")) .andExpect(status().is(400)); } diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 62f3aa8..f39ee8e 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -26,6 +26,7 @@ public class TweetServiceTest { private static final String PIRATE = "Pirate"; private static final String VALID_MESSAGE = "I am Guybrush Threepwood, mighty pirate."; private static final String TOO_LONG_MESSAGE = "LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to drop dead. So he did. Then things really got ugly."; + private static final String VALID_MESSAGE_WITH_URLS = "Link http 1 http://www.foogle.com - link https 1 https://www.foogle.com - link http 2 http://www.foogle.com - link https 2 https://www.foogle.com"; private EntityManager entityManager; private MetricWriter metricWriter; @@ -77,4 +78,12 @@ public void shouldThrowAnExceptionWhenTweetTextLengthIsInvalid() { this.tweetService.publish(PIRATE, TOO_LONG_MESSAGE); } + @Test + public void shouldNotCountUrlsForTweetLength() { + this.tweetService.publish(GUYBRUSH, VALID_MESSAGE_WITH_URLS); + InOrder inOrder = inOrder(metricWriter, entityManager); + inOrder.verify(metricWriter).increment(any(Delta.class)); + inOrder.verify(entityManager).persist(any(Tweet.class)); + } + } From b8d638ff22a9b5b6cffdedb9b618155ca9f4fdd3 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 19:36:10 +0200 Subject: [PATCH 27/43] Ignoring URLs at tweet's content. --- .../com/scmspain/application/services/UrlExtractor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/scmspain/application/services/UrlExtractor.java b/src/main/java/com/scmspain/application/services/UrlExtractor.java index 8e7c5aa..903ef53 100644 --- a/src/main/java/com/scmspain/application/services/UrlExtractor.java +++ b/src/main/java/com/scmspain/application/services/UrlExtractor.java @@ -1,7 +1,7 @@ package com.scmspain.application.services; /** - * + * Extractor of URLs from a String for the some protocols. */ public class UrlExtractor { @@ -10,9 +10,10 @@ public class UrlExtractor { private static final String HTTPS_PROTOCOL = "https://"; /** + * Given a text removes the URLs contained on it. * - * @param text - * @return + * @param text Text. + * @return Text without URLs. */ public String extract(final String text) { String cleanText = extract(text, HTTP_PROTOCOL); From 5f8584eda1b94883fb9010c6f9b641defe13d74f Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 21:27:39 +0200 Subject: [PATCH 28/43] Renaming. --- .../services/TweetValidationService.java | 6 +++--- .../{UrlExtractor.java => UrlRemover.java} | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) rename src/main/java/com/scmspain/application/services/{UrlExtractor.java => UrlRemover.java} (59%) diff --git a/src/main/java/com/scmspain/application/services/TweetValidationService.java b/src/main/java/com/scmspain/application/services/TweetValidationService.java index 5f68b20..86319c5 100644 --- a/src/main/java/com/scmspain/application/services/TweetValidationService.java +++ b/src/main/java/com/scmspain/application/services/TweetValidationService.java @@ -13,7 +13,7 @@ public class TweetValidationService implements TweetService { private static final int MAX_CHARACTERS = 140; private final TweetService tweetService; - private final UrlExtractor urlExtractor; + private final UrlRemover urlRemover; /** * Constructor. @@ -22,7 +22,7 @@ public class TweetValidationService implements TweetService { */ public TweetValidationService(final TweetService tweetService) { this.tweetService = tweetService; - this.urlExtractor = new UrlExtractor(); + this.urlRemover = new UrlRemover(); } @Override @@ -53,7 +53,7 @@ private boolean publisherEmpty(final String publisher) { } private boolean textTooLong(final String text) { - return urlExtractor.extract(text).length() > MAX_CHARACTERS; + return urlRemover.removeUrls(text).length() > MAX_CHARACTERS; } } diff --git a/src/main/java/com/scmspain/application/services/UrlExtractor.java b/src/main/java/com/scmspain/application/services/UrlRemover.java similarity index 59% rename from src/main/java/com/scmspain/application/services/UrlExtractor.java rename to src/main/java/com/scmspain/application/services/UrlRemover.java index 903ef53..1482a3d 100644 --- a/src/main/java/com/scmspain/application/services/UrlExtractor.java +++ b/src/main/java/com/scmspain/application/services/UrlRemover.java @@ -1,9 +1,9 @@ package com.scmspain.application.services; /** - * Extractor of URLs from a String for the some protocols. + * Remove URLs from a String for some protocols. */ -public class UrlExtractor { +public class UrlRemover { private static final String WHITE_SPACE = " "; private static final String HTTP_PROTOCOL = "http://"; @@ -15,20 +15,20 @@ public class UrlExtractor { * @param text Text. * @return Text without URLs. */ - public String extract(final String text) { - String cleanText = extract(text, HTTP_PROTOCOL); - return extract(cleanText, HTTPS_PROTOCOL); + public String removeUrls(final String text) { + String cleanText = removeUrls(text, HTTP_PROTOCOL); + return removeUrls(cleanText, HTTPS_PROTOCOL); } - private String extract(final String text, final String protocol) { + private String removeUrls(final String text, final String protocol) { if (text.contains(protocol)) { - String newText = extractUrl(text, protocol); - return extract(newText, protocol); + String newText = removeUrl(text, protocol); + return removeUrls(newText, protocol); } return text; } - private String extractUrl(String text, String protocol) { + private String removeUrl(String text, String protocol) { int urlStart = text.indexOf(protocol); int urlEnd = text.indexOf(WHITE_SPACE, urlStart); if (urlEnd == -1) { From 61b6a66cefce93125061de8813eb224dc704f0c1 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 21:38:07 +0200 Subject: [PATCH 29/43] Publish tweet test for tweets with URLs. Tweet entity was updated to increase the size of the content from 140 to 2000 (it has to store long messages with URLs). It's not a good solution. --- .../scmspain/infrastructure/database/entities/Tweet.java | 2 +- .../infrastructure/controller/TweetControllerTest.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java index c1104ec..5d90e6f 100644 --- a/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java @@ -20,7 +20,7 @@ public class Tweet { @Column(nullable = false) private String publisher; - @Column(name = "tweet", nullable = false, length = 140) + @Column(name = "tweet", nullable = false, length = 2000) private String text; @Column diff --git a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java index ab1122b..b78fb5c 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java +++ b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java @@ -47,6 +47,13 @@ public void shouldReturn200WhenInsertingAValidTweet() throws Exception { .andExpect(status().is(201)); } + @Test + public void shouldReturn200WhenInsertingAValidTweetWithUrls() throws Exception { + mockMvc + .perform(newTweet("Prospect", "Breaking the law: http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com")) + .andExpect(status().is(201)); + } + @Test public void shouldReturn400WhenInsertingAnInvalidTweet() throws Exception { mockMvc From f7c6c6e8898ed6110e6f9f1a1496da504af448fc Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 22:24:14 +0200 Subject: [PATCH 30/43] Removing publication date from tweet response (it's a requirement I forgot). --- .../scmspain/domain/model/TweetResponse.java | 14 +--- .../database/TweetRepository.java | 2 +- .../controller/TweetControllerTest.java | 68 +++++++++++++------ 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/scmspain/domain/model/TweetResponse.java b/src/main/java/com/scmspain/domain/model/TweetResponse.java index e7847b4..39850e1 100644 --- a/src/main/java/com/scmspain/domain/model/TweetResponse.java +++ b/src/main/java/com/scmspain/domain/model/TweetResponse.java @@ -9,19 +9,16 @@ public class TweetResponse { private final String publisher; private final String tweet; - private final Date publicationDate; /** * Constructor. * * @param publisher Creator of the tweet. * @param tweet Content of the tweet. - * @param publicationDate Publication date of the tweet. */ - public TweetResponse(final String publisher, final String tweet, final Date publicationDate) { + public TweetResponse(final String publisher, final String tweet) { this.publisher = publisher; this.tweet = tweet; - this.publicationDate = publicationDate; } /** @@ -42,13 +39,4 @@ public String getTweet() { return tweet; } - /** - * Gets the publication date. - * - * @return Publication date of the tweet. - */ - public Date getPublicationDate() { - return publicationDate; - } - } diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java index 409bf42..7b5ee3f 100644 --- a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java +++ b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java @@ -60,7 +60,7 @@ public List listAll() { */ private TweetResponse getTweet(final Long id) { final Tweet tweet = this.entityManager.find(Tweet.class, id); - return new TweetResponse(tweet.getPublisher(), tweet.getText(), tweet.getPublicationDate()); + return new TweetResponse(tweet.getPublisher(), tweet.getText()); } } diff --git a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java index b78fb5c..e821bc1 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java +++ b/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java @@ -1,7 +1,8 @@ package com.scmspain.infrastructure.controller; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.scmspain.infrastructure.configuration.TestConfiguration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -9,18 +10,19 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.context.WebApplicationContext; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.scmspain.infrastructure.configuration.TestConfiguration; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -28,6 +30,7 @@ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfiguration.class) +@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) public class TweetControllerTest { @Autowired @@ -61,6 +64,19 @@ public void shouldReturn400WhenInsertingAnInvalidTweet() throws Exception { .andExpect(status().is(400)); } + @Test + public void shouldReturn200WhenDiscardingTweet() throws Exception { + mockMvc + .perform(newTweet("Me", "Tweet to discard")) + .andExpect(status().is(201)); + + MvcResult getResult = + mockMvc + .perform(get("/tweet")) + .andExpect(status().is(200)) + .andReturn(); + } + @Test public void shouldReturnAllPublishedTweets() throws Exception { mockMvc @@ -77,18 +93,17 @@ public void shouldReturnAllPublishedTweets() throws Exception { assertThat(new ObjectMapper().readValue(content, List.class).size()).isEqualTo(1); } - private MockHttpServletRequestBuilder newTweet(String publisher, String tweet) { - return post("/tweet") - .contentType(MediaType.APPLICATION_JSON_UTF8) - .content(format("{\"publisher\": \"%s\", \"tweet\": \"%s\"}", publisher, tweet)); - } - @Test public void shouldListAllTweetsSortedByPublicationDateDescendingOrder() throws Exception { - mockMvc.perform(newTweet("First", "This is the first and older tweet.")).andExpect(status().is(201)); - mockMvc.perform(newTweet("Second", "This is the second tweet.")).andExpect(status().is(201)); - mockMvc.perform(newTweet("Third", "This is the third tweet.")).andExpect(status().is(201)); - mockMvc.perform(newTweet("Fourth", "This is the first and newer tweet.")).andExpect(status().is(201)); + Map tweet1 = newMapTweet("First", "First tweet"); + Map tweet2 = newMapTweet("Second", "Second tweet"); + Map tweet3 = newMapTweet("Third", "Third tweet"); + Map tweet4 = newMapTweet("Fourth", "Fourth tweet"); + + mockMvc.perform(newTweet(tweet1)).andExpect(status().is(201)); + mockMvc.perform(newTweet(tweet2)).andExpect(status().is(201)); + mockMvc.perform(newTweet(tweet3)).andExpect(status().is(201)); + mockMvc.perform(newTweet(tweet4)).andExpect(status().is(201)); MvcResult getResult = mockMvc @@ -97,15 +112,26 @@ public void shouldListAllTweetsSortedByPublicationDateDescendingOrder() throws E .andReturn(); String content = getResult.getResponse().getContentAsString(); - List tweets = new ObjectMapper().readValue(content, List.class); + List tweets = new ObjectMapper().readValue(content, List.class); + + assertThat(tweets).containsExactly(tweet4, tweet3, tweet2, tweet1); + } + + private MockHttpServletRequestBuilder newTweet(Map tweet) { + return newTweet(tweet.get("publisher"), tweet.get("tweet")); + } - assertThat(tweets).isSortedAccordingTo(this::decreasingPublicationDateTweetComparator); + private MockHttpServletRequestBuilder newTweet(String publisher, String tweet) { + return post("/tweet") + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(format("{\"publisher\": \"%s\", \"tweet\": \"%s\"}", publisher, tweet)); } - private int decreasingPublicationDateTweetComparator(Map tweet1, Map tweet2) { - Long object1 = (Long) tweet1.get("publicationDate"); - Long object2 = (Long) tweet2.get("publicationDate"); - return -object1.compareTo(object2); + private Map newMapTweet(final String publisher, final String tweet) { + Map map = new LinkedHashMap<>(); + map.put("publisher", publisher); + map.put("tweet", tweet); + return map; } } From 593db909e11afaccc3915c548eed6490220cf283 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Sun, 15 Apr 2018 22:32:54 +0200 Subject: [PATCH 31/43] Refactoring tests to increase execution speed. --- ...t.java => ListAllTweetsCommandITCase.java} | 55 +--------------- .../controller/PublishTweetCommandITCase.java | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 54 deletions(-) rename src/test/java/com/scmspain/infrastructure/controller/{TweetControllerTest.java => ListAllTweetsCommandITCase.java} (61%) create mode 100644 src/test/java/com/scmspain/infrastructure/controller/PublishTweetCommandITCase.java diff --git a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java similarity index 61% rename from src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java rename to src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java index e821bc1..4e90c52 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/TweetControllerTest.java +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java @@ -10,7 +10,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -22,7 +21,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -30,8 +28,7 @@ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfiguration.class) -@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) -public class TweetControllerTest { +public class ListAllTweetsCommandITCase { @Autowired private WebApplicationContext context; @@ -43,56 +40,6 @@ public void setUp() { this.mockMvc = webAppContextSetup(this.context).build(); } - @Test - public void shouldReturn200WhenInsertingAValidTweet() throws Exception { - mockMvc - .perform(newTweet("Prospect", "Breaking the law")) - .andExpect(status().is(201)); - } - - @Test - public void shouldReturn200WhenInsertingAValidTweetWithUrls() throws Exception { - mockMvc - .perform(newTweet("Prospect", "Breaking the law: http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com")) - .andExpect(status().is(201)); - } - - @Test - public void shouldReturn400WhenInsertingAnInvalidTweet() throws Exception { - mockMvc - .perform(newTweet("Schibsted Spain", "We are Schibsted Spain (look at our home page http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome! Text added to make it fail.")) - .andExpect(status().is(400)); - } - - @Test - public void shouldReturn200WhenDiscardingTweet() throws Exception { - mockMvc - .perform(newTweet("Me", "Tweet to discard")) - .andExpect(status().is(201)); - - MvcResult getResult = - mockMvc - .perform(get("/tweet")) - .andExpect(status().is(200)) - .andReturn(); - } - - @Test - public void shouldReturnAllPublishedTweets() throws Exception { - mockMvc - .perform(newTweet("Yo", "How are you?")) - .andExpect(status().is(201)); - - MvcResult getResult = - mockMvc - .perform(get("/tweet")) - .andExpect(status().is(200)) - .andReturn(); - - String content = getResult.getResponse().getContentAsString(); - assertThat(new ObjectMapper().readValue(content, List.class).size()).isEqualTo(1); - } - @Test public void shouldListAllTweetsSortedByPublicationDateDescendingOrder() throws Exception { Map tweet1 = newMapTweet("First", "First tweet"); diff --git a/src/test/java/com/scmspain/infrastructure/controller/PublishTweetCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/PublishTweetCommandITCase.java new file mode 100644 index 0000000..1488b33 --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/controller/PublishTweetCommandITCase.java @@ -0,0 +1,62 @@ +package com.scmspain.infrastructure.controller; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.web.context.WebApplicationContext; + +import com.scmspain.infrastructure.configuration.TestConfiguration; + +import static java.lang.String.format; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestConfiguration.class) +public class PublishTweetCommandITCase { + + @Autowired + private WebApplicationContext context; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = webAppContextSetup(this.context).build(); + } + + @Test + public void shouldReturn200WhenInsertingAValidTweet() throws Exception { + mockMvc + .perform(newTweet("Prospect", "Breaking the law")) + .andExpect(status().is(201)); + } + + @Test + public void shouldReturn200WhenInsertingAValidTweetWithUrls() throws Exception { + mockMvc + .perform(newTweet("Prospect", "Breaking the law: http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com")) + .andExpect(status().is(201)); + } + + @Test + public void shouldReturn400WhenInsertingAnInvalidTweet() throws Exception { + mockMvc + .perform(newTweet("Schibsted Spain", "We are Schibsted Spain (look at our home page http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome! Text added to make it fail.")) + .andExpect(status().is(400)); + } + + private MockHttpServletRequestBuilder newTweet(String publisher, String tweet) { + return post("/tweet") + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(format("{\"publisher\": \"%s\", \"tweet\": \"%s\"}", publisher, tweet)); + } + +} From 6b2286fb7c584e0b6585722bd4c78f7f3c47f284 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Mon, 16 Apr 2018 21:44:54 +0200 Subject: [PATCH 32/43] Discard tweet command and handler. --- .../services/TweetMetricService.java | 10 ++- .../services/TweetValidationService.java | 13 +++- .../domain/TweetNotFoundException.java | 11 ++++ .../com/scmspain/domain/TweetService.java | 11 +++- .../scmspain/domain/command/CommandBus.java | 2 +- .../domain/command/CommandException.java | 9 +++ .../domain/command/CommandHandler.java | 2 +- .../domain/command/DiscardTweetCommand.java | 18 +++++ .../command/DiscardTweetCommandHandler.java | 28 ++++++++ .../domain/command/PublishTweetCommand.java | 2 +- .../command/PublishTweetCommandHandler.java | 7 +- .../commandbus/SpringCommandBus.java | 3 +- .../configuration/TweetConfiguration.java | 6 ++ .../controller/TweetController.java | 24 ++++++- .../database/TweetRepository.java | 40 +++++++++-- .../database/entities/Tweet.java | 27 ++++++-- .../commandbus/CommandBusITCase.java | 3 +- .../commandbus/SpringCommandBusTest.java | 3 +- .../controller/DiscardTweetCommandITCase.java | 66 +++++++++++++++++++ .../ListAllTweetsCommandITCase.java | 27 +++----- .../scmspain/services/TweetServiceTest.java | 58 +++++++++++++--- 21 files changed, 317 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/scmspain/domain/TweetNotFoundException.java create mode 100644 src/main/java/com/scmspain/domain/command/CommandException.java create mode 100644 src/main/java/com/scmspain/domain/command/DiscardTweetCommand.java create mode 100644 src/main/java/com/scmspain/domain/command/DiscardTweetCommandHandler.java create mode 100644 src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java diff --git a/src/main/java/com/scmspain/application/services/TweetMetricService.java b/src/main/java/com/scmspain/application/services/TweetMetricService.java index c74ed1f..7c4efc7 100644 --- a/src/main/java/com/scmspain/application/services/TweetMetricService.java +++ b/src/main/java/com/scmspain/application/services/TweetMetricService.java @@ -3,6 +3,7 @@ import java.util.List; import com.scmspain.domain.MetricService; +import com.scmspain.domain.TweetNotFoundException; import com.scmspain.domain.TweetService; import com.scmspain.domain.model.TweetResponse; @@ -32,9 +33,14 @@ public List listAll() { } @Override - public void publish(String publisher, String text) { + public Long publish(String publisher, String text) { metricService.incrementPublishedTweets(); - tweetService.publish(publisher, text); + return tweetService.publish(publisher, text); + } + + @Override + public void discard(Long tweetId) throws TweetNotFoundException { + tweetService.discard(tweetId); } } diff --git a/src/main/java/com/scmspain/application/services/TweetValidationService.java b/src/main/java/com/scmspain/application/services/TweetValidationService.java index 86319c5..85ab0aa 100644 --- a/src/main/java/com/scmspain/application/services/TweetValidationService.java +++ b/src/main/java/com/scmspain/application/services/TweetValidationService.java @@ -2,6 +2,7 @@ import java.util.List; +import com.scmspain.domain.TweetNotFoundException; import com.scmspain.domain.TweetService; import com.scmspain.domain.model.TweetResponse; @@ -31,7 +32,7 @@ public List listAll() { } @Override - public void publish(final String publisher, final String text) { + public Long publish(final String publisher, final String text) { if (publisherEmpty(publisher)) { throw new IllegalArgumentException("Publisher must not be empty"); } @@ -41,7 +42,15 @@ public void publish(final String publisher, final String text) { if (textTooLong(text)) { throw new IllegalArgumentException("Tweet must not be greater than " + MAX_CHARACTERS + " characters"); } - this.tweetService.publish(publisher, text); + return this.tweetService.publish(publisher, text); + } + + @Override + public void discard(Long tweetId) throws TweetNotFoundException { + if (tweetId == null) { + throw new IllegalArgumentException("Tweet identifier must not be empty"); + } + this.tweetService.discard(tweetId); } private boolean textEmpty(final String text) { diff --git a/src/main/java/com/scmspain/domain/TweetNotFoundException.java b/src/main/java/com/scmspain/domain/TweetNotFoundException.java new file mode 100644 index 0000000..f7a9f6f --- /dev/null +++ b/src/main/java/com/scmspain/domain/TweetNotFoundException.java @@ -0,0 +1,11 @@ +package com.scmspain.domain; + +import com.scmspain.domain.command.CommandException; + +public class TweetNotFoundException extends CommandException { + + public TweetNotFoundException(Long id) { + super(String.format("Tweet with identifier %s not found", id)); + } + +} diff --git a/src/main/java/com/scmspain/domain/TweetService.java b/src/main/java/com/scmspain/domain/TweetService.java index 38ceb91..05edacb 100644 --- a/src/main/java/com/scmspain/domain/TweetService.java +++ b/src/main/java/com/scmspain/domain/TweetService.java @@ -21,7 +21,16 @@ public interface TweetService { * * @param publisher Creator of the tweet. * @param text Content of the tweet. + * @return Identifier of the published tweet. */ - void publish(String publisher, String text); + Long publish(String publisher, String text); + + /** + * Discard a tweet. + * + * @param tweetId Tweet identifier. + * @throws TweetNotFoundException if no tweet found for the specified identifier. + */ + void discard(Long tweetId) throws TweetNotFoundException; } diff --git a/src/main/java/com/scmspain/domain/command/CommandBus.java b/src/main/java/com/scmspain/domain/command/CommandBus.java index 63c561a..87146e6 100644 --- a/src/main/java/com/scmspain/domain/command/CommandBus.java +++ b/src/main/java/com/scmspain/domain/command/CommandBus.java @@ -13,6 +13,6 @@ public interface CommandBus { * @param type of return value. * @param type of the command. */ - > R execute(C command); + > R execute(C command) throws CommandException; } diff --git a/src/main/java/com/scmspain/domain/command/CommandException.java b/src/main/java/com/scmspain/domain/command/CommandException.java new file mode 100644 index 0000000..1704f60 --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/CommandException.java @@ -0,0 +1,9 @@ +package com.scmspain.domain.command; + +public class CommandException extends Exception { + + public CommandException(String message) { + super(message); + } + +} diff --git a/src/main/java/com/scmspain/domain/command/CommandHandler.java b/src/main/java/com/scmspain/domain/command/CommandHandler.java index 3136ac1..c903b75 100644 --- a/src/main/java/com/scmspain/domain/command/CommandHandler.java +++ b/src/main/java/com/scmspain/domain/command/CommandHandler.java @@ -15,6 +15,6 @@ public interface CommandHandler> { * @return Return value as specified in {@link Command}. * {@link Void} if none value returned. */ - R handle(C command); + R handle(C command) throws CommandException; } diff --git a/src/main/java/com/scmspain/domain/command/DiscardTweetCommand.java b/src/main/java/com/scmspain/domain/command/DiscardTweetCommand.java new file mode 100644 index 0000000..038edaf --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/DiscardTweetCommand.java @@ -0,0 +1,18 @@ +package com.scmspain.domain.command; + +/** + * Command to discard a tweet. + */ +public class DiscardTweetCommand implements Command { + + private Long tweet; + + /** + * Gets the tweet identifier to discard. + * @return Tweet identifier. + */ + public Long getTweet() { + return tweet; + } + +} diff --git a/src/main/java/com/scmspain/domain/command/DiscardTweetCommandHandler.java b/src/main/java/com/scmspain/domain/command/DiscardTweetCommandHandler.java new file mode 100644 index 0000000..0dd70a3 --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/DiscardTweetCommandHandler.java @@ -0,0 +1,28 @@ +package com.scmspain.domain.command; + +import com.scmspain.domain.TweetNotFoundException; +import com.scmspain.domain.TweetService; + +/** + * Handler for the discard tweet command. + */ +public class DiscardTweetCommandHandler implements CommandHandler { + + private final TweetService tweetService; + + /** + * Constructor. + * + * @param tweetService Tweet service. + */ + public DiscardTweetCommandHandler(final TweetService tweetService) { + this.tweetService = tweetService; + } + + @Override + public Void handle(DiscardTweetCommand command) throws TweetNotFoundException { + tweetService.discard(command.getTweet()); + return null; + } + +} diff --git a/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java b/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java index 1b297ee..490b625 100644 --- a/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java +++ b/src/main/java/com/scmspain/domain/command/PublishTweetCommand.java @@ -3,7 +3,7 @@ /** * Command for publish a tweet. */ -public class PublishTweetCommand implements Command { +public class PublishTweetCommand implements Command { private String publisher; private String tweet; diff --git a/src/main/java/com/scmspain/domain/command/PublishTweetCommandHandler.java b/src/main/java/com/scmspain/domain/command/PublishTweetCommandHandler.java index 7af234e..077acd5 100644 --- a/src/main/java/com/scmspain/domain/command/PublishTweetCommandHandler.java +++ b/src/main/java/com/scmspain/domain/command/PublishTweetCommandHandler.java @@ -5,7 +5,7 @@ /** * Handler for the publish tweet command. */ -public class PublishTweetCommandHandler implements CommandHandler { +public class PublishTweetCommandHandler implements CommandHandler { private final TweetService tweetService; @@ -19,9 +19,8 @@ public PublishTweetCommandHandler(final TweetService tweetService) { } @Override - public Void handle(PublishTweetCommand command) { - tweetService.publish(command.getPublisher(), command.getTweet()); - return null; + public Long handle(PublishTweetCommand command) { + return tweetService.publish(command.getPublisher(), command.getTweet()); } } diff --git a/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java b/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java index c98af88..96ebb31 100644 --- a/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java +++ b/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java @@ -4,6 +4,7 @@ import com.scmspain.domain.command.Command; import com.scmspain.domain.command.CommandBus; +import com.scmspain.domain.command.CommandException; import com.scmspain.domain.command.CommandHandler; /** @@ -25,7 +26,7 @@ public SpringCommandBus(Registry registry) { @Override @SuppressWarnings("unchecked") - public > R execute(C command) { + public > R execute(C command) throws CommandException { CommandHandler commandHandler = (CommandHandler) registry.get(command.getClass()); return commandHandler.handle(command); } diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 92b8970..733da45 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -5,6 +5,7 @@ import com.scmspain.application.services.TweetValidationService; import com.scmspain.domain.TweetService; import com.scmspain.domain.command.CommandBus; +import com.scmspain.domain.command.DiscardTweetCommandHandler; import com.scmspain.domain.command.ListAllTweetsCommandHandler; import com.scmspain.domain.command.PublishTweetCommandHandler; import com.scmspain.infrastructure.controller.TweetController; @@ -35,6 +36,11 @@ public PublishTweetCommandHandler getPublishTweetCommandHandler(@Qualifier("main return new PublishTweetCommandHandler(tweetService); } + @Bean + public DiscardTweetCommandHandler getDiscardTweetCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { + return new DiscardTweetCommandHandler(tweetService); + } + @Bean public ListAllTweetsCommandHandler getListAllTweetsCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { return new ListAllTweetsCommandHandler(tweetService); diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index ccaaa3d..4f9fcc7 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -1,15 +1,20 @@ package com.scmspain.infrastructure.controller; +import com.scmspain.domain.TweetNotFoundException; import com.scmspain.domain.command.CommandBus; +import com.scmspain.domain.command.CommandException; +import com.scmspain.domain.command.DiscardTweetCommand; import com.scmspain.domain.command.ListAllTweetsCommand; import com.scmspain.domain.command.PublishTweetCommand; import com.scmspain.domain.model.TweetResponse; + import org.springframework.web.bind.annotation.*; import java.util.List; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.NOT_FOUND; /** * Rest controller for tweet API. @@ -29,16 +34,21 @@ public TweetController(final CommandBus commandBus) { } @GetMapping("/tweet") - public List listAllTweets() { + public List listAllTweets() throws CommandException { return this.commandBus.execute(new ListAllTweetsCommand()); } @PostMapping("/tweet") @ResponseStatus(CREATED) - public void publishTweet(@RequestBody PublishTweetCommand publishTweetCommand) { + public void publishTweet(@RequestBody PublishTweetCommand publishTweetCommand) throws CommandException { this.commandBus.execute(publishTweetCommand); } + @PostMapping("/discarded") + public void discardTweet(@RequestBody DiscardTweetCommand discardTweetCommand) throws CommandException { + this.commandBus.execute(discardTweetCommand); + } + @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(BAD_REQUEST) @ResponseBody @@ -49,4 +59,14 @@ public Object invalidArgumentException(IllegalArgumentException ex) { }; } + @ExceptionHandler(TweetNotFoundException.class) + @ResponseStatus(NOT_FOUND) + @ResponseBody + public Object tweetNotFoundException(TweetNotFoundException ex) { + return new Object() { + public String message = ex.getMessage(); + public String exceptionClass = ex.getClass().getSimpleName(); + }; + } + } diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java index 7b5ee3f..fcfb077 100644 --- a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java +++ b/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java @@ -2,6 +2,7 @@ import java.util.Date; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import javax.persistence.EntityManager; @@ -11,6 +12,7 @@ import org.springframework.stereotype.Repository; import com.scmspain.domain.TweetService; +import com.scmspain.domain.TweetNotFoundException; import com.scmspain.domain.model.TweetResponse; import com.scmspain.infrastructure.database.entities.Tweet; @@ -33,9 +35,17 @@ public TweetRepository(EntityManager entityManager) { } @Override - public void publish(String publisher, String text) { + public Long publish(String publisher, String text) { Tweet tweet = new Tweet(publisher, text, new Date()); this.entityManager.persist(tweet); + return tweet.getId(); + } + + @Override + public void discard(final Long tweetId) throws TweetNotFoundException { + Tweet tweet = getTweet(tweetId); + tweet.setDiscarded(); + this.entityManager.merge(tweet); } @Override @@ -48,19 +58,39 @@ public List listAll() { query .getResultList() .stream() - .map(this::getTweet) + .map(this::getTweetResponse) + .filter(Optional::isPresent) + .map(Optional::get) .collect(Collectors.toList()); } + /** + * Recover tweet from repository. + * + * @param id Identifier of the Tweet to retrieve + * @return Retrieved tweet response. + */ + private Optional getTweetResponse(final Long id) { + try { + Tweet tweet = getTweet(id); + return Optional.of(new TweetResponse(tweet.getPublisher(), tweet.getText())); + } catch (TweetNotFoundException e) { + return Optional.empty(); + } + } + /** * Recover tweet from repository. * * @param id Identifier of the Tweet to retrieve * @return Retrieved tweet. */ - private TweetResponse getTweet(final Long id) { - final Tweet tweet = this.entityManager.find(Tweet.class, id); - return new TweetResponse(tweet.getPublisher(), tweet.getText()); + public Tweet getTweet(final Long id) throws TweetNotFoundException { + Tweet tweet = this.entityManager.find(Tweet.class, id); + if (tweet == null) { + throw new TweetNotFoundException(id); + } + return tweet; } } diff --git a/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java index 5d90e6f..bbe47b1 100644 --- a/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java @@ -29,6 +29,9 @@ public class Tweet { @Column private Date publicationDate; + @Column + private Boolean discarded = Boolean.FALSE; + /** * Constructor to help the persistence framework to instantiate the entity. */ @@ -47,6 +50,15 @@ public Tweet(final String publisher, final String text, final Date publicationDa this.publicationDate = publicationDate; } + /** + * Gets de identifier of the tweet. + * + * @return Identifier. + */ + public Long getId() { + return id; + } + /** * Gets the publisher of the tweet. * @@ -66,12 +78,19 @@ public String getText() { } /** - * Gets the publication date of the tweet. + * Gets the disabled mark or the tweet. * - * @return Publidation date. + * @return True if tweet is disabled. + */ + public Boolean getDiscarded() { + return discarded; + } + + /** + * Mark the tweet as discarded. */ - public Date getPublicationDate() { - return publicationDate; + public void setDiscarded() { + this.discarded = true; } } diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java b/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java index 9c21dbb..71368c9 100644 --- a/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java +++ b/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java @@ -9,6 +9,7 @@ import org.springframework.test.context.junit4.SpringRunner; import com.scmspain.domain.command.CommandBus; +import com.scmspain.domain.command.CommandException; import com.scmspain.infrastructure.commandbus.command.ByeCommand; import com.scmspain.infrastructure.commandbus.command.HelloCommand; import com.scmspain.infrastructure.commandbus.handler.ByeCommandHandler; @@ -34,7 +35,7 @@ public class CommandBusITCase { private MessageCollector messageCollector; @Test - public void executeHandlersForGivenCommands() { + public void executeHandlersForGivenCommands() throws CommandException { String actualStringReturnValue = commandBus.execute(new HelloCommand("Schibsted")); Void actualVoidReturnValue = commandBus.execute(new ByeCommand("AEM")); diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/SpringCommandBusTest.java b/src/test/java/com/scmspain/infrastructure/commandbus/SpringCommandBusTest.java index 8f4acf4..cb9f5df 100644 --- a/src/test/java/com/scmspain/infrastructure/commandbus/SpringCommandBusTest.java +++ b/src/test/java/com/scmspain/infrastructure/commandbus/SpringCommandBusTest.java @@ -6,6 +6,7 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import com.scmspain.domain.command.CommandException; import com.scmspain.domain.command.CommandHandler; import com.scmspain.infrastructure.commandbus.command.HelloCommand; @@ -25,7 +26,7 @@ public class SpringCommandBusTest { private SpringCommandBus commandBus; @Test - public void shouldExecuteHandlerForCommand() { + public void shouldExecuteHandlerForCommand() throws CommandException { when(registry.get(HelloCommand.class)).thenReturn(handler); HelloCommand command = new HelloCommand("Schibsted"); diff --git a/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java new file mode 100644 index 0000000..990c2b6 --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java @@ -0,0 +1,66 @@ +package com.scmspain.infrastructure.controller; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.web.context.WebApplicationContext; + +import com.scmspain.domain.TweetNotFoundException; +import com.scmspain.infrastructure.configuration.TestConfiguration; +import com.scmspain.infrastructure.database.TweetRepository; +import com.scmspain.infrastructure.database.entities.Tweet; + +import static java.lang.String.format; +import static junit.framework.TestCase.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestConfiguration.class) +public class DiscardTweetCommandITCase { + + @Autowired + private WebApplicationContext context; + + @Autowired + private TweetRepository tweetRepository; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = webAppContextSetup(this.context).build(); + } + + @Test + public void shouldReturn400WhenDiscardingANullTweet() throws Exception { + mockMvc.perform(discardTweet(null)).andExpect(status().is(400)); + } + + @Test + public void shouldReturn404WhenDiscardingANonExistentTweet() throws Exception { + mockMvc.perform(discardTweet(Long.valueOf("9999"))).andExpect(status().is(404)); + } + + @Test + public void shouldReturn200WhenDiscardingTweet() throws Exception, TweetNotFoundException { + Long tweetId = tweetRepository.publish("Prospect", "Breaking the law"); + mockMvc.perform(discardTweet(tweetId)).andExpect(status().is(200)); + Tweet tweet = tweetRepository.getTweet(tweetId); + assertTrue(tweet.getDiscarded()); + } + + private MockHttpServletRequestBuilder discardTweet(Long tweetId) { + return post("/discarded") + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(format("{\"tweet\": \"%s\"}", tweetId)); + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java index 4e90c52..f877a81 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java @@ -9,30 +9,33 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.context.WebApplicationContext; import com.fasterxml.jackson.databind.ObjectMapper; import com.scmspain.infrastructure.configuration.TestConfiguration; +import com.scmspain.infrastructure.database.TweetRepository; -import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfiguration.class) +@DirtiesContext(classMode = BEFORE_CLASS) public class ListAllTweetsCommandITCase { @Autowired private WebApplicationContext context; + @Autowired + private TweetRepository tweetRepository; + private MockMvc mockMvc; @Before @@ -47,11 +50,6 @@ public void shouldListAllTweetsSortedByPublicationDateDescendingOrder() throws E Map tweet3 = newMapTweet("Third", "Third tweet"); Map tweet4 = newMapTweet("Fourth", "Fourth tweet"); - mockMvc.perform(newTweet(tweet1)).andExpect(status().is(201)); - mockMvc.perform(newTweet(tweet2)).andExpect(status().is(201)); - mockMvc.perform(newTweet(tweet3)).andExpect(status().is(201)); - mockMvc.perform(newTweet(tweet4)).andExpect(status().is(201)); - MvcResult getResult = mockMvc .perform(get("/tweet")) @@ -64,20 +62,11 @@ public void shouldListAllTweetsSortedByPublicationDateDescendingOrder() throws E assertThat(tweets).containsExactly(tweet4, tweet3, tweet2, tweet1); } - private MockHttpServletRequestBuilder newTweet(Map tweet) { - return newTweet(tweet.get("publisher"), tweet.get("tweet")); - } - - private MockHttpServletRequestBuilder newTweet(String publisher, String tweet) { - return post("/tweet") - .contentType(MediaType.APPLICATION_JSON_UTF8) - .content(format("{\"publisher\": \"%s\", \"tweet\": \"%s\"}", publisher, tweet)); - } - private Map newMapTweet(final String publisher, final String tweet) { Map map = new LinkedHashMap<>(); map.put("publisher", publisher); map.put("tweet", tweet); + this.tweetRepository.publish(publisher, tweet); return map; } diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index f39ee8e..3337cb6 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,12 +1,11 @@ package com.scmspain.services; -import com.scmspain.domain.MetricService; -import com.scmspain.application.services.TweetMetricService; -import com.scmspain.application.services.TweetValidationService; -import com.scmspain.domain.TweetService; -import com.scmspain.infrastructure.database.TweetRepository; -import com.scmspain.infrastructure.database.entities.Tweet; -import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; import org.junit.Before; import org.junit.Test; @@ -14,11 +13,20 @@ import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; -import javax.persistence.EntityManager; +import com.scmspain.application.services.TweetMetricService; +import com.scmspain.application.services.TweetValidationService; +import com.scmspain.domain.MetricService; +import com.scmspain.domain.TweetService; +import com.scmspain.domain.model.TweetResponse; +import com.scmspain.infrastructure.database.TweetRepository; +import com.scmspain.infrastructure.database.entities.Tweet; +import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TweetServiceTest { @@ -86,4 +94,38 @@ public void shouldNotCountUrlsForTweetLength() { inOrder.verify(entityManager).persist(any(Tweet.class)); } + @Test + public void something() { + Long idTweet1 = 9997L; + Long idTweet2 = 9998L; + Long idTweet3 = 9999L; + mockEntityManagerQuery(idTweet1, idTweet2, idTweet3); + + Tweet tweet1 = tweet("publisher 9997", "content 9997"); + Tweet tweet3 = tweet("publisher 9999", "content 9999"); + mockEntityManagerFind(idTweet1, tweet1); + mockEntityManagerFind(idTweet2, null); + mockEntityManagerFind(idTweet3, tweet3); + + List tweets = this.tweetService.listAll(); + assertThat(tweets) + .hasSize(2) + .extracting(TweetResponse::getPublisher) + .containsExactly("publisher 9997", "publisher 9999"); + } + + private void mockEntityManagerQuery(Long... idTweet) { + TypedQuery mockedQuery = mock(TypedQuery.class); + when(mockedQuery.getResultList()).thenReturn(Arrays.asList(idTweet)); + when(entityManager.createQuery(any(String.class), any(Class.class))).thenReturn(mockedQuery); + } + + private void mockEntityManagerFind(Long tweetId, Tweet tweet) { + when(entityManager.find(Tweet.class, tweetId)).thenReturn(tweet); + } + + private Tweet tweet(String publisher, String content) { + return new Tweet(publisher, content, new Date()); + } + } From 5a5dc6d68758c4df5ba33004aa78e88cb4ab2048 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Mon, 16 Apr 2018 21:49:15 +0200 Subject: [PATCH 33/43] Rename database package to repository. --- .../configuration/InfrastructureConfiguration.java | 2 +- .../{database => repository}/TweetRepository.java | 4 ++-- .../{database => repository}/entities/Tweet.java | 2 +- .../infrastructure/controller/DiscardTweetCommandITCase.java | 4 ++-- .../infrastructure/controller/ListAllTweetsCommandITCase.java | 2 +- src/test/java/com/scmspain/services/TweetServiceTest.java | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) rename src/main/java/com/scmspain/infrastructure/{database => repository}/TweetRepository.java (96%) rename src/main/java/com/scmspain/infrastructure/{database => repository}/entities/Tweet.java (97%) diff --git a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java index 7c9fa5f..acdda6f 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java @@ -15,7 +15,7 @@ import com.scmspain.domain.command.CommandBus; import com.scmspain.infrastructure.commandbus.Registry; import com.scmspain.infrastructure.commandbus.SpringCommandBus; -import com.scmspain.infrastructure.database.TweetRepository; +import com.scmspain.infrastructure.repository.TweetRepository; import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; /** diff --git a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java similarity index 96% rename from src/main/java/com/scmspain/infrastructure/database/TweetRepository.java rename to src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java index fcfb077..60c5762 100644 --- a/src/main/java/com/scmspain/infrastructure/database/TweetRepository.java +++ b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java @@ -1,4 +1,4 @@ -package com.scmspain.infrastructure.database; +package com.scmspain.infrastructure.repository; import java.util.Date; import java.util.List; @@ -14,7 +14,7 @@ import com.scmspain.domain.TweetService; import com.scmspain.domain.TweetNotFoundException; import com.scmspain.domain.model.TweetResponse; -import com.scmspain.infrastructure.database.entities.Tweet; +import com.scmspain.infrastructure.repository.entities.Tweet; /** * Repository implementation for tweets with an entity manager. diff --git a/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java similarity index 97% rename from src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java rename to src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java index bbe47b1..55e5c60 100644 --- a/src/main/java/com/scmspain/infrastructure/database/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java @@ -1,4 +1,4 @@ -package com.scmspain.infrastructure.database.entities; +package com.scmspain.infrastructure.repository.entities; import java.util.Date; diff --git a/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java index 990c2b6..cc2209d 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java @@ -13,8 +13,8 @@ import com.scmspain.domain.TweetNotFoundException; import com.scmspain.infrastructure.configuration.TestConfiguration; -import com.scmspain.infrastructure.database.TweetRepository; -import com.scmspain.infrastructure.database.entities.Tweet; +import com.scmspain.infrastructure.repository.TweetRepository; +import com.scmspain.infrastructure.repository.entities.Tweet; import static java.lang.String.format; import static junit.framework.TestCase.assertTrue; diff --git a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java index f877a81..e314904 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java @@ -17,7 +17,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.scmspain.infrastructure.configuration.TestConfiguration; -import com.scmspain.infrastructure.database.TweetRepository; +import com.scmspain.infrastructure.repository.TweetRepository; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS; diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 3337cb6..5938f34 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -18,8 +18,8 @@ import com.scmspain.domain.MetricService; import com.scmspain.domain.TweetService; import com.scmspain.domain.model.TweetResponse; -import com.scmspain.infrastructure.database.TweetRepository; -import com.scmspain.infrastructure.database.entities.Tweet; +import com.scmspain.infrastructure.repository.TweetRepository; +import com.scmspain.infrastructure.repository.entities.Tweet; import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; import static org.assertj.core.api.Assertions.assertThat; From 9267b4c2c2c3c7a01207ba1b3acbaca3a559d4b6 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Mon, 16 Apr 2018 21:53:32 +0200 Subject: [PATCH 34/43] Rename test method. --- src/test/java/com/scmspain/services/TweetServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index 5938f34..a2639b0 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -95,7 +95,7 @@ public void shouldNotCountUrlsForTweetLength() { } @Test - public void something() { + public void shouldListAllAvailableTweetsWithInvalidTweetIdentifiers() { Long idTweet1 = 9997L; Long idTweet2 = 9998L; Long idTweet3 = 9999L; From 30911c2068c15c335c8a0be8740c27aa0c7edebb Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Mon, 16 Apr 2018 22:29:58 +0200 Subject: [PATCH 35/43] Discarded tweets will not be shown in the published tweet list. --- .../repository/TweetRepository.java | 2 +- .../ListAllTweetsCommandITCase.java | 46 +++++++++++++++---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java index 60c5762..a16332c 100644 --- a/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java +++ b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java @@ -52,7 +52,7 @@ public void discard(final Long tweetId) throws TweetNotFoundException { public List listAll() { TypedQuery query = this.entityManager - .createQuery("SELECT id FROM Tweet WHERE pre2015MigrationStatus<>99 ORDER BY publicationDate DESC", Long.class); + .createQuery("SELECT id FROM Tweet WHERE pre2015MigrationStatus<>99 AND discarded = false ORDER BY publicationDate DESC", Long.class); return query diff --git a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java index e314904..5c32ee8 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java @@ -1,5 +1,6 @@ package com.scmspain.infrastructure.controller; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -44,11 +45,10 @@ public void setUp() { } @Test - public void shouldListAllTweetsSortedByPublicationDateDescendingOrder() throws Exception { - Map tweet1 = newMapTweet("First", "First tweet"); - Map tweet2 = newMapTweet("Second", "Second tweet"); - Map tweet3 = newMapTweet("Third", "Third tweet"); - Map tweet4 = newMapTweet("Fourth", "Fourth tweet"); + public void shouldListNotDiscardedTweetsSortedByPublicationDateDescendingOrder() throws Exception { + List testTweets = testTweets(); + this.tweetRepository.discard(testTweets.get(1).tweetId); + this.tweetRepository.discard(testTweets.get(3).tweetId); MvcResult getResult = mockMvc @@ -59,15 +59,43 @@ public void shouldListAllTweetsSortedByPublicationDateDescendingOrder() throws E String content = getResult.getResponse().getContentAsString(); List tweets = new ObjectMapper().readValue(content, List.class); - assertThat(tweets).containsExactly(tweet4, tweet3, tweet2, tweet1); + assertThat(tweets).containsExactly( + testTweets.get(5).tweet, + testTweets.get(4).tweet, + testTweets.get(2).tweet, + testTweets.get(0).tweet); } - private Map newMapTweet(final String publisher, final String tweet) { + private List testTweets() { + List tweets = new ArrayList<>(); + tweets.add(newTestTweet("First", "First tweet")); + tweets.add(newTestTweet("Second", "Second tweet")); + tweets.add(newTestTweet("Third", "Third tweet")); + tweets.add(newTestTweet("Fourth", "Fourth tweet")); + tweets.add(newTestTweet("Fifth", "Fifth tweet")); + tweets.add(newTestTweet("Sixth", "Sixth tweet")); + return tweets; + } + + private TestTweet newTestTweet(final String publisher, final String tweet) { + Long tweetId = this.tweetRepository.publish(publisher, tweet); + Map map = new LinkedHashMap<>(); map.put("publisher", publisher); map.put("tweet", tweet); - this.tweetRepository.publish(publisher, tweet); - return map; + + return new TestTweet(tweetId, map); + } + + private static class TestTweet { + + final Long tweetId; + final Map tweet; + + private TestTweet(Long tweetId, Map tweet) { + this.tweetId = tweetId; + this.tweet = tweet; + } } } From a5f54958d2dad8718b0d117fa231d4f80568b7e7 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Mon, 16 Apr 2018 23:10:03 +0200 Subject: [PATCH 36/43] List all discarded tweets command and handler. --- .../services/TweetMetricService.java | 5 + .../services/TweetValidationService.java | 5 + .../com/scmspain/domain/TweetService.java | 9 +- .../ListAllDiscardedTweetsCommand.java | 10 ++ .../ListAllDiscardedTweetsCommandHandler.java | 29 +++++ .../commandbus/CommandProvider.java | 2 +- .../configuration/TweetConfiguration.java | 6 ++ .../controller/TweetController.java | 6 ++ .../repository/TweetRepository.java | 12 ++- .../repository/entities/Tweet.java | 12 +++ .../ListAllDiscardedTweetsCommandITCase.java | 101 ++++++++++++++++++ 11 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/scmspain/domain/command/ListAllDiscardedTweetsCommand.java create mode 100644 src/main/java/com/scmspain/domain/command/ListAllDiscardedTweetsCommandHandler.java create mode 100644 src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java diff --git a/src/main/java/com/scmspain/application/services/TweetMetricService.java b/src/main/java/com/scmspain/application/services/TweetMetricService.java index 7c4efc7..feea5ef 100644 --- a/src/main/java/com/scmspain/application/services/TweetMetricService.java +++ b/src/main/java/com/scmspain/application/services/TweetMetricService.java @@ -43,4 +43,9 @@ public void discard(Long tweetId) throws TweetNotFoundException { tweetService.discard(tweetId); } + @Override + public List listAllDiscarded() { + return tweetService.listAllDiscarded(); + } + } diff --git a/src/main/java/com/scmspain/application/services/TweetValidationService.java b/src/main/java/com/scmspain/application/services/TweetValidationService.java index 85ab0aa..6564c87 100644 --- a/src/main/java/com/scmspain/application/services/TweetValidationService.java +++ b/src/main/java/com/scmspain/application/services/TweetValidationService.java @@ -53,6 +53,11 @@ public void discard(Long tweetId) throws TweetNotFoundException { this.tweetService.discard(tweetId); } + @Override + public List listAllDiscarded() { + return tweetService.listAllDiscarded(); + } + private boolean textEmpty(final String text) { return text == null || text.isEmpty(); } diff --git a/src/main/java/com/scmspain/domain/TweetService.java b/src/main/java/com/scmspain/domain/TweetService.java index 05edacb..7a420d6 100644 --- a/src/main/java/com/scmspain/domain/TweetService.java +++ b/src/main/java/com/scmspain/domain/TweetService.java @@ -10,7 +10,7 @@ public interface TweetService { /** - * List all available tweets. + * List all available tweets sorted by publication date in descending order. * * @return List of tweets. */ @@ -33,4 +33,11 @@ public interface TweetService { */ void discard(Long tweetId) throws TweetNotFoundException; + /** + * List all discarded tweets sorted by the date it was discarded on in descending order.. + * + * @return List of discarded tweets. + */ + List listAllDiscarded(); + } diff --git a/src/main/java/com/scmspain/domain/command/ListAllDiscardedTweetsCommand.java b/src/main/java/com/scmspain/domain/command/ListAllDiscardedTweetsCommand.java new file mode 100644 index 0000000..ab241c6 --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/ListAllDiscardedTweetsCommand.java @@ -0,0 +1,10 @@ +package com.scmspain.domain.command; + +import java.util.List; + +import com.scmspain.domain.model.TweetResponse; + +/** + * Command for list all discarded tweets. + */ +public class ListAllDiscardedTweetsCommand implements Command> { } \ No newline at end of file diff --git a/src/main/java/com/scmspain/domain/command/ListAllDiscardedTweetsCommandHandler.java b/src/main/java/com/scmspain/domain/command/ListAllDiscardedTweetsCommandHandler.java new file mode 100644 index 0000000..3652953 --- /dev/null +++ b/src/main/java/com/scmspain/domain/command/ListAllDiscardedTweetsCommandHandler.java @@ -0,0 +1,29 @@ +package com.scmspain.domain.command; + +import java.util.List; + +import com.scmspain.domain.TweetService; +import com.scmspain.domain.model.TweetResponse; + +/** + * Handler for the list all discarded tweets command. + */ +public class ListAllDiscardedTweetsCommandHandler implements CommandHandler, ListAllDiscardedTweetsCommand> { + + private final TweetService tweetService; + + /** + * Constructor. + * + * @param tweetService Tweet service. + */ + public ListAllDiscardedTweetsCommandHandler(final TweetService tweetService) { + this.tweetService = tweetService; + } + + @Override + public List handle(ListAllDiscardedTweetsCommand command) { + return this.tweetService.listAllDiscarded(); + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/commandbus/CommandProvider.java b/src/main/java/com/scmspain/infrastructure/commandbus/CommandProvider.java index 1a0e3cd..f0e318c 100644 --- a/src/main/java/com/scmspain/infrastructure/commandbus/CommandProvider.java +++ b/src/main/java/com/scmspain/infrastructure/commandbus/CommandProvider.java @@ -30,7 +30,7 @@ class CommandProvider> { * Gets a bean of the proper type from the Spring framework application context. * @return Spring bean. */ - public H get() { + H get() { return applicationContext.getBean(type); } diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 733da45..f619300 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -6,6 +6,7 @@ import com.scmspain.domain.TweetService; import com.scmspain.domain.command.CommandBus; import com.scmspain.domain.command.DiscardTweetCommandHandler; +import com.scmspain.domain.command.ListAllDiscardedTweetsCommandHandler; import com.scmspain.domain.command.ListAllTweetsCommandHandler; import com.scmspain.domain.command.PublishTweetCommandHandler; import com.scmspain.infrastructure.controller.TweetController; @@ -46,4 +47,9 @@ public ListAllTweetsCommandHandler getListAllTweetsCommandHandler(@Qualifier("ma return new ListAllTweetsCommandHandler(tweetService); } + @Bean + public ListAllDiscardedTweetsCommandHandler getListAllDiscardedTweetsCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { + return new ListAllDiscardedTweetsCommandHandler(tweetService); + } + } diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index 4f9fcc7..4c222c9 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -4,6 +4,7 @@ import com.scmspain.domain.command.CommandBus; import com.scmspain.domain.command.CommandException; import com.scmspain.domain.command.DiscardTweetCommand; +import com.scmspain.domain.command.ListAllDiscardedTweetsCommand; import com.scmspain.domain.command.ListAllTweetsCommand; import com.scmspain.domain.command.PublishTweetCommand; import com.scmspain.domain.model.TweetResponse; @@ -49,6 +50,11 @@ public void discardTweet(@RequestBody DiscardTweetCommand discardTweetCommand) t this.commandBus.execute(discardTweetCommand); } + @GetMapping("/discarded") + public List listAllDiscardedTweets() throws CommandException { + return this.commandBus.execute(new ListAllDiscardedTweetsCommand()); + } + @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(BAD_REQUEST) @ResponseBody diff --git a/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java index a16332c..5dfd381 100644 --- a/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java +++ b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java @@ -45,14 +45,24 @@ public Long publish(String publisher, String text) { public void discard(final Long tweetId) throws TweetNotFoundException { Tweet tweet = getTweet(tweetId); tweet.setDiscarded(); + tweet.setDiscardedDate(new Date()); this.entityManager.merge(tweet); } @Override public List listAll() { + return listTweets("SELECT id FROM Tweet WHERE pre2015MigrationStatus<>99 AND discarded = false ORDER BY publicationDate DESC"); + } + + @Override + public List listAllDiscarded() { + return listTweets("SELECT id FROM Tweet WHERE pre2015MigrationStatus<>99 AND discarded = true ORDER BY discardedDate DESC"); + } + + private List listTweets(String qlString) { TypedQuery query = this.entityManager - .createQuery("SELECT id FROM Tweet WHERE pre2015MigrationStatus<>99 AND discarded = false ORDER BY publicationDate DESC", Long.class); + .createQuery(qlString, Long.class); return query diff --git a/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java index 55e5c60..0f0f5d0 100644 --- a/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java @@ -29,6 +29,9 @@ public class Tweet { @Column private Date publicationDate; + @Column + private Date discardedDate; + @Column private Boolean discarded = Boolean.FALSE; @@ -93,4 +96,13 @@ public void setDiscarded() { this.discarded = true; } + /** + * Sets the discarded date. + * + * @param discardedDate Discarded date. + */ + public void setDiscardedDate(Date discardedDate) { + this.discardedDate = discardedDate; + } + } diff --git a/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java new file mode 100644 index 0000000..dc1b365 --- /dev/null +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java @@ -0,0 +1,101 @@ +package com.scmspain.infrastructure.controller; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.web.context.WebApplicationContext; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.scmspain.infrastructure.configuration.TestConfiguration; +import com.scmspain.infrastructure.repository.TweetRepository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestConfiguration.class) +@DirtiesContext(classMode = BEFORE_CLASS) +public class ListAllDiscardedTweetsCommandITCase { + + @Autowired + private WebApplicationContext context; + + @Autowired + private TweetRepository tweetRepository; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = webAppContextSetup(this.context).build(); + } + + @Test + public void shouldListAllDiscardedTweetsSortedByDiscardedDateDescendingOrder() throws Exception { + List testTweets = testTweets(); + this.tweetRepository.discard(testTweets.get(1).tweetId); + this.tweetRepository.discard(testTweets.get(5).tweetId); + this.tweetRepository.discard(testTweets.get(3).tweetId); + + MvcResult getResult = + mockMvc + .perform(get("/discarded")) + .andExpect(status().is(200)) + .andReturn(); + + String content = getResult.getResponse().getContentAsString(); + List tweets = new ObjectMapper().readValue(content, List.class); + + assertThat(tweets).containsExactly( + testTweets.get(3).tweet, + testTweets.get(5).tweet, + testTweets.get(1).tweet); + } + + private List testTweets() { + List tweets = new ArrayList<>(); + tweets.add(newTestTweet("First", "First tweet")); + tweets.add(newTestTweet("Second", "Second tweet")); + tweets.add(newTestTweet("Third", "Third tweet")); + tweets.add(newTestTweet("Fourth", "Fourth tweet")); + tweets.add(newTestTweet("Fifth", "Fifth tweet")); + tweets.add(newTestTweet("Sixth", "Sixth tweet")); + return tweets; + } + + private TestTweet newTestTweet(final String publisher, final String tweet) { + Long tweetId = this.tweetRepository.publish(publisher, tweet); + + Map map = new LinkedHashMap<>(); + map.put("publisher", publisher); + map.put("tweet", tweet); + + return new TestTweet(tweetId, map); + } + + private static class TestTweet { + + final Long tweetId; + final Map tweet; + + private TestTweet(Long tweetId, Map tweet) { + this.tweetId = tweetId; + this.tweet = tweet; + } + } + +} From b040fa06a8f3e0a92448784558f7a75eecd0a55c Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Mon, 16 Apr 2018 23:16:49 +0200 Subject: [PATCH 37/43] Ordering methods. --- .../services/TweetMetricService.java | 12 ++++++------ .../services/TweetValidationService.java | 10 +++++----- .../application/services/UrlRemover.java | 4 ++-- .../com/scmspain/domain/TweetService.java | 14 +++++++------- .../controller/TweetController.java | 19 ++++++++++--------- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/scmspain/application/services/TweetMetricService.java b/src/main/java/com/scmspain/application/services/TweetMetricService.java index feea5ef..481a4ec 100644 --- a/src/main/java/com/scmspain/application/services/TweetMetricService.java +++ b/src/main/java/com/scmspain/application/services/TweetMetricService.java @@ -26,18 +26,18 @@ public TweetMetricService(final TweetService tweetService, final MetricService m this.metricService = metricService; } - @Override - public List listAll() { - metricService.incrementTimesQueriedTweets(); - return tweetService.listAll(); - } - @Override public Long publish(String publisher, String text) { metricService.incrementPublishedTweets(); return tweetService.publish(publisher, text); } + @Override + public List listAll() { + metricService.incrementTimesQueriedTweets(); + return tweetService.listAll(); + } + @Override public void discard(Long tweetId) throws TweetNotFoundException { tweetService.discard(tweetId); diff --git a/src/main/java/com/scmspain/application/services/TweetValidationService.java b/src/main/java/com/scmspain/application/services/TweetValidationService.java index 6564c87..7831113 100644 --- a/src/main/java/com/scmspain/application/services/TweetValidationService.java +++ b/src/main/java/com/scmspain/application/services/TweetValidationService.java @@ -26,11 +26,6 @@ public TweetValidationService(final TweetService tweetService) { this.urlRemover = new UrlRemover(); } - @Override - public List listAll() { - return tweetService.listAll(); - } - @Override public Long publish(final String publisher, final String text) { if (publisherEmpty(publisher)) { @@ -45,6 +40,11 @@ public Long publish(final String publisher, final String text) { return this.tweetService.publish(publisher, text); } + @Override + public List listAll() { + return tweetService.listAll(); + } + @Override public void discard(Long tweetId) throws TweetNotFoundException { if (tweetId == null) { diff --git a/src/main/java/com/scmspain/application/services/UrlRemover.java b/src/main/java/com/scmspain/application/services/UrlRemover.java index 1482a3d..45cc0ba 100644 --- a/src/main/java/com/scmspain/application/services/UrlRemover.java +++ b/src/main/java/com/scmspain/application/services/UrlRemover.java @@ -3,7 +3,7 @@ /** * Remove URLs from a String for some protocols. */ -public class UrlRemover { +class UrlRemover { private static final String WHITE_SPACE = " "; private static final String HTTP_PROTOCOL = "http://"; @@ -15,7 +15,7 @@ public class UrlRemover { * @param text Text. * @return Text without URLs. */ - public String removeUrls(final String text) { + String removeUrls(final String text) { String cleanText = removeUrls(text, HTTP_PROTOCOL); return removeUrls(cleanText, HTTPS_PROTOCOL); } diff --git a/src/main/java/com/scmspain/domain/TweetService.java b/src/main/java/com/scmspain/domain/TweetService.java index 7a420d6..5e58e0c 100644 --- a/src/main/java/com/scmspain/domain/TweetService.java +++ b/src/main/java/com/scmspain/domain/TweetService.java @@ -9,13 +9,6 @@ */ public interface TweetService { - /** - * List all available tweets sorted by publication date in descending order. - * - * @return List of tweets. - */ - List listAll(); - /** * Publish a new tweet. * @@ -25,6 +18,13 @@ public interface TweetService { */ Long publish(String publisher, String text); + /** + * List all available tweets sorted by publication date in descending order. + * + * @return List of tweets. + */ + List listAll(); + /** * Discard a tweet. * diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index 4c222c9..629facd 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -34,17 +34,17 @@ public TweetController(final CommandBus commandBus) { this.commandBus = commandBus; } - @GetMapping("/tweet") - public List listAllTweets() throws CommandException { - return this.commandBus.execute(new ListAllTweetsCommand()); - } - @PostMapping("/tweet") @ResponseStatus(CREATED) public void publishTweet(@RequestBody PublishTweetCommand publishTweetCommand) throws CommandException { this.commandBus.execute(publishTweetCommand); } + @GetMapping("/tweet") + public List listAllTweets() throws CommandException { + return this.commandBus.execute(new ListAllTweetsCommand()); + } + @PostMapping("/discarded") public void discardTweet(@RequestBody DiscardTweetCommand discardTweetCommand) throws CommandException { this.commandBus.execute(discardTweetCommand); @@ -59,16 +59,17 @@ public List listAllDiscardedTweets() throws CommandException { @ResponseStatus(BAD_REQUEST) @ResponseBody public Object invalidArgumentException(IllegalArgumentException ex) { - return new Object() { - public String message = ex.getMessage(); - public String exceptionClass = ex.getClass().getSimpleName(); - }; + return getExceptionObject(ex); } @ExceptionHandler(TweetNotFoundException.class) @ResponseStatus(NOT_FOUND) @ResponseBody public Object tweetNotFoundException(TweetNotFoundException ex) { + return getExceptionObject(ex); + } + + private Object getExceptionObject(Exception ex) { return new Object() { public String message = ex.getMessage(); public String exceptionClass = ex.getClass().getSimpleName(); From b536a90a8dab1e68ddc727e8c70fd341d29d6b26 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Mon, 16 Apr 2018 23:22:24 +0200 Subject: [PATCH 38/43] Define a global exception handler. --- .../configuration/TweetConfiguration.java | 6 +++ .../controller/GlobalExceptionHandler.java | 40 +++++++++++++++++++ .../controller/TweetController.java | 36 ++++------------- 3 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/scmspain/infrastructure/controller/GlobalExceptionHandler.java diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index f619300..8150c52 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -9,6 +9,7 @@ import com.scmspain.domain.command.ListAllDiscardedTweetsCommandHandler; import com.scmspain.domain.command.ListAllTweetsCommandHandler; import com.scmspain.domain.command.PublishTweetCommandHandler; +import com.scmspain.infrastructure.controller.GlobalExceptionHandler; import com.scmspain.infrastructure.controller.TweetController; import org.springframework.beans.factory.annotation.Qualifier; @@ -32,6 +33,11 @@ public TweetController getTweetController(final CommandBus commandBus) { return new TweetController(commandBus); } + @Bean + public GlobalExceptionHandler getGlobalExceptionHandler() { + return new GlobalExceptionHandler(); + } + @Bean public PublishTweetCommandHandler getPublishTweetCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { return new PublishTweetCommandHandler(tweetService); diff --git a/src/main/java/com/scmspain/infrastructure/controller/GlobalExceptionHandler.java b/src/main/java/com/scmspain/infrastructure/controller/GlobalExceptionHandler.java new file mode 100644 index 0000000..990a750 --- /dev/null +++ b/src/main/java/com/scmspain/infrastructure/controller/GlobalExceptionHandler.java @@ -0,0 +1,40 @@ +package com.scmspain.infrastructure.controller; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import com.scmspain.domain.TweetNotFoundException; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +/** + * Controller to manage exceptions. + */ +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(BAD_REQUEST) + @ResponseBody + public Object invalidArgumentException(IllegalArgumentException ex) { + return getExceptionObject(ex); + } + + @ExceptionHandler(TweetNotFoundException.class) + @ResponseStatus(NOT_FOUND) + @ResponseBody + public Object tweetNotFoundException(TweetNotFoundException ex) { + return getExceptionObject(ex); + } + + private Object getExceptionObject(Exception ex) { + return new Object() { + public String message = ex.getMessage(); + public String exceptionClass = ex.getClass().getSimpleName(); + }; + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java index 629facd..9e44815 100644 --- a/src/main/java/com/scmspain/infrastructure/controller/TweetController.java +++ b/src/main/java/com/scmspain/infrastructure/controller/TweetController.java @@ -1,6 +1,13 @@ package com.scmspain.infrastructure.controller; -import com.scmspain.domain.TweetNotFoundException; +import java.util.List; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + import com.scmspain.domain.command.CommandBus; import com.scmspain.domain.command.CommandException; import com.scmspain.domain.command.DiscardTweetCommand; @@ -9,13 +16,7 @@ import com.scmspain.domain.command.PublishTweetCommand; import com.scmspain.domain.model.TweetResponse; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CREATED; -import static org.springframework.http.HttpStatus.NOT_FOUND; /** * Rest controller for tweet API. @@ -55,25 +56,4 @@ public List listAllDiscardedTweets() throws CommandException { return this.commandBus.execute(new ListAllDiscardedTweetsCommand()); } - @ExceptionHandler(IllegalArgumentException.class) - @ResponseStatus(BAD_REQUEST) - @ResponseBody - public Object invalidArgumentException(IllegalArgumentException ex) { - return getExceptionObject(ex); - } - - @ExceptionHandler(TweetNotFoundException.class) - @ResponseStatus(NOT_FOUND) - @ResponseBody - public Object tweetNotFoundException(TweetNotFoundException ex) { - return getExceptionObject(ex); - } - - private Object getExceptionObject(Exception ex) { - return new Object() { - public String message = ex.getMessage(); - public String exceptionClass = ex.getClass().getSimpleName(); - }; - } - } From 01549967c1d6f966e2fdb7cb06df84948b49fcb0 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Mon, 16 Apr 2018 23:42:43 +0200 Subject: [PATCH 39/43] Use Spring magic. --- .../com/scmspain/MsFcTechTestApplication.java | 6 ++--- .../infrastructure/commandbus/Registry.java | 2 ++ .../commandbus/SpringCommandBus.java | 2 ++ .../InfrastructureConfiguration.java | 26 ------------------- .../configuration/TweetConfiguration.java | 17 ++---------- .../metrics/SpringActuatorMetricService.java | 2 ++ .../commandbus/CommandBusITCase.java | 14 +++------- .../CommandBusITCaseConfiguration.java | 25 ------------------ .../commandbus/handler/ByeCommandHandler.java | 2 ++ .../handler/HelloCommandHandler.java | 2 ++ .../commandbus/handler/MessageCollector.java | 3 +++ 11 files changed, 20 insertions(+), 81 deletions(-) delete mode 100644 src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCaseConfiguration.java diff --git a/src/main/java/com/scmspain/MsFcTechTestApplication.java b/src/main/java/com/scmspain/MsFcTechTestApplication.java index e40fb18..77ca812 100644 --- a/src/main/java/com/scmspain/MsFcTechTestApplication.java +++ b/src/main/java/com/scmspain/MsFcTechTestApplication.java @@ -1,15 +1,13 @@ package com.scmspain; -import com.scmspain.infrastructure.configuration.InfrastructureConfiguration; -import com.scmspain.infrastructure.configuration.TweetConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; @Configuration @EnableAutoConfiguration -@Import({TweetConfiguration.class, InfrastructureConfiguration.class}) +@ComponentScan(basePackageClasses = { MsFcTechTestApplication.class }) public class MsFcTechTestApplication { public static void main(String[] args) { diff --git a/src/main/java/com/scmspain/infrastructure/commandbus/Registry.java b/src/main/java/com/scmspain/infrastructure/commandbus/Registry.java index d5fa6c3..78077f7 100644 --- a/src/main/java/com/scmspain/infrastructure/commandbus/Registry.java +++ b/src/main/java/com/scmspain/infrastructure/commandbus/Registry.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.GenericTypeResolver; +import org.springframework.stereotype.Component; import com.scmspain.domain.command.Command; import com.scmspain.domain.command.CommandHandler; @@ -14,6 +15,7 @@ * Basic implementation of a registry with the mapping between commands and handlers based on the Spring framework * application context. */ +@Component public class Registry { private final Map, CommandProvider> providerMap = new HashMap<>(); diff --git a/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java b/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java index 96ebb31..156c30b 100644 --- a/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java +++ b/src/main/java/com/scmspain/infrastructure/commandbus/SpringCommandBus.java @@ -1,6 +1,7 @@ package com.scmspain.infrastructure.commandbus; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import com.scmspain.domain.command.Command; import com.scmspain.domain.command.CommandBus; @@ -10,6 +11,7 @@ /** * Basic implementation of a command bus based on Spring framework application context. */ +@Component public class SpringCommandBus implements CommandBus { private final Registry registry; diff --git a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java index acdda6f..26b6151 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/InfrastructureConfiguration.java @@ -1,48 +1,22 @@ package com.scmspain.infrastructure.configuration; -import javax.persistence.EntityManager; - import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter; import org.springframework.boot.actuate.metrics.jmx.JmxMetricWriter; import org.springframework.boot.actuate.metrics.writer.MetricWriter; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jmx.export.MBeanExporter; -import com.scmspain.domain.MetricService; -import com.scmspain.domain.TweetService; -import com.scmspain.domain.command.CommandBus; -import com.scmspain.infrastructure.commandbus.Registry; -import com.scmspain.infrastructure.commandbus.SpringCommandBus; -import com.scmspain.infrastructure.repository.TweetRepository; -import com.scmspain.infrastructure.metrics.SpringActuatorMetricService; - /** * Configuration for the infrastructure. */ @Configuration public class InfrastructureConfiguration { - @Bean - public CommandBus getCommandBus(final ApplicationContext applicationContext) { - return new SpringCommandBus(new Registry(applicationContext)); - } - @Bean @ExportMetricWriter public MetricWriter getMetricWriter(final MBeanExporter exporter) { return new JmxMetricWriter(exporter); } - @Bean - public MetricService getMetricService(final MetricWriter metricWriter) { - return new SpringActuatorMetricService(metricWriter); - } - - @Bean("tweetRepository") - public TweetService getTweetRepository(final EntityManager entityManager) { - return new TweetRepository(entityManager); - } - } diff --git a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java index 8150c52..3b88237 100644 --- a/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/infrastructure/configuration/TweetConfiguration.java @@ -4,13 +4,10 @@ import com.scmspain.application.services.TweetMetricService; import com.scmspain.application.services.TweetValidationService; import com.scmspain.domain.TweetService; -import com.scmspain.domain.command.CommandBus; import com.scmspain.domain.command.DiscardTweetCommandHandler; import com.scmspain.domain.command.ListAllDiscardedTweetsCommandHandler; import com.scmspain.domain.command.ListAllTweetsCommandHandler; import com.scmspain.domain.command.PublishTweetCommandHandler; -import com.scmspain.infrastructure.controller.GlobalExceptionHandler; -import com.scmspain.infrastructure.controller.TweetController; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; @@ -22,22 +19,12 @@ @Configuration public class TweetConfiguration { - @Bean("mainTweetService") - public TweetService getTweetService(final @Qualifier("tweetRepository") TweetService tweetService, final MetricService metricService) { + @Bean + public TweetService mainTweetService(final @Qualifier("tweetRepository") TweetService tweetService, final MetricService metricService) { TweetService tweetValidationService = new TweetValidationService(tweetService); return new TweetMetricService(tweetValidationService, metricService); } - @Bean - public TweetController getTweetController(final CommandBus commandBus) { - return new TweetController(commandBus); - } - - @Bean - public GlobalExceptionHandler getGlobalExceptionHandler() { - return new GlobalExceptionHandler(); - } - @Bean public PublishTweetCommandHandler getPublishTweetCommandHandler(@Qualifier("mainTweetService") final TweetService tweetService) { return new PublishTweetCommandHandler(tweetService); diff --git a/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java b/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java index 9ed50ce..4a7baf3 100644 --- a/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java +++ b/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java @@ -2,12 +2,14 @@ import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; +import org.springframework.stereotype.Service; import com.scmspain.domain.MetricService; /** * Metric service implementation based on spring actuator metric writer. */ +@Service public class SpringActuatorMetricService implements MetricService { private static final String PUBLISHED_TWEETS = "published-tweets"; diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java b/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java index 71368c9..8b72b91 100644 --- a/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java +++ b/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCase.java @@ -4,28 +4,20 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import com.scmspain.domain.command.CommandBus; import com.scmspain.domain.command.CommandException; import com.scmspain.infrastructure.commandbus.command.ByeCommand; import com.scmspain.infrastructure.commandbus.command.HelloCommand; -import com.scmspain.infrastructure.commandbus.handler.ByeCommandHandler; -import com.scmspain.infrastructure.commandbus.handler.HelloCommandHandler; import com.scmspain.infrastructure.commandbus.handler.MessageCollector; +import com.scmspain.infrastructure.configuration.TestConfiguration; import static org.assertj.core.api.Assertions.assertThat; -@ContextConfiguration(classes = { - HelloCommandHandler.class, - ByeCommandHandler.class, - MessageCollector.class, - Registry.class -}) -@Import(CommandBusITCaseConfiguration.class) @RunWith(SpringRunner.class) +@SpringBootTest(classes = TestConfiguration.class) public class CommandBusITCase { @Autowired diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCaseConfiguration.java b/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCaseConfiguration.java deleted file mode 100644 index 96b0dbc..0000000 --- a/src/test/java/com/scmspain/infrastructure/commandbus/CommandBusITCaseConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.scmspain.infrastructure.commandbus; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.jmx.export.MBeanExporter; -import org.springframework.test.context.ContextConfiguration; - -import com.scmspain.MsFcTechTestApplication; -import com.scmspain.domain.command.CommandBus; -import com.scmspain.infrastructure.commandbus.handler.ByeCommandHandler; -import com.scmspain.infrastructure.commandbus.handler.HelloCommandHandler; -import com.scmspain.infrastructure.commandbus.handler.MessageCollector; - -import static org.mockito.Mockito.mock; - -@Configuration -public class CommandBusITCaseConfiguration { - - @Bean - public CommandBus getCommandBus(Registry registry) { - return new SpringCommandBus(registry); - } - -} diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/handler/ByeCommandHandler.java b/src/test/java/com/scmspain/infrastructure/commandbus/handler/ByeCommandHandler.java index 806d40d..dd01e70 100644 --- a/src/test/java/com/scmspain/infrastructure/commandbus/handler/ByeCommandHandler.java +++ b/src/test/java/com/scmspain/infrastructure/commandbus/handler/ByeCommandHandler.java @@ -1,10 +1,12 @@ package com.scmspain.infrastructure.commandbus.handler; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import com.scmspain.domain.command.CommandHandler; import com.scmspain.infrastructure.commandbus.command.ByeCommand; +@Component public class ByeCommandHandler implements CommandHandler { private MessageCollector messageCollector; diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/handler/HelloCommandHandler.java b/src/test/java/com/scmspain/infrastructure/commandbus/handler/HelloCommandHandler.java index c5a873e..420daff 100644 --- a/src/test/java/com/scmspain/infrastructure/commandbus/handler/HelloCommandHandler.java +++ b/src/test/java/com/scmspain/infrastructure/commandbus/handler/HelloCommandHandler.java @@ -1,10 +1,12 @@ package com.scmspain.infrastructure.commandbus.handler; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import com.scmspain.domain.command.CommandHandler; import com.scmspain.infrastructure.commandbus.command.HelloCommand; +@Component public class HelloCommandHandler implements CommandHandler { private MessageCollector messageCollector; diff --git a/src/test/java/com/scmspain/infrastructure/commandbus/handler/MessageCollector.java b/src/test/java/com/scmspain/infrastructure/commandbus/handler/MessageCollector.java index d3693f4..f79fdde 100644 --- a/src/test/java/com/scmspain/infrastructure/commandbus/handler/MessageCollector.java +++ b/src/test/java/com/scmspain/infrastructure/commandbus/handler/MessageCollector.java @@ -3,6 +3,9 @@ import java.util.ArrayList; import java.util.List; +import org.springframework.stereotype.Component; + +@Component public class MessageCollector { private List messages = new ArrayList<>(); From 647f15b02058408e8d568e198da23750696d4243 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Mon, 16 Apr 2018 23:47:27 +0200 Subject: [PATCH 40/43] Use HttpStatus constants instead of magic numbers. --- .../controller/DiscardTweetCommandITCase.java | 6 +++--- .../controller/ListAllDiscardedTweetsCommandITCase.java | 3 ++- .../controller/ListAllTweetsCommandITCase.java | 3 ++- .../controller/PublishTweetCommandITCase.java | 7 ++++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java index cc2209d..070a0e6 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/DiscardTweetCommandITCase.java @@ -5,13 +5,13 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.context.WebApplicationContext; -import com.scmspain.domain.TweetNotFoundException; import com.scmspain.infrastructure.configuration.TestConfiguration; import com.scmspain.infrastructure.repository.TweetRepository; import com.scmspain.infrastructure.repository.entities.Tweet; @@ -50,9 +50,9 @@ public void shouldReturn404WhenDiscardingANonExistentTweet() throws Exception { } @Test - public void shouldReturn200WhenDiscardingTweet() throws Exception, TweetNotFoundException { + public void shouldReturn200WhenDiscardingTweet() throws Exception { Long tweetId = tweetRepository.publish("Prospect", "Breaking the law"); - mockMvc.perform(discardTweet(tweetId)).andExpect(status().is(200)); + mockMvc.perform(discardTweet(tweetId)).andExpect(status().is(HttpStatus.OK.value())); Tweet tweet = tweetRepository.getTweet(tweetId); assertTrue(tweet.getDiscarded()); } diff --git a/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java index dc1b365..86d3c8f 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java @@ -10,6 +10,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; @@ -54,7 +55,7 @@ public void shouldListAllDiscardedTweetsSortedByDiscardedDateDescendingOrder() t MvcResult getResult = mockMvc .perform(get("/discarded")) - .andExpect(status().is(200)) + .andExpect(status().is(HttpStatus.OK.value())) .andReturn(); String content = getResult.getResponse().getContentAsString(); diff --git a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java index 5c32ee8..5440f6a 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java @@ -10,6 +10,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; @@ -53,7 +54,7 @@ public void shouldListNotDiscardedTweetsSortedByPublicationDateDescendingOrder() MvcResult getResult = mockMvc .perform(get("/tweet")) - .andExpect(status().is(200)) + .andExpect(status().is(HttpStatus.OK.value())) .andReturn(); String content = getResult.getResponse().getContentAsString(); diff --git a/src/test/java/com/scmspain/infrastructure/controller/PublishTweetCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/PublishTweetCommandITCase.java index 1488b33..5553408 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/PublishTweetCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/PublishTweetCommandITCase.java @@ -5,6 +5,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; @@ -36,21 +37,21 @@ public void setUp() { public void shouldReturn200WhenInsertingAValidTweet() throws Exception { mockMvc .perform(newTweet("Prospect", "Breaking the law")) - .andExpect(status().is(201)); + .andExpect(status().is(HttpStatus.CREATED.value())); } @Test public void shouldReturn200WhenInsertingAValidTweetWithUrls() throws Exception { mockMvc .perform(newTweet("Prospect", "Breaking the law: http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com http://www.judaspriest.com")) - .andExpect(status().is(201)); + .andExpect(status().is(HttpStatus.CREATED.value())); } @Test public void shouldReturn400WhenInsertingAnInvalidTweet() throws Exception { mockMvc .perform(newTweet("Schibsted Spain", "We are Schibsted Spain (look at our home page http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome! Text added to make it fail.")) - .andExpect(status().is(400)); + .andExpect(status().is(HttpStatus.BAD_REQUEST.value())); } private MockHttpServletRequestBuilder newTweet(String publisher, String tweet) { From 0fda3c980bae0761a91f8d56060880a12c20ce36 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Tue, 17 Apr 2018 00:22:36 +0200 Subject: [PATCH 41/43] Model URLs at database. --- .../application/services/UrlExtractor.java | 73 +++++++++++++++++++ .../repository/TweetRepository.java | 22 +++++- .../repository/entities/Tweet.java | 19 ++++- .../repository/entities/TweetUrl.java | 50 +++++++++++++ .../ListAllTweetsCommandITCase.java | 12 +-- 5 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/scmspain/application/services/UrlExtractor.java create mode 100644 src/main/java/com/scmspain/infrastructure/repository/entities/TweetUrl.java diff --git a/src/main/java/com/scmspain/application/services/UrlExtractor.java b/src/main/java/com/scmspain/application/services/UrlExtractor.java new file mode 100644 index 0000000..ba0442f --- /dev/null +++ b/src/main/java/com/scmspain/application/services/UrlExtractor.java @@ -0,0 +1,73 @@ +package com.scmspain.application.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Extract URLs from a String for some protocols. + */ +public class UrlExtractor { + + private static final String WHITE_SPACE = " "; + private static final String HTTP_PROTOCOL = "http://"; + private static final String HTTPS_PROTOCOL = "https://"; + + private String text; + private List urls = new ArrayList<>(); + + public UrlExtractor(final String text) { + this.text = text; + this.extractUrls(); + } + + public String getText() { + return text; + } + + public List getUrls() { + return urls; + } + + private void extractUrls() { + extractUrls(HTTP_PROTOCOL); + extractUrls(HTTPS_PROTOCOL); + } + + private void extractUrls(final String protocol) { + Optional url = extractUrl(protocol); + if (url.isPresent()) { + text = text.replace(url.get(), getEscapeIndex(urls.size())); + urls.add(url.get()); + extractUrl(protocol); + } + } + + private Optional extractUrl(String protocol) { + int urlStart = text.indexOf(protocol); + if (urlStart != -1) { + int urlEnd = text.indexOf(WHITE_SPACE, urlStart); + if (urlEnd != -1) { + return Optional.of(text.substring(urlStart, urlEnd)); + } else { + return Optional.of(text.substring(urlStart)); + } + } + return Optional.empty(); + } + + public static String rebuild(String text, List urls) { + for (int i = 0; i < urls.size(); i++) { + String target = getEscapeIndex(i); + while (text.contains(target)) { + text = text.replace(target, urls.get(i)); + } + } + return text; + } + + private static String getEscapeIndex(int index) { + return "{" + index + "}"; + } + +} diff --git a/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java index 5dfd381..19ddc73 100644 --- a/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java +++ b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java @@ -11,10 +11,12 @@ import org.springframework.stereotype.Repository; +import com.scmspain.application.services.UrlExtractor; import com.scmspain.domain.TweetService; import com.scmspain.domain.TweetNotFoundException; import com.scmspain.domain.model.TweetResponse; import com.scmspain.infrastructure.repository.entities.Tweet; +import com.scmspain.infrastructure.repository.entities.TweetUrl; /** * Repository implementation for tweets with an entity manager. @@ -36,7 +38,11 @@ public TweetRepository(EntityManager entityManager) { @Override public Long publish(String publisher, String text) { - Tweet tweet = new Tweet(publisher, text, new Date()); + UrlExtractor urlExtractor = new UrlExtractor(text); + Tweet tweet = new Tweet(publisher, urlExtractor.getText(), new Date()); + for (String url : urlExtractor.getUrls()) { + tweet.getUrls().add(new TweetUrl(tweet, url)); + } this.entityManager.persist(tweet); return tweet.getId(); } @@ -83,7 +89,8 @@ private List listTweets(String qlString) { private Optional getTweetResponse(final Long id) { try { Tweet tweet = getTweet(id); - return Optional.of(new TweetResponse(tweet.getPublisher(), tweet.getText())); + String text = rebuildText(tweet); + return Optional.of(new TweetResponse(tweet.getPublisher(), text)); } catch (TweetNotFoundException e) { return Optional.empty(); } @@ -103,4 +110,15 @@ public Tweet getTweet(final Long id) throws TweetNotFoundException { return tweet; } + private String rebuildText(Tweet tweet) { + List urls = + tweet + .getUrls() + .stream() + .map(TweetUrl::getUrl) + .collect(Collectors.toList()); + + return UrlExtractor.rebuild(tweet.getText(), urls); + } + } diff --git a/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java index 0f0f5d0..a9b5c63 100644 --- a/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java @@ -1,11 +1,16 @@ package com.scmspain.infrastructure.repository.entities; +import java.util.ArrayList; import java.util.Date; +import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.OneToMany; /** * Tweet entity. @@ -20,7 +25,7 @@ public class Tweet { @Column(nullable = false) private String publisher; - @Column(name = "tweet", nullable = false, length = 2000) + @Column(name = "tweet", nullable = false) private String text; @Column @@ -35,6 +40,9 @@ public class Tweet { @Column private Boolean discarded = Boolean.FALSE; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "tweet", fetch = FetchType.EAGER) + private List urls = new ArrayList<>(); + /** * Constructor to help the persistence framework to instantiate the entity. */ @@ -105,4 +113,13 @@ public void setDiscardedDate(Date discardedDate) { this.discardedDate = discardedDate; } + /** + * Gets the URLs of the tweet. + * + * @return List of URLs of the tweet. + */ + public List getUrls() { + return urls; + } + } diff --git a/src/main/java/com/scmspain/infrastructure/repository/entities/TweetUrl.java b/src/main/java/com/scmspain/infrastructure/repository/entities/TweetUrl.java new file mode 100644 index 0000000..36461cc --- /dev/null +++ b/src/main/java/com/scmspain/infrastructure/repository/entities/TweetUrl.java @@ -0,0 +1,50 @@ +package com.scmspain.infrastructure.repository.entities; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * Tweet URL entity. + */ +@Entity +public class TweetUrl { + + @Id + @GeneratedValue + private Long id; + + @Column + private String url; + + @ManyToOne + private Tweet tweet; + + /** + * Constructor to help the persistence framework to instantiate the entity. + */ + private TweetUrl() { } + + /** + * Constructor with parameters. + * + * @param tweet Tweet that owns the URL. + * @param url URL included at a tweet. + */ + public TweetUrl(final Tweet tweet, final String url) { + this.url = url; + this.tweet = tweet; + } + + /** + * Gets an URL included at a tweet. + * + * @return URL. + */ + public String getUrl() { + return url; + } + +} diff --git a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java index 5440f6a..0f81d37 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java @@ -69,12 +69,12 @@ public void shouldListNotDiscardedTweetsSortedByPublicationDateDescendingOrder() private List testTweets() { List tweets = new ArrayList<>(); - tweets.add(newTestTweet("First", "First tweet")); - tweets.add(newTestTweet("Second", "Second tweet")); - tweets.add(newTestTweet("Third", "Third tweet")); - tweets.add(newTestTweet("Fourth", "Fourth tweet")); - tweets.add(newTestTweet("Fifth", "Fifth tweet")); - tweets.add(newTestTweet("Sixth", "Sixth tweet")); + tweets.add(newTestTweet("First", "First tweet https://www.google.com")); + tweets.add(newTestTweet("Second", "Second https://www.google.com tweet")); + tweets.add(newTestTweet("Third", "https://www.google.com Third tweet")); + tweets.add(newTestTweet("Fourth", "Fourth tweet http://www.google.com")); + tweets.add(newTestTweet("Fifth", "Fifth http://www.google.com tweet")); + tweets.add(newTestTweet("Sixth", "http://www.google.com Sixth tweet")); return tweets; } From 9e99e57a0fa2a574ff0a0991af6683b83cb96a0f Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Tue, 17 Apr 2018 09:57:16 +0200 Subject: [PATCH 42/43] The response body format should be identical to the published tweets GET /tweet endpoint at initial commit. --- .../scmspain/domain/model/TweetResponse.java | 27 ++++++++++++++++--- .../repository/TweetRepository.java | 2 +- .../repository/entities/Tweet.java | 9 +++++++ .../ListAllDiscardedTweetsCommandITCase.java | 8 +++--- .../ListAllTweetsCommandITCase.java | 8 +++--- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/scmspain/domain/model/TweetResponse.java b/src/main/java/com/scmspain/domain/model/TweetResponse.java index 39850e1..743e474 100644 --- a/src/main/java/com/scmspain/domain/model/TweetResponse.java +++ b/src/main/java/com/scmspain/domain/model/TweetResponse.java @@ -1,24 +1,36 @@ package com.scmspain.domain.model; -import java.util.Date; - /** * Tweet response. */ public class TweetResponse { + private final Long id; private final String publisher; private final String tweet; + private final Long pre2015MigrationStatus; /** * Constructor. * + * @param id Identifier of the tweet. * @param publisher Creator of the tweet. * @param tweet Content of the tweet. */ - public TweetResponse(final String publisher, final String tweet) { + public TweetResponse(final Long id, final String publisher, final String tweet, final Long pre2015MigrationStatus) { + this.id = id; this.publisher = publisher; this.tweet = tweet; + this.pre2015MigrationStatus = pre2015MigrationStatus; + } + + /** + * Gets the identifier of the tweet. + * + * @return Identifier of the tweet. + */ + public Long getId() { + return this.id; } /** @@ -39,4 +51,13 @@ public String getTweet() { return tweet; } + /** + * Gets the pre 2015 migration status. + * + * @return Pre 2015 migration status. + */ + public Long getPre2015MigrationStatus() { + return pre2015MigrationStatus; + } + } diff --git a/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java index 19ddc73..4b4b9b4 100644 --- a/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java +++ b/src/main/java/com/scmspain/infrastructure/repository/TweetRepository.java @@ -90,7 +90,7 @@ private Optional getTweetResponse(final Long id) { try { Tweet tweet = getTweet(id); String text = rebuildText(tweet); - return Optional.of(new TweetResponse(tweet.getPublisher(), text)); + return Optional.of(new TweetResponse(id, tweet.getPublisher(), text, tweet.getPre2015MigrationStatus())); } catch (TweetNotFoundException e) { return Optional.empty(); } diff --git a/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java b/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java index a9b5c63..0436a8c 100644 --- a/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java +++ b/src/main/java/com/scmspain/infrastructure/repository/entities/Tweet.java @@ -88,6 +88,15 @@ public String getText() { return text; } + /** + * Gets the pre 2015 migration status. + * + * @return Pre 2015 migration status. + */ + public Long getPre2015MigrationStatus() { + return pre2015MigrationStatus; + } + /** * Gets the disabled mark or the tweet. * diff --git a/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java index 86d3c8f..94962d4 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllDiscardedTweetsCommandITCase.java @@ -81,9 +81,11 @@ private List testTweets() { private TestTweet newTestTweet(final String publisher, final String tweet) { Long tweetId = this.tweetRepository.publish(publisher, tweet); - Map map = new LinkedHashMap<>(); + Map map = new LinkedHashMap<>(); + map.put("id", tweetId.intValue()); map.put("publisher", publisher); map.put("tweet", tweet); + map.put("pre2015MigrationStatus", 0); return new TestTweet(tweetId, map); } @@ -91,9 +93,9 @@ private TestTweet newTestTweet(final String publisher, final String tweet) { private static class TestTweet { final Long tweetId; - final Map tweet; + final Map tweet; - private TestTweet(Long tweetId, Map tweet) { + private TestTweet(Long tweetId, Map tweet) { this.tweetId = tweetId; this.tweet = tweet; } diff --git a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java index 0f81d37..1838bba 100644 --- a/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java +++ b/src/test/java/com/scmspain/infrastructure/controller/ListAllTweetsCommandITCase.java @@ -81,9 +81,11 @@ private List testTweets() { private TestTweet newTestTweet(final String publisher, final String tweet) { Long tweetId = this.tweetRepository.publish(publisher, tweet); - Map map = new LinkedHashMap<>(); + Map map = new LinkedHashMap<>(); + map.put("id", tweetId.intValue()); map.put("publisher", publisher); map.put("tweet", tweet); + map.put("pre2015MigrationStatus", 0); return new TestTweet(tweetId, map); } @@ -91,9 +93,9 @@ private TestTweet newTestTweet(final String publisher, final String tweet) { private static class TestTweet { final Long tweetId; - final Map tweet; + final Map tweet; - private TestTweet(Long tweetId, Map tweet) { + private TestTweet(Long tweetId, Map tweet) { this.tweetId = tweetId; this.tweet = tweet; } From 1eff55bbbbbe618bbd92597714698786266e18b8 Mon Sep 17 00:00:00 2001 From: ivan-perez Date: Thu, 19 Apr 2018 09:44:57 +0200 Subject: [PATCH 43/43] Adding missing metrics and improving metrics verifications. There are more missing verifications and tests to do, but I wanted to include this at the pull-request. --- .../application/services/TweetMetricService.java | 2 ++ src/main/java/com/scmspain/domain/MetricService.java | 10 ++++++++++ .../metrics/SpringActuatorMetricService.java | 12 ++++++++++++ .../java/com/scmspain/services/TweetServiceTest.java | 12 +++++++++++- 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/scmspain/application/services/TweetMetricService.java b/src/main/java/com/scmspain/application/services/TweetMetricService.java index 481a4ec..d498679 100644 --- a/src/main/java/com/scmspain/application/services/TweetMetricService.java +++ b/src/main/java/com/scmspain/application/services/TweetMetricService.java @@ -40,11 +40,13 @@ public List listAll() { @Override public void discard(Long tweetId) throws TweetNotFoundException { + metricService.incrementDiscardedTweets(); tweetService.discard(tweetId); } @Override public List listAllDiscarded() { + metricService.incrementTimesQueriedDiscardedTweets(); return tweetService.listAllDiscarded(); } diff --git a/src/main/java/com/scmspain/domain/MetricService.java b/src/main/java/com/scmspain/domain/MetricService.java index 62743d5..a351e5f 100644 --- a/src/main/java/com/scmspain/domain/MetricService.java +++ b/src/main/java/com/scmspain/domain/MetricService.java @@ -15,4 +15,14 @@ public interface MetricService { */ void incrementTimesQueriedTweets(); + /** + * Increment by one the discarded tweets metrics. + */ + void incrementDiscardedTweets(); + + /** + * Increment by one the times queried discarded tweets metrics. + */ + void incrementTimesQueriedDiscardedTweets(); + } diff --git a/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java b/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java index 4a7baf3..f2c2977 100644 --- a/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java +++ b/src/main/java/com/scmspain/infrastructure/metrics/SpringActuatorMetricService.java @@ -14,6 +14,8 @@ public class SpringActuatorMetricService implements MetricService { private static final String PUBLISHED_TWEETS = "published-tweets"; private static final String TIMES_QUERIED_TWEETS = "times-queried-tweets"; + private static final String DISCARDED_TWEETS = "discarded-tweets"; + private static final String TIMES_QUERIED_DISCARDED_TWEETS = "times-queried-discarded-tweets"; private final MetricWriter metricWriter; @@ -36,6 +38,16 @@ public void incrementTimesQueriedTweets() { metricWriter.increment(deltaOne(TIMES_QUERIED_TWEETS)); } + @Override + public void incrementDiscardedTweets() { + metricWriter.increment(deltaOne(DISCARDED_TWEETS)); + } + + @Override + public void incrementTimesQueriedDiscardedTweets() { + metricWriter.increment(deltaOne(TIMES_QUERIED_DISCARDED_TWEETS)); + } + private Delta deltaOne(String name) { return new Delta<>(name, 1); } diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java index a2639b0..5428b18 100644 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -9,6 +9,7 @@ import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; @@ -26,6 +27,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class TweetServiceTest { @@ -90,7 +92,10 @@ public void shouldThrowAnExceptionWhenTweetTextLengthIsInvalid() { public void shouldNotCountUrlsForTweetLength() { this.tweetService.publish(GUYBRUSH, VALID_MESSAGE_WITH_URLS); InOrder inOrder = inOrder(metricWriter, entityManager); - inOrder.verify(metricWriter).increment(any(Delta.class)); + ArgumentCaptor deltaArgument = ArgumentCaptor.forClass(Delta.class); + inOrder.verify(metricWriter).increment(deltaArgument.capture()); + assertThat("published-tweets").isEqualTo(deltaArgument.getValue().getName()); + assertThat(1).isEqualTo(deltaArgument.getValue().getValue()); inOrder.verify(entityManager).persist(any(Tweet.class)); } @@ -112,6 +117,11 @@ public void shouldListAllAvailableTweetsWithInvalidTweetIdentifiers() { .hasSize(2) .extracting(TweetResponse::getPublisher) .containsExactly("publisher 9997", "publisher 9999"); + + ArgumentCaptor deltaArgument = ArgumentCaptor.forClass(Delta.class); + verify(metricWriter).increment(deltaArgument.capture()); + assertThat("times-queried-tweets").isEqualTo(deltaArgument.getValue().getName()); + assertThat(1).isEqualTo(deltaArgument.getValue().getValue()); } private void mockEntityManagerQuery(Long... idTweet) {