Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Hexagonal architecture

Hexagonal architecture

Christian Schmidt

March 19, 2019
Tweet

More Decks by Christian Schmidt

Other Decks in Programming

Transcript

  1. Wer bin ich? Christian Iwanzik (33) Softwareentwickler Dipl-Inf. (FH) -

    FH Köln Steckenpferde: • JVM • Kotlin • Scala • auch java ;-) • Domain Driven Design • Microservicearchitekturen Seit 2010 bei tarent Solutions in Bonn Schmidt
  2. Wir Bauen einen Warenkorb! DB Shoppingcart - Service Product-Service GET

    /products/{sku} GET /shoppingcart/{uuid} PUT /shoppingcart/{uuid}/items Produktbild: http://tinobusiness.com/wp-content/uploads/2015/09/CONSUMER-PRODUCTS-e1442909155136.jpg
  3. Wir Bauen einen Warenkorb! Controller Servicelayer DB-Repository HTTP Client top

    down Separation of concerns! Wenn wir mal die DB austauschen: Kein Problem! Decoupling! Dependency Inversion!
  4. Maximaler Warenkorb- wert: 120€ Maximale Produktanzahl im Warenkorb: 50 Maximale

    Anzahl pro Produkt: 10 SKU (Stock Keeping Unit): alphanumerisch von 5 - 20 Zeichen … und dann kam die Fachlogik
  5. 0..n 1 Controller Servicelayer DB-Repository HTTP Client Klar: Hier im

    Service! Da ist die Logik! Product sku: String price: Int ShoppingCart products: Map <Product, Int> … und dann kam die Fachlogik
  6. Was bauen wir denn hier? Ein Warenkorb- service! Ein µService

    mit Spring Boot! Alles in Kotlin. Voll cool! Oh und JPA und so! Ja… Aber was MACHT denn der Service!? Bob kommt neu ins Team
  7. fun putProduct(@PathVariable uuid: String, sku: String): ResponseEntity<*> { val skuRegex

    = "[\\w\\d]{5,20}".toRegex() return if(skuRegex.matches(sku)) { val cart = service.addProduct(uuid, sku) … } else { ResponseEntity.badRequest().body("sku is not valid") } } Controller Also hier wird erst mal die SKU validiert. Beim Fehler geben wir HTTP 400 zurück. Was macht denn der Service?
  8. fun addProduct(uuid: String, sku: String): ShoppingCart? { val optionalCart =

    shoppingCartRepo.findById(uuid) return if(optionalCart.isPresent) { val cart = optionalCart.get() val product: Product = productServiceClient.getProduct(sku) if(product != null) { cart.products?.add(ShoppingCartItem(product.sku, product, 1)) shoppingCartRepo.save(cart) } return cart } else { null } } Controller Servicelayer Und hier geben wir dann dem Warenkorb das gefundene Produkt. Was sind denn • ShoppingCart • ShoppingCartItem • Product? Hier holen wir ein Produkt beim Productservice ab. Was macht denn der Service?
  9. @Entity data class ShoppingCart ( @Id var uuid: String?, var

    amount: Int?, @OneToMany(cascade = [ALL]) var products: MutableList<ShoppingCartItem>? ) Controller Servicelayer Was sind denn • ShoppingCart • ShoppingCartItem • Product? Achso. Das sind die Entities für den OR Mapper. Die nutzen wir aber auch im Client. Shopping Cart Was macht denn der Service?
  10. @Entity data class ShoppingCartItem( @Id var sku: String?, @OneToOne(cascade =

    [ALL]) var product: Product?, var quantity: Int? ) Aber ShoppingCartItem kommt doch im Modell gar nicht vor? Ja, das brauchen wir aber für den ORM wegen der Quantity Shopping Cart Product 0..n 1 Was macht denn der Service?
  11. DB-Repository -- schema.sql ALTER TABLE SHOPPING_CART_ITEM ADD CONSTRAINT max_quantity CHECK

    QUANTITY <= 10 Controller Servicelayer Von einem Produkt darf man doch nur maximal 10 haben. Wo wird das denn geprüft? Na hier. In der `schema.sql` Shopping Cart Ähem… Anna? Wo war noch mal der Constraint? DB-Repository Was macht denn der Service?
  12. BIG BALL OF MUD Wo wird noch mal der Warenkorbwert

    berechnet? Ein paar Wochen später:
  13. Domain Application Ports and adapters Product-Service DB • Fachlogik •

    Modell • “Das Herz der Software” • keine Technik • Domain Driven Design • Usecases • Komposition • Technische “Anhängsel” • Controller • Queue Publisher • SQL Client Das Hexagon - Die Schichten
  14. Reine Fachklassen- und Funktionen Product SKU Price ProductName simple Fachtypen:

    syntaktische- und semantische Validierung zusammengesetzter Fachtyp << Aggregate >> ShoppingCart Quantity AggregateRoot - Interface nach außen - alle Fachlogik - Invarianten - immer gültig! putProductInto amount quantityOfProduct checkMaximumProduct Count Nur diese Teile reden mit der Außenwelt! WICHTIG: Nach außen gegebene Objekte, dürfen keinen Einfluss mehr auf die Domäne haben! Kopieren! Die Domain
  15. data class Product(val sku: SKU, val price: Price, val name:

    Name) Product(SKU("132456"), Price(4, 99), Name("Brot")) data class SKU(val value: String) { private val regex = "[\\w\\d]{5,20}".toRegex() init { if(!regex.matches(value)) throw IllegalArgumentException("...") } } fun putProductInto(product: Product, quantity: Quantity): ShoppingCart { checkMaximumProductCount() val newAmount: ShoppingCartAmount = overallAmount + (product.price * quantity) val existingQuantity: Quantity? = cartItems[product] if(existingQuantity == null) { cartItems[product] = quantity } else { cartItems[product] = existingQuantity.copy(value = existingQuantity.value + quantity.value) } Die Domain
  16. Application Product-Service DB User Interface Infrastructure Domain Usecase Service Driven

    Port Die Application • Usecases. • Komposition der Ports und Domain. • Keine Fachlogik! Driver Port Product-Port ShoppingCart- Port
  17. Die Application ≪ Aggregate ≫ ShoppingCart putProductInto AppShopping CartService ≪

    port ≫ ShoppingCartService Aufrufer ≪ port ≫ ShoppingCart RepositoryPort Aufgeru- fener ≪ implements ≫ ≪ implements ≫ interface ShoppingCartService { fun putProductIntoShoppingCart(shoppingCartUuid: ShoppingCartUuid,...) ... } @Service class AppShoppingCartService(val shoppingCartRepositoryPort: ...): ShoppingCartService { override fun putProductIntoShoppingCart(shoppingCartUuid ...): Optional<ShoppingCart> { shoppingCartRepositoryPort.load(shoppingCartUuid) .map { shoppingCart -> shoppingCart.putProductInto(...) } ….
  18. Ports And Adapter Application Product-Service DB User Interface Infrastructure Domain

    Driver Adapters • Technische Frameworks • Keine Fachlogik! UI Ports and adapters
  19. Ports And Adapter Application Product-Service DB REST User Interface Infrastructure

    Domain Ports and adapters Driver Adapters HTTP SQL Driven Adapters REST UI
  20. Ports And Adapter Application Product-Service DB REST Driver Driven Domain

    Views Controller HTTP SQL REST Controller HTTP Client OR Mapper Ports and adapters
  21. Ports And Adapter ≪ Aggregate ≫ ShoppingCart putProductInto AppShopping CartService

    ≪ port ≫ ShoppingCartService ≪ port ≫ ShoppingCart RepositoryPort ≪ implements ≫ ≪ implements ≫ ≪ adapter ≫ ShoppingCartController ≪ adapter ≫ JPARepository ≪ Framework ≫ SpringBootMVC ≪ Framework ≫ Hibernate
  22. Hexagonale Architektur Application Product-Service DB REST Driver Driven Domain Views

    Controller HTTP SQL REST Controller HTTP Client OR Mapper Service Ports and adapters
  23. Klaus kommt neu ins Team Was bauen wir denn hier?

    Hier ist der Domainkern und hier die Tests dazu. Wow! Das ist echt sauber und verständlich. Die Umsysteme findest du unter Ports.
  24. Wann sollte ich es einsetzen? • Man hat generell eine

    Fachlogik (Domain) • Die Domain hat Invarianten • Viele Umsysteme, bzw. APIs • Verschiedene fachliche Sichten • Kein Fachmodell • Keine Invarianten • simples CRUD
  25. Vor- und Nachteile • Macht die Domainkomplexität handhabbarer, da zentralisiert.

    • Einzelne Schichten lassen sich besser testen. • Einführung eines neuen Umsystems ist einfacher. • Neue Kollegen sehen sich den Kern an und wissen, was passiert. Die Wahrheit steht im Code. Diesmal wirklich. ;-) • Overhead - Domainobjekte müssen oft in neue Modelle umgewandelt werden. • Manche Frameworks machen einem ein Strich durch die Rechnung. Aufwand von Sonderlocken hier: Hibernate und Jackson
  26. Vielen Dank! tarent solutions GmbH Rochusstraße 2-4 53123 Bonn Telefon:

    0228 54881-0 Telefax: 0228 54881-235 [email protected] www.tarent.de Christian Schmidt Softwareentwickler [email protected] @chrisIwanzik github.com/DarkToast