1 / 19

You Can't Be Agile if Your Architecture's a Mess

Learn about the importance of having a clean architecture for agile development, with a focus on the Hexagonal Architecture model. Understand how to implement it, discounting with different rates, and separate the domain and application from ports and adapters.

joeys
Download Presentation

You Can't Be Agile if Your Architecture's a Mess

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. You Can't Be Agile if Your Architecture's a Mess Alistair Cockburn “Continuous attention to technical excellence and good design enhances agility.” (Agile Manifesto, 2001)

  2. go tohttp://Alistair.Cockburn.us/Hexagonal Architecture • implement • discount = amount * discountRate(amount) • amount comes from User and/or Testframework • discountRate(amount) from DB and/or in-memory mock • Implement the “barn door” • Make it simple • Prepare to show your code

  3. App App User User Why? Up-down and Left-right maps of architecturemiss an important symmetry Both user and DB are‘outside’ the application

  4. Boo :-( Can’t regression test Can’t make batch Can’t hook to another program Can’t isolate app when DB breaks User Most people implement them ‘inside’ the application APP GUI(with some bus.logic) business logic DB

  5. Put both user and DB outside the application The “Hexagonal” architecture:Separate domain & application from ports & adapters. Port Port Port Domain +Application Port Port

  6. app Use “adapters” to connect ports to external devices Adapter Adapter Adapter Adapter Adapter Adapter Adapter Adapter Application Adapter Adapter Adapter Adapter

  7. Typical ports are input, data stores.Typical adapters are test, UI, app-app, DB, mocks test harness adapter GUI adapter DB access service HTTP adapter app Application mock (in-memory) database user-side API data-side API app-to-app adapter (Use case boundary)

  8. Sample application: Storm Warning System wire feed answering machine adapter test adapter emailadapter Application http feed trigger data notifications mock telephone GUI httpadapter DB database administration app-to-app adapter mock database app test adapter

  9. testharness user interface Application mockdatabase databaseaccess 1 2 3 4 Implement Test+Mock first. Add UI & DB later

  10. 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); } • } testharness user interface Application mockdatabase databaseaccess 1 2 3 4

  11. 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 ); testharness user interface Application mockdatabase databaseaccess 1 2 3 4

  12. Sample Implementation of Replaceable Mock/DB ... • public interfaceRateRepository { • 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; • } • } testharness user interface Application mockdatabase databaseaccess 1 2 3 4

  13. 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 ); } • }

  14. Phil Borland’s ‘Riser’ Entity Framework • http://code.google.com/p/riser/ • 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: phil@risertech.com • src: net.sf.riser.examples.documentation • with package: net.sf.riser.examples.hexagonal

  15. Domain Objects publicclassDiscount { private String productName; privatedoublerate; // Getters, setters, constructors, etc } publicclass Discounter { publicstaticdouble getDiscount(String productName) { FetchRequest fetchRequest = new FetchRequest(Constants.DISCOUNT); fetchRequest.setPredicate(Predicates. createPredicate("productName=\""+productName+"\” ”)); List<Object> discounts =getContext().executeFetchRequest(fetchRequest); if (discounts == null || discounts.size() == 0) { return 0; } else { return ((Discount) discounts.get(0)).getRate(); } } }

  16. Mock Store publicclass MockStore extends AbstractMockStore { publicstaticfinal String TYPE = "HexagonalMockStore"; publicMockStore(PersistentStoreCoordinator coordinator, String configurationName, URL location, Map<String, Object> options) { super(coordinator, configurationName, location, options); } public MockStore(PersistentStoreCoordinator coordinator) { super(coordinator, null, null, null); } @Override publicvoid doLoad() { EntityDescription entity = Constants.DISCOUNT; addObject(entity, new Discount("ProductOne", .01)); addObject(entity, new Discount("ProductTwo", .02)); } @Override public String getType() { returnTYPE; } }

  17. Creating the Context privatestatic 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: phil@risertech.comsrc at: net.sf.riser.examples.documentation with package: net.sf.riser.examples.hexagonal

  18. test harness adapter testharness user interface GUI adapter DB access service HTTP adapter app Application Application mock (in-memory) database user-side API data-side API app-to-app adapter mockdatabase databaseaccess (Use case boundary) 1 2 3 4 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.

  19. http://Alistair.Cockburn.us

More Related