Hexagonale Architektur - FrOSCon · Hexagonale Architektur Domain zentrierte Microservices Javaland...
Transcript of Hexagonale Architektur - FrOSCon · Hexagonale Architektur Domain zentrierte Microservices Javaland...
Hexagonale ArchitekturDomain zentrierte Microservices
Javaland 2019 - 19.03.2019
Christian Iwanzik@chrisIwanzik
Christian Iwanzik (33)SoftwareentwicklerDipl-Inf. (FH) - FH Köln
Steckenpferde:➢ JVM
○ Kotlin○ Scala○ auch java ;-)
➢ Domain Driven Design➢ Microservicearchitekturen
Seit 2010 bei tarent Solutions in Bonn
www.tarent.de
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
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!
… und dann kam die FachlogikMaximaler 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
0..n1
Controller
Servicelayer
DB-Repository HTTP Client
Klar: Hier im Service! Da ist die Logik!
Product
sku: String price: Int
ShoppingCart
products: Map <Product, Int>
Bob kommt neu ins TeamWas 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!?
Was macht denn der Service?fun putProduct(@PathVariable uuid: String, sku: String): ResponseEntity<*> { val skuRegex = "[\\w\\d]{1,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?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
ServicelayerUnd 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?@Entitydata 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.
ShoppingCart
Was macht denn der Service?@Entitydata 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
ShoppingCart Product
0..n1
DB-Repository
Was macht denn der Service?-- schema.sqlALTER 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 in der `schema.sql`
ShoppingCart
Ähem… Anna? Wo war noch mal der Constraint?
DB-Repository
Saubere Schichten. Verteilte FachlichkeitController
Servicelayer
DB-Repository HTTP Client
Formatvalidierung
Objektkomposition
Überprüfung von Invarianten
Ein paar Wochen später:
BIG BALL OF
MUD
Wo wird noch mal der Warenkorbwert berechnet?
Die IDee !
Erzähl mal!
Ich hab mal was von Hexagonaler Architektur gehört.
Das Hexagon - Die Schichten
Domain
Application
Ports
Product-Service
DB
● Fachlogik● Modell● “Das Herz der Software”● keine Technik● Domain Driven Design
● Usecases ● Komposition
● Technische “Anhängsel”● Controller● Queue Publisher● SQL Client
Die Domain
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
checkMaximumProductCount
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 Domaindata 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]{1,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) }
overallAmount = newAmount
return this}
Das Hexagon - Datenfluss
User Interface Infrastructure
Product-Service
DB
Domain
Application
Ports
Die Application
Application
Product-Service
DBUser Interface Infrastructure
Domain
CommandService
Report
Service
Usecaseinterface
Usecase interface
Repositoryport
Port
● Usecases.● Komposition der Ports und
Domains. ● Keine Fachlogik!
Die Application
≪ Aggregate ≫ShoppingCart
putProductInto
AppShoppingCartService
≪ interface ≫ShoppingCartService
Aufrufer
≪ interface ≫ShoppingCartRepositoryPort
Aufgeru-fener
≪ implements ≫
≪ implements ≫interface ShoppingCartService { fun putProductIntoShoppingCart(shoppingCartUuid: ShoppingCartUuid,...) ...}
@Serviceclass AppShoppingCartService(val shoppingCartRepositoryPort: ...): ShoppingCartService {
override fun putProductIntoShoppingCart(shoppingCartUuid ...): Optional<ShoppingCart> { shoppingCartRepositoryPort.load(shoppingCartUuid) .map { shoppingCart -> shoppingCart.putProductInto(...) } …. }
Ports And Adapter
Application
Ports
REST
UI
Product-Service
DBUser Interface Infrastructure
Domain
Driver Adapters
● Technische Frameworks● Keine Fachlogik!
Ports And Adapter
Application
Ports
Product-Service
DB
REST
User Interface Infrastructure
Domain
Driver Adapters
HTTP
SQL
Driven Adapters
REST
UI
Ports And Adapter
Application
Ports
Product-Service
DB
REST
Driver Driven
Domain
ViewsController
HTTP
SQL
RESTController
HTTP Client
OR Mapper
Ports And Adapter
≪ Aggregate ≫ShoppingCart
putProductInto
AppShoppingCartService
≪ interface ≫ShoppingCartService
≪ interface ≫ShoppingCartRepositoryPort
≪ implements ≫
≪ implements ≫
≪ adapter ≫ShoppingCartController
≪ adapter ≫JPARepository
≪ port ≫SpringBootMVC
≪ port ≫Hibernate
Hatten wir das nicht schon mal?ShoppingCartController
CommandService
JPARepository
top down
ShoppingCart Product
0..n1
Hexagonale Architektur
Application
Ports
Product-Service
DB
REST
Driver Driven
Domain
ViewsController
HTTP
SQL
RESTController
HTTP Client
OR Mapper
Service
Klaus kommt neu ins TeamWas 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.
Wie geht es weiter?
Product-Service
DB
Component
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
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 Sonderlockenhier: Hibernate und Jackson
● https://fideloper.com/hexagonal-architecture
● https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexa
gonal-onion-clean-cqrs-how-i-put-it-all-together/
● https://marcus-biel.com/hexagonal-architecture/
● https://web.archive.org/web/20060711220612/http://alistair.cockburn.us:80/index.php/Main_Page
Literatur
Beispiele:
https://gitlab.com/Iwanzik/hexagonal-service
https://gitlab.com/Iwanzik/muddy-service
12. April 2019 10 - 17 Uhr
Vielen Dank!