├── .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 | --------------------------------------------------------------------------------