©Alistair Cockburn Alistair Cockburn You Can't Be Agile if Your Architecture's a Mess “Continuous attention to technical excellence and good design enhances agility.” (Agile Manifesto, 2001)
©Alistair Cockburn go to Architecture implement discount = amount * discountRate(amount) amount comes from User and/or Test framework discountRate (amount) from DB and/or in-memory mock Implement the “barn door” Make it simple Prepare to show your code
©Alistair Cockburn Why? Up-down and Left-right maps of architecture miss an important symmetry App User App User Both user and DB are ‘outside’ the application
©Alistair Cockburn Most people implement them ‘inside’ the application User GUI (with some bus.logic) APP business logic DB Boo :-( Can’t regression test Can’t make batch Can’t hook to another program Can’t isolate app when DB breaks
©Alistair Cockburn Put both user and DB outside the application The “Hexagonal” architecture: Separate domain & application from ports & adapters. Domain + Application Port
©Alistair Cockburn Use “adapters” to connect ports to external devices Application Adapter app Adapter
©Alistair Cockburn Typical ports are input, data stores. Typical adapters are test, UI, app-app, DB, mocks Application user-side API app test harness adapter data-side API DB access service mock (in-memory) database (Use case boundary) GUI adapter HTTP adapter app-to-app adapter
©Alistair Cockburn Sample application: Storm Warning System Application trigger data app wire feed test adapter http feed notifications answering machine adapter mock telephone administration database test adapter GUI app-to-app adapter DB mock database http adapter adapter
©Alistair Cockburn Implement Test+Mock first. Add UI & DB later Application test harness user interface mock database database access 1 324
©Alistair Cockburn Sample Implementation [1] Test Adapter with FIT import fit.ColumnFixture; public class TestDiscounter extends ColumnFixture { private Discounter app = new Discounter(); public double amount; public double discountRate() { return app. discountRate(amount); } } Application test harness user interface mock database database access 1324
©Alistair Cockburn Sample Implementation of [2] fake DB Discounter app = new Discounter(); public void actionPerformed(ActionEvent event) {... String amountStr = text1.getText(); double amount = Double.parseDouble(amountStr); discountRate = app.discountRate(amount)); text3.setText( "" + discount ); Application test harness user interface mock database database access 1324
©Alistair Cockburn Sample Implementation of Replaceable Mock/DB... public interface RateRepository { double getRate(double amount); } public class RepositoryFactory { public RepositoryFactory() { super(); } public static RateRepository getMockRateRepository() {return new MockRateRepository(); }} public class MockRateRepository implements RateRepository { public double getRate(double amount) { if(amount <= 100) return 0.01; if(amount <= 1000) return 0.02; return 0.05; } Application test harness user interface mock database database access 1324
©Alistair Cockburn Sample Implementation of Switchable Mock/DB import repository.RepositoryFactory; import repository.RateRepository; public class Discounter { private RateRepository rateRepository; public Discounter(RateRepository r) { super(); rateRepository = r; } public double discount(double amount) { double rate = rateRepository.getRate( amount ); return amount * rate; } import app.Discounter; import fit.ColumnFixture; public class TestDiscounter extends ColumnFixture { private Discounter app = new Discounter(RepositoryFactory.getMockRateRepository()); public double amount; public double discountRate() {return app.discountRate( amount ); } }
©Alistair Cockburn Phil Borland’s ‘Riser’ Entity Framework Based on Apple's Core Data Framework ManagedObjectContext to fetch, delete, save objects. PersistentStore represents a single data store. e.g. SQL data store (MySQL, Postres, etc) & a custom one with mock objects. Switch them at will and the app runs the same. contact: src: net.sf.riser.examples.documentation with package: net.sf.riser.examples.hexagonal
©Alistair Cockburn Domain Objects public class Discount { private String productName; private double rate; // Getters, setters, constructors, etc } public class Discounter { public static double getDiscount(String productName) { FetchRequest fetchRequest = new FetchRequest(Constants.DISCOUNT); fetchRequest.setPredicate(Predicates. createPredicate("productName=\""+productName+"\” ”)); List discounts = getContext().executeFetchRequest(fetchRequest); if (discounts == null || discounts.size() == 0) { return 0; } else { return ((Discount) discounts.get(0)).getRate(); }
©Alistair Cockburn Mock Store public class MockStore extends AbstractMockStore { public static final String TYPE = "HexagonalMockStore"; public MockStore (PersistentStoreCoordinator coordinator, String configurationName, URL location, Map options) { super(coordinator, configurationName, location, options); } public MockStore(PersistentStoreCoordinator coordinator) { super(coordinator, null, null, null); public void doLoad() { EntityDescription entity = Constants.DISCOUNT; addObject(entity, new Discount("ProductOne",.01)); addObject(entity, new Discount("ProductTwo",.02)); public String getType() { return TYPE; }
©Alistair Cockburn Creating the Context private static ManagedObjectContext createContext(ManagedObjectModel model) { PersistentStoreCoordinator coordinator = new PersistentStoreCoordinator(model); if ("mock".equals(System.getProperty("store"))) { coordinator.addPersistentStore( new MockStore(coordinator)); } else { coordinator.addPersistentStore( new SQLStore(coordinator,createSQLOptions())); } ManagedObjectContext context = new ManagedObjectContext(coordinator); return context; } contact: src at: net.sf.riser.examples.documentation with package: net.sf.riser.examples.hexagonal
©Alistair Cockburn Keep your design clean so you stay able to move. Make the tests the first “user” of your app. Make the first DB a mock, be able to swap them. Application user-side API app test harness adapter data-side API DB access service mock (in-memory) database (Use case boundary) GUI adapter HTTP adapter app-to-app adapter Application test harness user interface mock database database access 1324
©Alistair Cockburn