├── .gitignore
├── 01-refactoring-smelly-mars-rover
├── README.md
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── mars_rover
│ │ └── Rover.java
│ └── test
│ └── java
│ └── mars_rover
│ ├── RoverEqualityTests.java
│ ├── RoverPositionTests.java
│ ├── RoverReceivingCommandsListTests.java
│ └── RoverRotationTests.java
├── 02-refactoring-video-store
├── .gitignore
├── README.md
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── video_store
│ │ ├── Customer.java
│ │ ├── Movie.java
│ │ └── Rental.java
│ └── test
│ └── java
│ └── video_store
│ └── unit_tests
│ └── VideoStoreTest.java
├── 03-refactoring-to-hexagonal-architecture
├── pom.xml
├── readme.md
└── src
│ ├── main
│ └── java
│ │ └── birthdaygreetings
│ │ ├── BirthdayService.java
│ │ ├── Employee.java
│ │ └── OurDate.java
│ └── test
│ ├── java
│ └── birthdaygreetings
│ │ └── test
│ │ ├── AcceptanceTest.java
│ │ ├── EmployeeTest.java
│ │ └── OurDateTest.java
│ └── resources
│ └── employee_data.txt
├── 04-refactoring-to-hexagonal-architecture-2
├── pom.xml
├── readme.md
└── src
│ ├── main
│ └── java
│ │ └── birthdaygreetings
│ │ ├── application
│ │ ├── BirthdayGreetingsService.java
│ │ └── Main.java
│ │ ├── core
│ │ ├── CannotReadEmployeesException.java
│ │ ├── Employee.java
│ │ ├── EmployeesRepository.java
│ │ ├── Greeting.java
│ │ ├── GreetingMessage.java
│ │ └── OurDate.java
│ │ └── infrastructure
│ │ └── repositories
│ │ ├── DateRepresentation.java
│ │ ├── EmployeesFile.java
│ │ └── FileEmployeesRepository.java
│ └── test
│ ├── java
│ └── test
│ │ └── birthdaygreetings
│ │ ├── application
│ │ └── BirthdayGreetingsServiceAcceptanceTest.java
│ │ ├── core
│ │ ├── EmployeeTest.java
│ │ └── OurDateTest.java
│ │ ├── helpers
│ │ └── OurDateFactory.java
│ │ └── infrastructure
│ │ └── repositories
│ │ └── FileEmployeeRepositoryTest.java
│ └── resources
│ ├── contains-employee-with-wrongly-formatted-birthdate.csv
│ └── employee_data.csv
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 | .classpath
3 | .project
4 | .idea
5 | *.iml
6 | target
--------------------------------------------------------------------------------
/01-refactoring-smelly-mars-rover/README.md:
--------------------------------------------------------------------------------
1 | Smelly Mars Rover code refactoring
2 | =============================================
3 |
4 | Smelly Mars Rover code to practice refactoring.
5 |
6 | We'll use it to learn to recognize some code smells
7 | and to fix them applying some useful refactorings.
--------------------------------------------------------------------------------
/01-refactoring-smelly-mars-rover/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.codesai.katas-java
5 | smelly-mars-rover-refactoring
6 | 0.0.1-SNAPSHOT
7 |
8 |
9 | 11
10 | 11
11 |
12 |
13 |
14 |
15 | org.hamcrest
16 | hamcrest
17 | 2.2
18 | test
19 |
20 |
21 | org.junit.jupiter
22 | junit-jupiter
23 | 5.9.0
24 | test
25 |
26 |
27 | org.junit.jupiter
28 | junit-jupiter-engine
29 | 5.9.0
30 | test
31 |
32 |
33 |
34 |
35 |
36 | org.apache.maven.plugins
37 | maven-surefire-plugin
38 | 2.22.2
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/01-refactoring-smelly-mars-rover/src/main/java/mars_rover/Rover.java:
--------------------------------------------------------------------------------
1 | package mars_rover;
2 |
3 | public class Rover {
4 |
5 | private String direction;
6 | private int y;
7 | private int x;
8 |
9 | public Rover(int x, int y, String direction) {
10 | this.direction = direction;
11 | this.y = y;
12 | this.x = x;
13 | }
14 |
15 | public void receive(String commandsSequence) {
16 | for (int i = 0; i < commandsSequence.length(); ++i) {
17 | String command = commandsSequence.substring(i, i + 1);
18 |
19 | if (command.equals("l") || command.equals("r")) {
20 |
21 | // Rotate Rover
22 | if (direction.equals("N")) {
23 | if (command.equals("r")) {
24 | direction = "E";
25 | } else {
26 | direction = "W";
27 | }
28 | } else if (direction.equals("S")) {
29 | if (command.equals("r")) {
30 | direction = "W";
31 | } else {
32 | direction = "E";
33 | }
34 | } else if (direction.equals("W")) {
35 | if (command.equals("r")) {
36 | direction = "N";
37 | } else {
38 | direction = "S";
39 | }
40 | } else {
41 | if (command.equals("r")) {
42 | direction = "S";
43 | } else {
44 | direction = "N";
45 | }
46 | }
47 | } else {
48 |
49 | // Displace Rover
50 | int displacement1 = -1;
51 |
52 | if (command.equals("f")) {
53 | displacement1 = 1;
54 | }
55 | int displacement = displacement1;
56 |
57 | if (direction.equals("N")) {
58 | y += displacement;
59 | } else if (direction.equals("S")) {
60 | y -= displacement;
61 | } else if (direction.equals("W")) {
62 | x -= displacement;
63 | } else {
64 | x += displacement;
65 | }
66 | }
67 | }
68 | }
69 |
70 | @Override
71 | public boolean equals(Object o) {
72 | if (this == o) return true;
73 | if (o == null || getClass() != o.getClass()) return false;
74 |
75 | Rover rover = (Rover) o;
76 |
77 | if (y != rover.y) return false;
78 | if (x != rover.x) return false;
79 | return direction != null ? direction.equals(rover.direction) : rover.direction == null;
80 |
81 | }
82 |
83 | @Override
84 | public int hashCode() {
85 | int result = direction != null ? direction.hashCode() : 0;
86 | result = 31 * result + y;
87 | result = 31 * result + x;
88 | return result;
89 | }
90 |
91 | @Override
92 | public String toString() {
93 | return "Rover{" +
94 | "direction='" + direction + '\'' +
95 | ", y=" + y +
96 | ", x=" + x +
97 | '}';
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/01-refactoring-smelly-mars-rover/src/test/java/mars_rover/RoverEqualityTests.java:
--------------------------------------------------------------------------------
1 | package mars_rover;
2 |
3 | import static org.junit.jupiter.api.Assertions.*;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | public class RoverEqualityTests {
8 | @Test
9 | public void equalRovers() {
10 | assertEquals(new Rover(1, 1, "N"), new Rover(1, 1, "N"));
11 | }
12 |
13 | @Test
14 | public void notEqualRovers() {
15 | assertNotEquals(new Rover(1, 1, "N"), new Rover(1, 1, "S"));
16 | assertNotEquals(new Rover(1, 1, "N"), new Rover(1, 2, "N"));
17 | assertNotEquals(new Rover(1, 1, "N"), new Rover(0, 1, "N"));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/01-refactoring-smelly-mars-rover/src/test/java/mars_rover/RoverPositionTests.java:
--------------------------------------------------------------------------------
1 | package mars_rover;
2 |
3 | import static org.junit.jupiter.api.Assertions.*;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | public class RoverPositionTests {
8 | @Test
9 | public void facingNorthMoveForward() {
10 | Rover rover = new Rover(0, 0, "N");
11 |
12 | rover.receive("f");
13 |
14 | assertEquals(new Rover(0, 1, "N"), rover);
15 | }
16 |
17 | @Test
18 | public void facingNorthMoveBackward() {
19 | Rover rover = new Rover(0, 0, "N");
20 |
21 | rover.receive("b");
22 |
23 | assertEquals(new Rover(0, -1, "N"), rover);
24 | }
25 |
26 | @Test
27 | public void facingSouthMoveForward() {
28 | Rover rover = new Rover(0, 0, "S");
29 |
30 | rover.receive("f");
31 |
32 | assertEquals(new Rover(0, -1, "S"), rover);
33 | }
34 |
35 | @Test
36 | public void facingSouthMoveBackward() {
37 | Rover rover = new Rover(0, 0, "S");
38 |
39 | rover.receive("b");
40 |
41 | assertEquals(new Rover(0, 1, "S"), rover);
42 | }
43 |
44 | @Test
45 | public void facingWestMoveForward() {
46 | Rover rover = new Rover(0, 0, "W");
47 |
48 | rover.receive("f");
49 |
50 | assertEquals(new Rover(-1, 0, "W"), rover);
51 | }
52 |
53 | @Test
54 | public void facingWestMoveBackward() {
55 | Rover rover = new Rover(0, 0, "W");
56 |
57 | rover.receive("b");
58 |
59 | assertEquals(new Rover(1, 0, "W"), rover);
60 | }
61 |
62 | @Test
63 | public void facingEastMoveForward() {
64 | Rover rover = new Rover(0, 0, "E");
65 |
66 | rover.receive("f");
67 |
68 | assertEquals(new Rover(1, 0, "E"), rover);
69 | }
70 |
71 | @Test
72 | public void facingEastMoveBackward() {
73 | Rover rover = new Rover(0, 0, "E");
74 |
75 | rover.receive("b");
76 |
77 | assertEquals(new Rover(-1, 0, "E"), rover);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/01-refactoring-smelly-mars-rover/src/test/java/mars_rover/RoverReceivingCommandsListTests.java:
--------------------------------------------------------------------------------
1 | package mars_rover;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | public class RoverReceivingCommandsListTests {
8 | @Test
9 | public void noCommands() {
10 | Rover rover = new Rover(0, 0, "N");
11 |
12 | rover.receive("");
13 |
14 | assertEquals(new Rover(0, 0, "N"), rover);
15 | }
16 |
17 | @Test
18 | public void twoCommands() {
19 | Rover rover = new Rover(0, 0, "N");
20 |
21 | rover.receive("lf");
22 |
23 | assertEquals(new Rover(-1, 0, "W"), rover);
24 | }
25 |
26 | @Test
27 | public void manyCommands() {
28 | Rover rover = new Rover(0, 0, "N");
29 |
30 | rover.receive("ffrbbrfflff");
31 |
32 | assertEquals(new Rover(0, 0, "E"), rover);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/01-refactoring-smelly-mars-rover/src/test/java/mars_rover/RoverRotationTests.java:
--------------------------------------------------------------------------------
1 | package mars_rover;
2 |
3 | import static org.junit.jupiter.api.Assertions.*;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | public class RoverRotationTests {
8 | @Test
9 | public void facingNorthRotateLeft() {
10 | Rover rover = new Rover(0, 0, "N");
11 |
12 | rover.receive("l");
13 |
14 | assertEquals(new Rover(0, 0, "W"), rover);
15 | }
16 |
17 | @Test
18 | public void facingNorthRotateRight() {
19 | Rover rover = new Rover(0, 0, "N");
20 |
21 | rover.receive("r");
22 |
23 | assertEquals(new Rover(0, 0, "E"), rover);
24 | }
25 |
26 | @Test
27 | public void facingSouthRotateLeft() {
28 | Rover rover = new Rover(0, 0, "S");
29 |
30 | rover.receive("l");
31 |
32 | assertEquals(new Rover(0, 0, "E"), rover);
33 | }
34 |
35 | @Test
36 | public void facingSouthRotateRight() {
37 | Rover rover = new Rover(0, 0, "S");
38 |
39 | rover.receive("r");
40 |
41 | assertEquals(new Rover(0, 0, "W"), rover);
42 | }
43 |
44 | @Test
45 | public void facingWestRotateLeft() {
46 | Rover rover = new Rover(0, 0, "W");
47 |
48 | rover.receive("l");
49 |
50 | assertEquals(new Rover(0, 0, "S"), rover);
51 | }
52 |
53 | @Test
54 | public void facingWestRotateRight() {
55 | Rover rover = new Rover(0, 0, "W");
56 |
57 | rover.receive("r");
58 |
59 | assertEquals(new Rover(0, 0, "N"), rover);
60 | }
61 |
62 | @Test
63 | public void facingEastRotateLeft() {
64 | Rover rover = new Rover(0, 0, "E");
65 |
66 | rover.receive("l");
67 |
68 | assertEquals(new Rover(0, 0, "N"), rover);
69 | }
70 |
71 | @Test
72 | public void facingEastRotateRight() {
73 | Rover rover = new Rover(0, 0, "E");
74 |
75 | rover.receive("r");
76 |
77 | assertEquals(new Rover(0, 0, "S"), rover);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/02-refactoring-video-store/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 |
--------------------------------------------------------------------------------
/02-refactoring-video-store/README.md:
--------------------------------------------------------------------------------
1 | videostore
2 | ==========
3 |
4 | The videostore example from Martin Fowler's Refactoring, and from Episode 3 of cleancoders.com
--------------------------------------------------------------------------------
/02-refactoring-video-store/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.codesai.katas-java
5 | video-store
6 | 0.0.1-SNAPSHOT
7 |
8 |
9 | 11
10 | 11
11 |
12 |
13 |
14 |
15 | org.hamcrest
16 | hamcrest
17 | 2.2
18 | test
19 |
20 |
21 | org.junit.jupiter
22 | junit-jupiter
23 | 5.9.0
24 | test
25 |
26 |
27 | org.junit.jupiter
28 | junit-jupiter-engine
29 | 5.9.0
30 | test
31 |
32 |
33 |
34 |
35 |
36 | org.apache.maven.plugins
37 | maven-surefire-plugin
38 | 2.22.2
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/02-refactoring-video-store/src/main/java/video_store/Customer.java:
--------------------------------------------------------------------------------
1 | package video_store;
2 |
3 | import java.util.Vector;
4 | import java.util.Enumeration;
5 |
6 | public class Customer {
7 | public Customer(String name) {
8 | this.name = name;
9 | }
10 |
11 | public void addRental(Rental rental) {
12 | rentals.addElement(rental);
13 | }
14 |
15 | public String getName() {
16 | return name;
17 | }
18 |
19 | public String statement() {
20 | double totalAmount = 0;
21 | int frequentRenterPoints = 0;
22 | Enumeration rentals = this.rentals.elements();
23 | String result = "Rental Record for " + getName() + "\n";
24 |
25 | while (rentals.hasMoreElements()) {
26 | double thisAmount = 0;
27 | Rental each = (Rental) rentals.nextElement();
28 |
29 | // determines the amount for each line
30 | switch (each.getMovie().getPriceCode()) {
31 | case Movie.REGULAR:
32 | thisAmount += 2;
33 | if (each.getDaysRented() > 2)
34 | thisAmount += (each.getDaysRented() - 2) * 1.5;
35 | break;
36 | case Movie.NEW_RELEASE:
37 | thisAmount += each.getDaysRented() * 3;
38 | break;
39 | case Movie.CHILDRENS:
40 | thisAmount += 1.5;
41 | if (each.getDaysRented() > 3)
42 | thisAmount += (each.getDaysRented() - 3) * 1.5;
43 | break;
44 | }
45 |
46 | frequentRenterPoints++;
47 |
48 | if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE
49 | && each.getDaysRented() > 1)
50 | frequentRenterPoints++;
51 |
52 | result += "\t" + each.getMovie().getTitle() + "\t"
53 | + String.valueOf(thisAmount) + "\n";
54 | totalAmount += thisAmount;
55 |
56 | }
57 |
58 | result += "You owed " + String.valueOf(totalAmount) + "\n";
59 | result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points\n";
60 |
61 | return result;
62 | }
63 |
64 |
65 | private String name;
66 | private Vector rentals = new Vector();
67 | }
68 |
--------------------------------------------------------------------------------
/02-refactoring-video-store/src/main/java/video_store/Movie.java:
--------------------------------------------------------------------------------
1 | package video_store;
2 |
3 | public class Movie {
4 | public static final int CHILDRENS = 2;
5 | public static final int REGULAR = 0;
6 | public static final int NEW_RELEASE = 1;
7 |
8 | private String title;
9 | private int priceCode;
10 |
11 | public Movie(String title, int priceCode) {
12 | this.title = title;
13 | this.priceCode = priceCode;
14 | }
15 |
16 | public int getPriceCode() {
17 | return priceCode;
18 | }
19 |
20 | public void setPriceCode(int code) {
21 | priceCode = code;
22 | }
23 |
24 | public String getTitle() {
25 | return title;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/02-refactoring-video-store/src/main/java/video_store/Rental.java:
--------------------------------------------------------------------------------
1 | package video_store;
2 |
3 | public class Rental {
4 | public Rental(Movie movie, int daysRented) {
5 | this.movie = movie;
6 | this.daysRented = daysRented;
7 | }
8 |
9 | public int getDaysRented() {
10 | return daysRented;
11 | }
12 |
13 | public Movie getMovie() {
14 | return movie;
15 | }
16 |
17 | private Movie movie;
18 | private int daysRented;
19 | }
20 |
--------------------------------------------------------------------------------
/02-refactoring-video-store/src/test/java/video_store/unit_tests/VideoStoreTest.java:
--------------------------------------------------------------------------------
1 | package video_store.unit_tests;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.Test;
5 | import video_store.Customer;
6 | import video_store.Movie;
7 | import video_store.Rental;
8 |
9 | import static org.hamcrest.MatcherAssert.assertThat;
10 | import static org.hamcrest.Matchers.is;
11 |
12 | public class VideoStoreTest {
13 | private Customer customer;
14 |
15 | @BeforeEach
16 | public void setUp() {
17 | customer = new Customer("Fred");
18 | }
19 |
20 | @Test
21 | public void testSingleNewReleaseStatement() {
22 | customer.addRental(new Rental(new Movie("The Cell", Movie.NEW_RELEASE), 3));
23 |
24 | assertThat(
25 | customer.statement(),
26 | is("Rental Record for Fred\n\tThe Cell\t9.0\nYou owed 9.0\nYou earned 2 frequent renter points\n"));
27 | }
28 |
29 | @Test
30 | public void testDualNewReleaseStatement() {
31 | customer.addRental(new Rental(new Movie("The Cell", Movie.NEW_RELEASE), 3));
32 | customer.addRental(new Rental(new Movie("The Tigger Movie", Movie.NEW_RELEASE), 3));
33 |
34 | assertThat(
35 | customer.statement(),
36 | is("Rental Record for Fred\n\tThe Cell\t9.0\n\tThe Tigger Movie\t9.0\nYou owed 18.0\nYou earned 4 frequent renter points\n"));
37 | }
38 |
39 | @Test
40 | public void testSingleChildrensStatement() {
41 | customer.addRental(new Rental(new Movie("The Tigger Movie", Movie.CHILDRENS), 3));
42 |
43 | assertThat(
44 | customer.statement(),
45 | is("Rental Record for Fred\n\tThe Tigger Movie\t1.5\nYou owed 1.5\nYou earned 1 frequent renter points\n"));
46 | }
47 |
48 | @Test
49 | public void testSingleChildrensStatementRentedMoreThanThreeDaysAgo() {
50 | customer.addRental(new Rental(new Movie("The Tigger Movie", Movie.CHILDRENS), 4));
51 |
52 | assertThat(
53 | customer.statement(),
54 | is("Rental Record for Fred\n\tThe Tigger Movie\t3.0\nYou owed 3.0\nYou earned 1 frequent renter points\n"));
55 | }
56 |
57 | @Test
58 | public void testMultipleRegularStatement() {
59 | customer.addRental(new Rental(new Movie("Plan 9 from Outer Space", Movie.REGULAR), 1));
60 | customer.addRental(new Rental(new Movie("8 1/2", Movie.REGULAR), 2));
61 | customer.addRental(new Rental(new Movie("Eraserhead", Movie.REGULAR), 3));
62 |
63 | assertThat(
64 | customer.statement(),
65 | is("Rental Record for Fred\n\tPlan 9 from Outer Space\t2.0\n\t8 1/2\t2.0\n\tEraserhead\t3.5\nYou owed 7.5\nYou earned 3 frequent renter points\n"));
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/03-refactoring-to-hexagonal-architecture/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.codesai.katas-java
5 | refactoring-to-hexagonal-architecture
6 | 0.0.1-SNAPSHOT
7 |
8 |
9 | 11
10 | 11
11 |
12 |
13 |
14 |
15 | com.sun.mail
16 | javax.mail
17 | 1.5.5
18 |
19 |
20 | org.mockito
21 | mockito-core
22 | 4.8.0
23 | test
24 |
25 |
26 | org.hamcrest
27 | hamcrest
28 | 2.2
29 | test
30 |
31 |
32 | org.junit.jupiter
33 | junit-jupiter
34 | 5.9.0
35 | test
36 |
37 |
38 | org.junit.jupiter
39 | junit-jupiter-engine
40 | 5.9.0
41 | test
42 |
43 |
44 |
45 |
46 |
47 | org.apache.maven.plugins
48 | maven-surefire-plugin
49 | 2.22.2
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/03-refactoring-to-hexagonal-architecture/readme.md:
--------------------------------------------------------------------------------
1 | # Birthday Greetings Kata
2 |
3 | We'll refactor a very convoluted service to segregate its business logic from its infrastructure logic.
4 |
5 | We'll use an architectural style called [hexagonal architecture or ports and adapters](http://alistair.cockburn.us/Hexagonal+architecture).
6 |
7 | To practice we'll do [Matteo Vaccari](http://matteo.vaccari.name/blog/)'s Birthday Greetings Refactoring Kata.
8 |
9 | To learn more about this great kata, read Matteo's post about it on its blog: [The birthday greetings kata](http://matteo.vaccari.name/blog/archives/154). That post also contains a great explanation of the hexagonal architecture.
10 |
--------------------------------------------------------------------------------
/03-refactoring-to-hexagonal-architecture/src/main/java/birthdaygreetings/BirthdayService.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.FileReader;
5 | import java.io.IOException;
6 | import java.text.ParseException;
7 |
8 | import javax.mail.Message;
9 | import javax.mail.MessagingException;
10 | import javax.mail.Session;
11 | import javax.mail.Transport;
12 | import javax.mail.internet.AddressException;
13 | import javax.mail.internet.InternetAddress;
14 | import javax.mail.internet.MimeMessage;
15 |
16 | public class BirthdayService {
17 |
18 | public void sendGreetings(String fileName, OurDate ourDate,
19 | String smtpHost, int smtpPort) throws IOException, ParseException,
20 | AddressException, MessagingException {
21 | BufferedReader in = new BufferedReader(new FileReader(fileName));
22 | String str = "";
23 | str = in.readLine(); // skip header
24 | while ((str = in.readLine()) != null) {
25 | String[] employeeData = str.split(", ");
26 | Employee employee = new Employee(employeeData[1], employeeData[0],
27 | employeeData[2], employeeData[3]);
28 | if (employee.isBirthday(ourDate)) {
29 | String recipient = employee.getEmail();
30 | String body = "Happy Birthday, dear %NAME%!".replace("%NAME%",
31 | employee.getFirstName());
32 | String subject = "Happy Birthday!";
33 | sendMessage(smtpHost, smtpPort, "sender@here.com", subject,
34 | body, recipient);
35 | }
36 | }
37 | }
38 |
39 | private void sendMessage(String smtpHost, int smtpPort, String sender,
40 | String subject, String body, String recipient)
41 | throws AddressException, MessagingException {
42 | // Create a mail session
43 | java.util.Properties props = new java.util.Properties();
44 | props.put("mail.smtp.host", smtpHost);
45 | props.put("mail.smtp.port", "" + smtpPort);
46 | Session session = Session.getDefaultInstance(props, null);
47 |
48 | // Construct the message
49 | Message msg = new MimeMessage(session);
50 | msg.setFrom(new InternetAddress(sender));
51 | msg.setRecipient(Message.RecipientType.TO, new InternetAddress(
52 | recipient));
53 | msg.setSubject(subject);
54 | msg.setText(body);
55 |
56 | // Send the message
57 | sendMessage(msg);
58 | }
59 |
60 | // made protected for testing :-(
61 | protected void sendMessage(Message msg) throws MessagingException {
62 | Transport.send(msg);
63 | }
64 |
65 | public static void main(String[] args) {
66 | BirthdayService service = new BirthdayService();
67 | try {
68 | service.sendGreetings("employee_data.txt",
69 | new OurDate("2008/10/08"), "localhost", 25);
70 | } catch (Exception e) {
71 | e.printStackTrace();
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/03-refactoring-to-hexagonal-architecture/src/main/java/birthdaygreetings/Employee.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings;
2 |
3 | import java.text.ParseException;
4 |
5 | public class Employee {
6 |
7 | private OurDate birthDate;
8 | private String lastName;
9 | private String firstName;
10 | private String email;
11 |
12 | public Employee(String firstName, String lastName, String birthDate,
13 | String email) throws ParseException {
14 | this.firstName = firstName;
15 | this.lastName = lastName;
16 | this.birthDate = new OurDate(birthDate);
17 | this.email = email;
18 | }
19 |
20 | public boolean isBirthday(OurDate today) {
21 | return today.isSameDay(birthDate);
22 | }
23 |
24 | public String getEmail() {
25 | return email;
26 | }
27 |
28 | public String getFirstName() {
29 | return firstName;
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | return "Employee " + firstName + " " + lastName + " <" + email
35 | + "> born " + birthDate;
36 | }
37 |
38 | @Override
39 | public int hashCode() {
40 | final int prime = 31;
41 | int result = 1;
42 | result = prime * result
43 | + ((birthDate == null) ? 0 : birthDate.hashCode());
44 | result = prime * result + ((email == null) ? 0 : email.hashCode());
45 | result = prime * result
46 | + ((firstName == null) ? 0 : firstName.hashCode());
47 | result = prime * result
48 | + ((lastName == null) ? 0 : lastName.hashCode());
49 | return result;
50 | }
51 |
52 | @Override
53 | public boolean equals(Object obj) {
54 | if (this == obj)
55 | return true;
56 | if (obj == null)
57 | return false;
58 | if (!(obj instanceof Employee))
59 | return false;
60 | Employee other = (Employee) obj;
61 | if (birthDate == null) {
62 | if (other.birthDate != null)
63 | return false;
64 | } else if (!birthDate.equals(other.birthDate))
65 | return false;
66 | if (email == null) {
67 | if (other.email != null)
68 | return false;
69 | } else if (!email.equals(other.email))
70 | return false;
71 | if (firstName == null) {
72 | if (other.firstName != null)
73 | return false;
74 | } else if (!firstName.equals(other.firstName))
75 | return false;
76 | if (lastName == null) {
77 | if (other.lastName != null)
78 | return false;
79 | } else if (!lastName.equals(other.lastName))
80 | return false;
81 | return true;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/03-refactoring-to-hexagonal-architecture/src/main/java/birthdaygreetings/OurDate.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings;
2 |
3 | import java.text.ParseException;
4 | import java.text.SimpleDateFormat;
5 | import java.util.Date;
6 | import java.util.GregorianCalendar;
7 |
8 | public class OurDate {
9 |
10 | private Date date;
11 |
12 | public OurDate(String yyyyMMdd) throws ParseException {
13 | date = new SimpleDateFormat("yyyy/MM/dd").parse(yyyyMMdd);
14 | }
15 |
16 | public int getDay() {
17 | return getPartOfDate(GregorianCalendar.DAY_OF_MONTH);
18 | }
19 |
20 | public int getMonth() {
21 | return 1 + getPartOfDate(GregorianCalendar.MONTH);
22 | }
23 |
24 | public boolean isSameDay(OurDate anotherDate) {
25 | return anotherDate.getDay() == this.getDay()
26 | && anotherDate.getMonth() == this.getMonth();
27 | }
28 |
29 | @Override
30 | public int hashCode() {
31 | return date.hashCode();
32 | }
33 |
34 | @Override
35 | public boolean equals(Object obj) {
36 | if (!(obj instanceof OurDate))
37 | return false;
38 | OurDate other = (OurDate) obj;
39 | return other.date.equals(this.date);
40 | }
41 |
42 | private int getPartOfDate(int part) {
43 | GregorianCalendar calendar = new GregorianCalendar();
44 | calendar.setTime(date);
45 | return calendar.get(part);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/03-refactoring-to-hexagonal-architecture/src/test/java/birthdaygreetings/test/AcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.test;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import javax.mail.Message;
7 | import javax.mail.MessagingException;
8 |
9 | import birthdaygreetings.BirthdayService;
10 | import birthdaygreetings.OurDate;
11 |
12 | import org.junit.jupiter.api.BeforeEach;
13 | import org.junit.jupiter.api.Test;
14 |
15 | import static org.junit.jupiter.api.Assertions.*;
16 |
17 | public class AcceptanceTest {
18 |
19 | private static final int SMTP_PORT = 25;
20 | private List messagesSent;
21 | private BirthdayService service;
22 |
23 | @BeforeEach
24 | public void setUp() throws Exception {
25 | messagesSent = new ArrayList();
26 |
27 | service = new BirthdayService() {
28 | @Override
29 | protected void sendMessage(Message msg) throws MessagingException {
30 | messagesSent.add(msg);
31 | }
32 | };
33 | }
34 |
35 | @Test
36 | public void baseScenario() throws Exception {
37 |
38 | service.sendGreetings("src/test/resources/employee_data.txt",
39 | new OurDate("2008/10/08"), "localhost", SMTP_PORT);
40 |
41 | assertEquals(1, messagesSent.size(), "message not sent?");
42 | Message message = messagesSent.get(0);
43 | assertEquals("Happy Birthday, dear John!", message.getContent());
44 | assertEquals("Happy Birthday!", message.getSubject());
45 | assertEquals(1, message.getAllRecipients().length);
46 | assertEquals("john.doe@foobar.com",
47 | message.getAllRecipients()[0].toString());
48 | }
49 |
50 | @Test
51 | public void willNotSendEmailsWhenNobodysBirthday() throws Exception {
52 | service.sendGreetings("src/test/resources/employee_data.txt",
53 | new OurDate("2008/01/01"), "localhost", SMTP_PORT);
54 |
55 | assertEquals(0, messagesSent.size(), "what? messages?");
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/03-refactoring-to-hexagonal-architecture/src/test/java/birthdaygreetings/test/EmployeeTest.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.test;
2 |
3 | import static org.junit.jupiter.api.Assertions.*;
4 |
5 | import birthdaygreetings.Employee;
6 | import birthdaygreetings.OurDate;
7 |
8 | import org.junit.jupiter.api.Test;
9 |
10 | public class EmployeeTest {
11 |
12 | @Test
13 | public void testBirthday() throws Exception {
14 | Employee employee = new Employee("foo", "bar", "1990/01/31", "a@b.c");
15 | assertFalse(employee.isBirthday(new OurDate("2008/01/30")),
16 | "not his birthday");
17 | assertTrue(employee.isBirthday(new OurDate("2008/01/31")),
18 | "his birthday");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/03-refactoring-to-hexagonal-architecture/src/test/java/birthdaygreetings/test/OurDateTest.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.test;
2 |
3 | import static org.junit.jupiter.api.Assertions.*;
4 |
5 | import birthdaygreetings.OurDate;
6 |
7 | import org.junit.jupiter.api.Test;
8 |
9 | public class OurDateTest {
10 |
11 | @Test
12 | public void isSameDate() throws Exception {
13 | OurDate ourDate = new OurDate("1789/01/24");
14 | OurDate sameDay = new OurDate("2001/01/24");
15 | OurDate notSameDay = new OurDate("1789/01/25");
16 | OurDate notSameMonth = new OurDate("1789/02/24");
17 |
18 | assertTrue(ourDate.isSameDay(sameDay), "same");
19 | assertFalse(ourDate.isSameDay(notSameDay), "not same day");
20 | assertFalse(ourDate.isSameDay(notSameMonth), "not same month");
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/03-refactoring-to-hexagonal-architecture/src/test/resources/employee_data.txt:
--------------------------------------------------------------------------------
1 | last_name, first_name, date_of_birth, email
2 | Doe, John, 1982/10/08, john.doe@foobar.com
3 | Ann, Mary, 1975/03/11, mary.ann@foobar.com
4 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | com.codesai.katas-java
4 | refactoring-to-hexagonal-architecture-2
5 | 0.0.1-SNAPSHOT
6 |
7 |
8 | 11
9 | 11
10 |
11 |
12 |
13 |
14 | com.sun.mail
15 | javax.mail
16 | 1.5.5
17 |
18 |
19 | org.mockito
20 | mockito-core
21 | 4.8.0
22 | test
23 |
24 |
25 | org.hamcrest
26 | hamcrest
27 | 2.2
28 | test
29 |
30 |
31 | org.junit.jupiter
32 | junit-jupiter
33 | 5.9.0
34 | test
35 |
36 |
37 | org.junit.jupiter
38 | junit-jupiter-engine
39 | 5.9.0
40 | test
41 |
42 |
43 |
44 |
45 |
46 | org.apache.maven.plugins
47 | maven-surefire-plugin
48 | 2.22.2
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/readme.md:
--------------------------------------------------------------------------------
1 | # Birthday Greetings Kata 2
2 |
3 | We'll continue refactoring a now less convoluted service to extract the remaining infrastructure logic.
4 |
5 | We'll use an architectural style called [hexagonal architecture or ports and adapters](http://alistair.cockburn.us/Hexagonal+architecture).
6 |
7 | To practice we'll do [Matteo Vaccari](http://matteo.vaccari.name/blog/)'s Birthday Greetings Refactoring Kata.
8 |
9 | To learn more about this great kata, read Matteo's post about it on its blog: [The birthday greetings kata](http://matteo.vaccari.name/blog/archives/154). That post also contains a great explanation of the hexagonal architecture.
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/application/BirthdayGreetingsService.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.application;
2 |
3 | import birthdaygreetings.core.Employee;
4 | import birthdaygreetings.core.EmployeesRepository;
5 | import birthdaygreetings.core.GreetingMessage;
6 | import birthdaygreetings.core.OurDate;
7 |
8 | import javax.mail.Message;
9 | import javax.mail.MessagingException;
10 | import javax.mail.Session;
11 | import javax.mail.Transport;
12 | import javax.mail.internet.InternetAddress;
13 | import javax.mail.internet.MimeMessage;
14 | import java.util.List;
15 | import java.util.stream.Collectors;
16 |
17 | public class BirthdayGreetingsService {
18 |
19 | private final EmployeesRepository employeesRepository;
20 |
21 | public BirthdayGreetingsService(EmployeesRepository employeesRepository) {
22 | this.employeesRepository = employeesRepository;
23 | }
24 |
25 | public void sendGreetings(OurDate date, String smtpHost, int smtpPort, String sender) throws MessagingException {
26 |
27 | send(greetingMessagesFor(employeesHavingBirthday(date)),
28 | smtpHost, smtpPort, sender);
29 | }
30 |
31 | private List greetingMessagesFor(List employees) {
32 | return GreetingMessage.generateForSome(employees);
33 | }
34 |
35 | private List employeesHavingBirthday(OurDate today) {
36 | List allEmployees = employeesRepository.getAll();
37 | return allEmployees.stream()
38 | .filter(employee -> employee.isBirthday(today))
39 | .collect(Collectors.toList());
40 | }
41 |
42 | private void send(List messages, String smtpHost, int smtpPort, String sender) throws MessagingException {
43 | for (GreetingMessage message : messages) {
44 | String recipient = message.to();
45 | String body = message.text();
46 | String subject = message.subject();
47 | sendMessage(smtpHost, smtpPort, sender, subject, body, recipient);
48 | }
49 | }
50 |
51 | private void sendMessage(String smtpHost, int smtpPort, String sender,
52 | String subject, String body, String recipient)
53 | throws MessagingException {
54 | // Create a mail session
55 | java.util.Properties props = new java.util.Properties();
56 | props.put("mail.smtp.host", smtpHost);
57 | props.put("mail.smtp.port", "" + smtpPort);
58 | Session session = Session.getDefaultInstance(props, null);
59 |
60 | // Construct the message
61 | Message msg = new MimeMessage(session);
62 | msg.setFrom(new InternetAddress(sender));
63 | msg.setRecipient(Message.RecipientType.TO, new InternetAddress(
64 | recipient));
65 | msg.setSubject(subject);
66 | msg.setText(body);
67 |
68 | // Send the message
69 | sendMessage(msg);
70 | }
71 |
72 | // made protected for testing :-(
73 | protected void sendMessage(Message msg) throws MessagingException {
74 | Transport.send(msg);
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/application/Main.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.application;
2 |
3 | import birthdaygreetings.core.OurDate;
4 | import birthdaygreetings.infrastructure.repositories.FileEmployeesRepository;
5 |
6 | import java.util.Date;
7 |
8 | public class Main {
9 |
10 | private static final String EMPLOYEES_FILE_PATH = "employee_data.txt";
11 | private static final String SENDER_EMAIL_ADDRESS = "sender@here.com";
12 | private static final String HOST = "localhost";
13 | private static final int SMTP_PORT = 25;
14 |
15 | public static void main(String[] args) {
16 | BirthdayGreetingsService service = new BirthdayGreetingsService(
17 | new FileEmployeesRepository(EMPLOYEES_FILE_PATH));
18 | try {
19 | OurDate today = new OurDate(new Date());
20 | service.sendGreetings(today, HOST, SMTP_PORT, SENDER_EMAIL_ADDRESS);
21 | } catch (Exception e) {
22 | e.printStackTrace();
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/core/CannotReadEmployeesException.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.core;
2 |
3 | public class CannotReadEmployeesException extends RuntimeException {
4 | public CannotReadEmployeesException(String cause, Exception exception) {
5 | super(cause, exception);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/core/Employee.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.core;
2 |
3 | public class Employee {
4 |
5 | private OurDate birthDate;
6 | private String lastName;
7 | private String firstName;
8 | private String email;
9 |
10 | public Employee(String firstName, String lastName, OurDate birthDate,
11 | String email) {
12 | this.firstName = firstName;
13 | this.lastName = lastName;
14 | this.birthDate = birthDate;
15 | this.email = email;
16 | }
17 |
18 | public boolean isBirthday(OurDate today) {
19 | return today.isSameDay(birthDate);
20 | }
21 |
22 | String email() {
23 | return email;
24 | }
25 |
26 | String firstName() {
27 | return firstName;
28 | }
29 |
30 | @Override
31 | public String toString() {
32 | return "Employee " + firstName + " " + lastName + " <" + email
33 | + "> born " + birthDate;
34 | }
35 |
36 | @Override
37 | public boolean equals(Object o) {
38 | if (this == o) return true;
39 | if (!(o instanceof Employee)) return false;
40 |
41 | Employee employee = (Employee) o;
42 |
43 | if (birthDate != null ? !birthDate.equals(employee.birthDate) : employee.birthDate != null) return false;
44 | if (lastName != null ? !lastName.equals(employee.lastName) : employee.lastName != null) return false;
45 | if (firstName != null ? !firstName.equals(employee.firstName) : employee.firstName != null) return false;
46 | return email != null ? email.equals(employee.email) : employee.email == null;
47 | }
48 |
49 | @Override
50 | public int hashCode() {
51 | int result = birthDate != null ? birthDate.hashCode() : 0;
52 | result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
53 | result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
54 | result = 31 * result + (email != null ? email.hashCode() : 0);
55 | return result;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/core/EmployeesRepository.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.core;
2 |
3 | import java.util.List;
4 |
5 | public interface EmployeesRepository {
6 | List getAll();
7 | }
8 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/core/Greeting.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.core;
2 |
3 | import java.util.Objects;
4 | import java.util.StringJoiner;
5 |
6 | class Greeting {
7 | private final String header;
8 | private final String content;
9 |
10 | private Greeting(String header, String content) {
11 | this.header = header;
12 | this.content = content;
13 | }
14 |
15 | static Greeting forBirthdayOf(Employee employee){
16 | String content = String.format("Happy Birthday, dear %s!", employee.firstName());
17 | String header = "Happy Birthday!";
18 | return new Greeting(header, content);
19 | }
20 |
21 | String header() {
22 | return header;
23 | }
24 |
25 | String content() {
26 | return content;
27 | }
28 |
29 | @Override
30 | public boolean equals(Object o) {
31 | if (this == o) return true;
32 | if (!(o instanceof Greeting)) return false;
33 | Greeting greeting = (Greeting) o;
34 | return Objects.equals(header, greeting.header) && Objects.equals(content, greeting.content);
35 | }
36 |
37 | @Override
38 | public int hashCode() {
39 | return Objects.hash(header, content);
40 | }
41 |
42 | @Override
43 | public String toString() {
44 | return new StringJoiner(", ", Greeting.class.getSimpleName() + "[", "]")
45 | .add("header='" + header + "'")
46 | .add("content='" + content + "'")
47 | .toString();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/core/GreetingMessage.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.core;
2 |
3 | import java.util.List;
4 | import java.util.Objects;
5 | import java.util.StringJoiner;
6 |
7 | import static java.util.stream.Collectors.toList;
8 |
9 | public class GreetingMessage {
10 |
11 | private final String to;
12 | private final Greeting greeting;
13 |
14 | private GreetingMessage(String to, Greeting greeting) {
15 | this.to = to;
16 | this.greeting = greeting;
17 | }
18 |
19 | public static List generateForSome(List employees) {
20 | return employees.stream().map(GreetingMessage::generateFor).collect(toList());
21 | }
22 |
23 | private static GreetingMessage generateFor(Employee employee) {
24 | Greeting greeting = Greeting.forBirthdayOf(employee);
25 | String recipient = employee.email();
26 | return new GreetingMessage(recipient, greeting);
27 | }
28 |
29 | public String subject() {
30 | return this.greeting.header();
31 | }
32 |
33 | public String text() {
34 | return this.greeting.content();
35 | }
36 |
37 | public String to() {
38 | return this.to;
39 | }
40 |
41 | @Override
42 | public boolean equals(Object o) {
43 | if (this == o) return true;
44 | if (!(o instanceof GreetingMessage)) return false;
45 | GreetingMessage that = (GreetingMessage) o;
46 | return Objects.equals(to, that.to) && Objects.equals(greeting, that.greeting);
47 | }
48 |
49 | @Override
50 | public int hashCode() {
51 | return Objects.hash(to, greeting);
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return new StringJoiner(", ", GreetingMessage.class.getSimpleName() + "[", "]")
57 | .add("to='" + to + "'")
58 | .add("greeting=" + greeting)
59 | .toString();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/core/OurDate.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.core;
2 |
3 | import java.util.Date;
4 | import java.util.GregorianCalendar;
5 |
6 | public class OurDate {
7 | private Date date;
8 |
9 | public OurDate(Date date) {
10 | this.date = date;
11 | }
12 |
13 | public boolean isSameDay(OurDate anotherDate) {
14 | return anotherDate.getDay() == this.getDay()
15 | && anotherDate.getMonth() == this.getMonth();
16 | }
17 |
18 | @Override
19 | public boolean equals(Object o) {
20 | if (this == o) return true;
21 | if (!(o instanceof OurDate)) return false;
22 |
23 | OurDate ourDate = (OurDate) o;
24 |
25 | return date != null ? date.equals(ourDate.date) : ourDate.date == null;
26 | }
27 |
28 | @Override
29 | public int hashCode() {
30 | return date != null ? date.hashCode() : 0;
31 | }
32 |
33 | private int getDay() {
34 | return getPartOfDate(GregorianCalendar.DAY_OF_MONTH);
35 | }
36 |
37 | private int getMonth() {
38 | return 1 + getPartOfDate(GregorianCalendar.MONTH);
39 | }
40 |
41 | private int getPartOfDate(int part) {
42 | GregorianCalendar calendar = new GregorianCalendar();
43 | calendar.setTime(date);
44 | return calendar.get(part);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/infrastructure/repositories/DateRepresentation.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.infrastructure.repositories;
2 |
3 | import birthdaygreetings.core.OurDate;
4 |
5 | import java.text.ParseException;
6 | import java.text.SimpleDateFormat;
7 |
8 | public class DateRepresentation {
9 | private static final String DATE_FORMAT = "yyyy/MM/dd";
10 | private final String dateAsString;
11 |
12 | public DateRepresentation(String dateAsString) {
13 | this.dateAsString = dateAsString;
14 | }
15 |
16 | public OurDate toDate() throws ParseException {
17 | return new OurDate(
18 | new SimpleDateFormat(DATE_FORMAT).parse(dateAsString)
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/infrastructure/repositories/EmployeesFile.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.infrastructure.repositories;
2 |
3 | import birthdaygreetings.core.CannotReadEmployeesException;
4 | import birthdaygreetings.core.Employee;
5 | import birthdaygreetings.core.OurDate;
6 |
7 | import java.io.IOException;
8 | import java.nio.file.Files;
9 | import java.nio.file.Path;
10 | import java.nio.file.Paths;
11 | import java.text.ParseException;
12 | import java.util.ArrayList;
13 | import java.util.Iterator;
14 | import java.util.List;
15 |
16 | class EmployeesFile {
17 | private final Iterator linesIterator;
18 |
19 | private EmployeesFile(Iterator linesIterator) {
20 | this.linesIterator = linesIterator;
21 | }
22 |
23 | public static EmployeesFile loadFrom(String path) {
24 | return new EmployeesFile(FileReader.readSkippingHeader(path));
25 | }
26 |
27 | public List extractEmployees() {
28 | List employees = new ArrayList<>();
29 | while (linesIterator.hasNext()) {
30 | String line = linesIterator.next();
31 | EmployeeCsvRepresentation representation = new EmployeeCsvRepresentation(line);
32 | employees.add(representation.convertToEmployee());
33 | }
34 | return employees;
35 | }
36 |
37 | private class EmployeeCsvRepresentation {
38 | private String content;
39 | private final String[] tokens;
40 |
41 | public EmployeeCsvRepresentation(String content) {
42 | this.content = content;
43 | this.tokens = content.split(", ");
44 | }
45 |
46 | public Employee convertToEmployee() {
47 | return new Employee(firstName(), lastName(), birthDate(), email());
48 | }
49 |
50 | private String lastName() {
51 | return tokens[0];
52 | }
53 |
54 | private String email() {
55 | return tokens[3];
56 | }
57 |
58 | private String firstName() {
59 | return tokens[1];
60 | }
61 |
62 | private OurDate birthDate() {
63 | try {
64 | return new DateRepresentation(dateAsString()).toDate();
65 | } catch (ParseException exception) {
66 | throw new CannotReadEmployeesException(
67 | String.format("Badly formatted employee birth date in: '%s'", content),
68 | exception
69 | );
70 | }
71 | }
72 |
73 | private String dateAsString() {
74 | return tokens[2];
75 | }
76 | }
77 |
78 | private static class FileReader {
79 | public static Iterator readSkippingHeader(String pathString) {
80 | Path path = Paths.get(pathString);
81 | try {
82 | return skipHeader(readFile(path));
83 | } catch (IOException exception) {
84 | throw new CannotReadEmployeesException(
85 | String.format("cannot loadFrom file = '%s'", path.toAbsolutePath()),
86 | exception
87 | );
88 | }
89 | }
90 |
91 | private static Iterator readFile(Path path) throws IOException {
92 | List lines = Files.readAllLines(path);
93 | return lines.iterator();
94 | }
95 |
96 | private static Iterator skipHeader(Iterator iterator) {
97 | iterator.next();
98 | return iterator;
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/main/java/birthdaygreetings/infrastructure/repositories/FileEmployeesRepository.java:
--------------------------------------------------------------------------------
1 | package birthdaygreetings.infrastructure.repositories;
2 |
3 | import birthdaygreetings.core.Employee;
4 | import birthdaygreetings.core.EmployeesRepository;
5 |
6 | import java.util.List;
7 |
8 | public class FileEmployeesRepository implements EmployeesRepository {
9 | private final String path;
10 |
11 | public FileEmployeesRepository(String path) {
12 | this.path = path;
13 | }
14 |
15 | @Override
16 | public List getAll() {
17 | EmployeesFile employeesFile = EmployeesFile.loadFrom(path);
18 | return employeesFile.extractEmployees();
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/test/java/test/birthdaygreetings/application/BirthdayGreetingsServiceAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package test.birthdaygreetings.application;
2 |
3 | import birthdaygreetings.application.BirthdayGreetingsService;
4 | import birthdaygreetings.core.OurDate;
5 | import birthdaygreetings.infrastructure.repositories.FileEmployeesRepository;
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import javax.mail.Message;
10 | import javax.mail.MessagingException;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | import static org.junit.jupiter.api.Assertions.assertEquals;
15 | import static test.birthdaygreetings.helpers.OurDateFactory.ourDate;
16 |
17 | public class BirthdayGreetingsServiceAcceptanceTest {
18 |
19 | private static final int SMTP_PORT = 25;
20 | private final String SMTP_HOST = "localhost";
21 | private static final String FROM = "sender@here.com";
22 | private List messagesSent;
23 | private BirthdayGreetingsService service;
24 | private static final String EMPLOYEES_FILE_PATH = "src/test/resources/employee_data.csv";
25 |
26 | @BeforeEach
27 | public void setUp() {
28 | messagesSent = new ArrayList<>();
29 |
30 | service = new BirthdayGreetingsService(new FileEmployeesRepository(EMPLOYEES_FILE_PATH)) {
31 | @Override
32 | protected void sendMessage(Message msg) throws MessagingException {
33 | messagesSent.add(msg);
34 | }
35 | };
36 | }
37 |
38 | @Test
39 | public void baseScenario() throws Exception {
40 | OurDate today = ourDate("2008/10/08");
41 |
42 | service.sendGreetings(today, SMTP_HOST, SMTP_PORT, FROM);
43 |
44 | assertEquals(1, messagesSent.size(), "message not sent?");
45 | Message message = messagesSent.get(0);
46 | assertEquals("Happy Birthday, dear John!", message.getContent());
47 | assertEquals("Happy Birthday!", message.getSubject());
48 | assertEquals(1, message.getAllRecipients().length);
49 | assertEquals("john.doe@foobar.com", message.getAllRecipients()[0].toString());
50 | }
51 |
52 | @Test
53 | public void willNotSendEmailsWhenNobodysBirthday() throws Exception {
54 | OurDate today = ourDate("2008/01/01");
55 |
56 | service.sendGreetings(today, SMTP_HOST, SMTP_PORT, FROM);
57 |
58 | assertEquals(0, messagesSent.size());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/test/java/test/birthdaygreetings/core/EmployeeTest.java:
--------------------------------------------------------------------------------
1 | package test.birthdaygreetings.core;
2 |
3 | import birthdaygreetings.core.Employee;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import static org.junit.jupiter.api.Assertions.*;
7 | import static test.birthdaygreetings.helpers.OurDateFactory.ourDate;
8 |
9 | public class EmployeeTest {
10 |
11 | @Test
12 | public void testBirthday() throws Exception {
13 | Employee employee = new Employee("foo", "bar", ourDate("1990/01/31"), "a@b.c");
14 |
15 | assertFalse(employee.isBirthday(ourDate("2008/01/30")), "no birthday");
16 |
17 | assertTrue(employee.isBirthday(ourDate("2008/01/31")), "birthday");
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/test/java/test/birthdaygreetings/core/OurDateTest.java:
--------------------------------------------------------------------------------
1 | package test.birthdaygreetings.core;
2 |
3 | import birthdaygreetings.core.OurDate;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import static org.junit.jupiter.api.Assertions.*;
7 | import static test.birthdaygreetings.helpers.OurDateFactory.ourDate;
8 |
9 | public class OurDateTest {
10 | @Test
11 | public void identifies_if_two_dates_were_in_the_same_day() throws Exception {
12 | OurDate ourDate = ourDate("1789/01/24");
13 | OurDate sameDay = ourDate("2001/01/24");
14 | OurDate notSameDay = ourDate("1789/01/25");
15 | OurDate notSameMonth = ourDate("1789/02/24");
16 |
17 | assertTrue(ourDate.isSameDay(sameDay), "same");
18 | assertFalse(ourDate.isSameDay(notSameDay), "not same day");
19 | assertFalse(ourDate.isSameDay(notSameMonth), "not same month");
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/test/java/test/birthdaygreetings/helpers/OurDateFactory.java:
--------------------------------------------------------------------------------
1 | package test.birthdaygreetings.helpers;
2 |
3 | import birthdaygreetings.core.OurDate;
4 |
5 | import java.text.ParseException;
6 | import java.text.SimpleDateFormat;
7 |
8 | public class OurDateFactory {
9 | private static final String DATE_FORMAT = "yyyy/MM/dd";
10 |
11 | public static OurDate ourDate(String dateAsString) throws ParseException {
12 | return new OurDate(new SimpleDateFormat(DATE_FORMAT).parse(dateAsString));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/test/java/test/birthdaygreetings/infrastructure/repositories/FileEmployeeRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package test.birthdaygreetings.infrastructure.repositories;
2 |
3 | import birthdaygreetings.core.CannotReadEmployeesException;
4 | import birthdaygreetings.core.EmployeesRepository;
5 | import birthdaygreetings.infrastructure.repositories.FileEmployeesRepository;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import static org.hamcrest.MatcherAssert.assertThat;
9 | import static org.hamcrest.core.StringContains.containsString;
10 | import static org.junit.jupiter.api.Assertions.assertThrows;
11 |
12 | public class FileEmployeeRepositoryTest {
13 |
14 | @Test
15 | public void fails_when_the_file_does_not_exist() {
16 | EmployeesRepository employeesRepository = new FileEmployeesRepository("non-existing.file");
17 |
18 | final CannotReadEmployeesException exception = assertThrows(CannotReadEmployeesException.class,
19 | employeesRepository::getAll);
20 |
21 | assertThat(exception.getMessage(), containsString("cannot loadFrom file"));
22 | assertThat(exception.getMessage(), containsString("non-existing.file"));
23 | }
24 |
25 | @Test
26 | public void fails_when_file_contains_wrongly_formatted_birthdate() {
27 | EmployeesRepository employeesRepository = new FileEmployeesRepository(
28 | "src/test/resources/contains-employee-with-wrongly-formatted-birthdate.csv"
29 | );
30 |
31 | final CannotReadEmployeesException exception = assertThrows(CannotReadEmployeesException.class,
32 | employeesRepository::getAll);
33 |
34 | assertThat(exception.getMessage(), containsString("Badly formatted employee birth date in"));
35 | }
36 | }
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/test/resources/contains-employee-with-wrongly-formatted-birthdate.csv:
--------------------------------------------------------------------------------
1 | #the date format is wrong
2 | Doe, John, 2016-01-01, john.doe@foobar.com
--------------------------------------------------------------------------------
/04-refactoring-to-hexagonal-architecture-2/src/test/resources/employee_data.csv:
--------------------------------------------------------------------------------
1 | last_name, first_name, date_of_birth, email
2 | Doe, John, 1982/10/08, john.doe@foobar.com
3 | Ann, Mary, 1975/03/11, mary.ann@foobar.com
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Refactoring Katas
2 |
3 | ### 1. Refactoring a smelly Mars Rover's code
4 |
5 | ### 2. Refactoring classic Video Store Example
6 |
7 | ### 3. Refactoring to Hexagonal Architecture 1
8 |
9 | ### 4. Refactoring to Hexagonal Architecture 2
10 |
--------------------------------------------------------------------------------