hexagonale architektur...die domain reine fachklassen- und funktionen product sku price productname...

Post on 01-Jan-2020

5 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Hexagonale ArchitekturDomain zentrierte Microservices

Javaland 2019 - 19.03.2019

Christian Iwanzik@chrisIwanzik

c.iwanzik@tarent.de

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 wir 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 wir 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

Saube 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

CommandService

≪ interface ≫ServiceInterface

Aufrufer

≪ interface ≫Repository

Aufgeru-fener

≪ implements ≫

≪ implements ≫interface ServiceInterface { fun putProductIntoShoppingCart(shoppingCartUuid: ShoppingCartUuid,...) ...}

@Serviceclass CommandService(val shoppingCartRepositoryPort: ...): ServiceInterface {

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

User Interface Infrastructure

Domain

ViewsController

HTTP

SQL

RESTController

HTTP Client

OR Mapper

Ports And Adapter

≪ Aggregate ≫ShoppingCart

putProductInto

CommandService

≪ interface ≫ServiceInterface

≪ interface ≫Repository

≪ 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

User Interface Infrastructure

Domain

ViewsController

HTTP

SQL

RESTController

HTTP Client

OR Mapper

Srv

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

Beispiel:

https://gitlab.com/Iwanzik/hexagonal-service

Vielen Dank!

top related