Aplikacja sklep internetowy. Podział na infrastrukturę i domenę

Featured image

Co zrobimy?

Podzielimy aplikację na domenę oraz infrastrukturę. Jest to ważne, bo oddziela biznesowe wymagania od technicznych detali. Ułatwia to pisanie samego kodu oraz umożliwia proste testowanie domeny w pamięci. W tym repozytorium zaczynając od brancha step-1-starter znajdziesz omawiane wycinki kodu.

Mikroserwisy

W services znajdziemy czysty kod Kotlinowy bez frameworka (Springa). Jak na razie mamy tylko jeden serwis product wraz z product-api. Jest on odpowiedzialny za tworzenie produktu oraz za zwrócenie listy produktów, czyli coś co posiada każdy sklep internetowy tyle, że uproszczone. Na początek znajdziesz tutaj setup jak zorganizować sobie kod.

code-structure

ProductAPI:

findProducts(query: ProductQuery) Zwraca produkty ze stronicowaniem (paging). Potrzebne jest to, aby nie zwracać wszystkich produktów do użytkownika.

createProduct(request: Valid<NewProductRequest>) Tworzymy nowy produkt. Wrapujemy również request o Valid (od Arrowa), aby ułatwić sobie walidację danych.

services/product-api

Ten moduł zawiera w sobie fasadę (wzorzec projektowy), czyli powyższy ProductAPI.kt. Jest to jednocześnie jedyna droga komunikacji pomiędzy innymi modułami. Wszystkie skomplikowane rzeczy dzieją się w domenie. A sama interakcja z domeną dzieje się właśnie przez ten interfejs. W product-api znajdziemy modele, które są dostępne dla korzystających z modułu ( publiczne). Możemy tu również mieć pomniejsze biblioteki takie jak Arrow, Guava, Jackson, ZalandoProblem oraz wiele, wiele innych. W kolejnej iteracji dodamy jeszcze zależność na support-domain. Jest to pojęcie wzięte z DDD i jest to po prostu moduł, którego używają inne moduły. Tworzymy tu generyczne rozwiązania, z których korzysta reszta naszych domen. Porobimy chociażby uprostszenia, rozszerzenia do Arrowa.

plugins {
    id "org.jetbrains.kotlin.jvm"
}
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

services/product

Implementacja powyższego. Dzieje się tutaj cała logika biznesowa.

plugins {
    id "org.jetbrains.kotlin.jvm"
}
dependencies {
    api project (":services:product-api")
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

infrastructure

W tym miejscu tworzymy RestControllera, a także bean naszego modułu product. W późniejszym etapie utworzymy tutaj dwa beany. Jeden będzie odpowiedzialny za faktyczną implementację, a drugi za implementację w pamięci (in-memory). Tutaj właśnie zachodzi interakcja ze światem zewnętrzynym (Cloud, Databases, Queues). Ponadto nic nie stoi na przeszkodzie w użyciu tutaj innego frameworka. Jeśli kiedyś będziemy chcieli odejść od Springa to będzie to zdecydowanie łatwiejsze, aniżeli w przypadku gdy kod Springowy jest powiązany z domeną. W naszym przypadku takiego powiązania nie ma przez co taka migracja jest w ogóle możliwa. Oczywiście im aplikacja jest bardziej rozwinięta tym trudnej jest wprowadzać takie zmiany niemniej sam podział jaki tutaj zrobiliśmy ułatwia nam wykonanie czegoś takiego.

plugins {
    id "org.jetbrains.kotlin.jvm"
    id "org.jetbrains.kotlin.plugin.spring"
}
dependencies {
    implementation project(":services:product")
    implementation project(":services:product-api")
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "com.fasterxml.jackson.core:jackson-core:$..."
    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$..."
    implementation "org.springframework.boot:spring-boot-starter-web:$..."
}

Ustawienia Gradle’a

settings.gradle

rootProject.name = 'online-store-clean-architecture'
include 'infrastructure'
include 'services:product'
include 'services:product-api'

gradle.properties

kotlin.code.style=official
# SPRING
versionSpringBoot = 2.2.1.RELEASE
# JSON
versionsJackson = 2.10.0

Projekt znajdziesz na Githubie.