Spring Boot ile Basit Bir DDD Projesi

Kendimi bildim bileli C# programlama dili ile geliştirme yapıyorum. Hobi amaçlı ilgilendiğim Rust, Zig ve OCaml dillerini saymazsak profesyonel iş hayatım .NET ekosistemi üzerinde geçti. Çok uzun yıllar önce ise Java programlama dilini öğrenmeye çalışmış ve bu vesile ile `Java ile 24 Kahve Molası` isimli bir seri yazmıştım. 2004 yılında yazdığım makaleleri derleyip bir doküman haline de getirmiştim ki buradan erişebilirsiniz. Tabii aradan yıllar yıllar geçti. Yazılım geliştirme dünyası sürekli değişti ama bazı temel kavramlar ve ilkeler hala aynı. Nesne yönelimli dil prensipleri, SOLID ilkeleri, tasarım kalıpları, yazılım mimarileri halen daha programlama dilleri ve platformlara referans oluyor. İş dünyası çözümleri ve kurumsal projeler bu felsefeler üzerine inşa ediliyor. Sözü fazla uzatmadan bu yazıda nelerden bahsedeceğimize gelelim.

Amacım Domain Driven Design(DDD) yaklaşımını yine hafifsiklet bir senaryo ile olabildiğince basit bir şekilde ele almak(CQRS, Event Sourcing, Message Bus gibi konuları sonraya bırakıyorum) ancak bu sefer Java programlama dilini ve Spring Boot Framework kullanacağım. Başlarken Java dilinde duayen olan arkadaşlarımın affına sığınıyorum zira bir C# geliştiricisi personasıyla olaylara yaklaşacağım. Dolayısıyla Java'nın ve Spring Boot'un bazı niş yeteneklerini atlayabilirim. Bu konuda yorumlarla bana ve okurlarımıza destek olabilirsiniz. Öyleyse başlayalım.

Senaryo

Bilgisayar oyunu kiralayabildiğimiz bir sistem üzerinde duracağız. Artık böyle bir devri yaşamıyoruz tabii ki ama zamanında Commodore 64 oyunları kiraladığımızı ve hatta bildiğiniz ya da bilmediğiniz doksanlık müzik kasetlerine oyunlar yüklettiğimizi hatırlıyorum. Öğrenme amaçlı kullanabileceğimiz keyifli bir senaryo olabilir. Tabii Domain Driven Design denince işin içerisine giren bazı önemli unsurlar var. Bunlardan birisi herkesin söz konusu çözüm için kullandığı ortak dil. Denizcilik alanında bir şeyler yazıyorsak kullanacağımız jargon ile oyun geliştirme sahasındaki jargon tamamen farklı olacaktır. Bu jargon genellikle Ubiquitous Language olarak anılır ve program kodu içerisindeki enstrümanların isimlendirilmesinde hayati önem arz eder. Tabii bunu oluşturmak için aslında kullanıcı hikayelerini detaylıca dinlemek ve senaryolaştırmak gerekir. Biz çok basit birkaç cümleyi baz alalım.

  • Oyunların her biri kataloğumuzda birer isim(Pacman, Super Mario,...) olarak varlığını sürdürür. Mağazamızda oyun kutularından n adet olabilir.
  • Sistemde abonelerimiz vardır(Bu demoda aboneleri sadece bir ID olarak tanımlayacağız)
  • Abonelerimiz oyun kiralayabilirler ancak bir kiralama süresi vardır. Kiralanan oyunlar belli bir sürede geri getirilir.
  • Abone, kiraladığı oyunu geri getirdiğinde kiralama sona erer ve oyun tekrar mağazada kiralanabilir hale gelir. Ama gecikme olursa bu gecikme süresi kadar ekstra ücret ödenir. Örneğin gecikilen gün başına 2.5 TL ceza ödemek gibi.

Bu cümlelerden aslında belli başlı iş kuralları da ortaya çıkar. Örneğin;

  • Mağaza stoğunda olmayan bir oyun kiralanamaz.
  • Oyun kiralandığında kullanılabilir oyun sayısı 1 azalır, geri getirildiğinde 1 artar.
  • Bir kiralama iki kez geri getirilemez.
  • Gecikme ücreti için şu formül geçerlidir: `gecikme ücreti = gecikilen gün sayısı * 2.5 TL`

Buradaki basit iş kuralları domain model içerisinde yaşar ve asla atlanamaz(No Bypass). Böylece iş kurallarımızın her zaman geçerli olmasını garanti edebiliriz. DDD yaklaşımında domain'i dış etkenlerden izole etmek çok önemlidir. Bana göre domain modelimiz first citizen' dir.

DDD Özeti

DDD daha çok iş modelini kodun tam merkezine yerleştirmeye odaklanmış bir yaklaşımdır. Bu sayede iş kuralları ve mantığı, programın her yerinde tutarlı bir şekilde uygulanabilir. Ayrıca framework, veritabanı ve benzeri altyapı detaylarını dışarıda tutarak iş modelinin bağımsız ve test edilebilir olmasını hedefler. Bu örnek özelinde ele alacağımız kavramları aşağıdaki tablo ile özetleyebiliriz...

Kavram Ne anlama gelir? Bizim örneğimizde neye karşılık gelir?
Ubiquitous Language Tüm ekip (Geliştiriciler, İş Analistleri, Test Uzmanları, vb.) tarafından paylaşılan ortak dil. Game, Rental, lateFee, returnOn vb
Value Object Kimliği olmayan, sadece değerleriyle tanımlanan nesneler. Değiştirilemezdirler. (Immutable) Money, GameId gibi
Entity Kimliği olan, yaşam döngüsü boyunca değişebilen nesneler. Game, Rental gibi
Aggregate Birbirleriyle ilişkili Entity ve Value Object'lerin bir araya gelerek oluşturduğu tutarlı bir bütün. RentalAggregate, GameAggregate gibi
Aggregate Root Aggregate'in dış dünya ile olan tek giriş noktasıdır. RentalAggregate'in root'u Rental, GameAggregate'in root'u Game gibi
Repository Aggregate'lerin saklanması ve erişilmesi için kullanılan koleksiyon benzeri aggregate yapıları. RentalRepository, GameRepository gibi
Domain Service Birden fazla Entity veya Value Object'in etkileşimde bulunduğu karmaşık iş mantığını içeren servisler. Bu örnekte ele almayacağız.
Application Service Bir transaction (Use Case) orkestrasyonunu yöneten servis. RentGameService, GameCatalogService gibi

Proje Mimarisi

Projede oldukça yalın düşünerek hareket edebiliriz ve katmanlı(layered) bir mimari modeli kullanabiliriz. Biraz ucundan da olsa hexagonal mimari ilkelerine göre inşa edebiliriz. Altın kural bağımlılıkların yönünün(Dependency Direction) doğru uygulanması. Örneğin domain paketi sıfır framework bağımlılığına sahip olmalı ki domain modelindeki iş kurallarını çok kısa sürelerde hiçbir dış framework bağımlılığı olmadan değiştirebilelim veya test edebilelim. Bu sayede domain modelimizi daha esnek ve sürdürülebilir bir hale getirebiliriz. Mimari kurguyu kabaca aşağıdaki şekilde olduğu gibi özetleyebiliriz.

Küçük bir not: Ben bu projeyi bir Linux platformunda(Ubuntu 26.04) geliştiriyorum ancak aynı prensipler diğer platformlar için de geçerlidir.

Proje İskeletinin Oluşturulması

Bir .NET geliştiricisi olarak normalde bir solution açıp projeleri tek tek içerisinde oluştururuz. İhtiyacımız olan bağımlılıkları ise projelere göre ekleriz. Java dünyasında bu tür işlemler ve özellikle bağımlılıkların en ideal şekilde tesis edilmesi uzun zamandır Spring Framework gibi araçlarla etkili bir şekilde yapılmakta. Benzer şekilde hareket edeceğiz. Spring Boot proje oluşturma aracı olan Spring Initializr adresini kullanarak uygulama iskeletini ve tüm gereklilikleri hazırlayabiliriz. Ama aynı işlemleri curl komutu ile terminalden de yapabiliriz.

Özellikle kendi sistemimizde hangi Java ve Maven versiyonları olduğuna dikkat etmekte yarar var. Zira versiyon parametleri uyumlu olmalı. Resimde görülen işlemleri curl komutu ile terminalden yapmak istersek de aşağıdaki gibi hareket edebiliriz.

curl https://start.spring.io/starter.zip \
  -d type=maven-project \
  -d language=java \
  -d bootVersion=3.5.15 \
  -d javaVersion=25 \
  -d groupId=com.example \
  -d artifactId=game-rental \
  -d name=game-rental \
  -d packageName=com.example.gamerental \
  -d dependencies=web,data-jpa,postgresql,validation,flyway \
  -o game-rental.zip

unzip game-rental.zip -d game-rental
cd game-rental

Dikkat edileceği üzere proje oluşturulurken bazı bağımlılıklar (dependencies) da ekledik. Hangisini ne için kullandığımızı özetleyelim.

Paket Ne işe yarar?
Spring Web REST API geliştirmek için gerekli olan Spring MVC altyapısını sağlar. Tomcat gibi gömülü bir web sunucusu içerir.
Spring Data JPA ORM (Object-Relational Mapping) işlemleri için gerekli altyapıyı sağlar. JPA (Java Persistence API) standartlarını kullanarak veritabanı işlemlerini kolaylaştırır.
PostgreSQL Driver PostgreSQL veritabanına bağlanmak için gerekli sürücüyü sağlar.
Spring Validation Bean Validation API'sini kullanarak veri doğrulama işlemlerini kolaylaştırır.
Flyway Veritabanı şemasını yönetmek ve sürüm kontrolü sağlamak için kullanılan bir araçtır. Migrations işlemlerini kolaylaştırır.

Proje Oluşturulduktan Sonra Bazı Ayarlamalar

Proje oluşturulduktan sonra PostgreSQL desteği için yeni bir flyway bağımlılığı (dependency) eklememiz de gerekebilir. Bunun için tüm proje ayarlarını içeren `pom.xml` dosyasına aşağıdaki bağımlılığı eklemek yeterli olacaktır. Daha önceden eklediğimiz bağımlılıklar da bu dosyada yer alır.

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-database-postgresql</artifactId>
</dependency>x

Eğer projeyi komut satırından oluşturduysak konfigurasyon dosyası(application.properties) YAML formatında oluşmamış olabilir. Bu dosyayı silip `application.yml` isimli yeni bir dosya oluşturup içeriğini aşağıdaki şekilde düzenleyebiliriz.(Dosya `src/main/resources` dizininde olmalıdır) Bu şart değil ancak yml formatında daha okunabilir ve hiyerarşik bir yapı sağladığı için tercih ediyorum. Tabii ki bu tamamen kişisel bir tercih.

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/gamerental
    username: gamerental
    password: secret

  jpa:
    hibernate:
      ddl-auto: validate
    open-in-view: false
    properties:
      hibernate:
        format_sql: true

  flyway:
    enabled: true

logging:
  level:
    org.hibernate.SQL: debug

Dikkat edileceği üzere veritabanı bağlantı bilgisi, hibernate ve flyway ayarları yapılıyor. Bu ayarlara göre JPA(Java Persistence API) nesneleri ORM tarafında hibernate kullanacak ve veritabanı şeması flyway ile yönetilecek. Ayrıca loglama seviyesini debug olarak ayarladık ki SQL sorgularını loglarda görebilelim(Bunu sadece development aşamasında açmak ama üretim ortamında kapatmak iyi bir pratiktir). Son olarak `ddl-auto` değerini validate olarak ayarladık. Bu sayede JPA, uygulama başlatıldığında veritabanı şemasını kontrol edecek ve eğer tablolar eksikse veya hatalıysa uygulama başlatılmayacaktır. Bu sayede veritabanı şemasının her zaman güncel ve doğru olmasını garanti edebiliriz.

Migration Hazırlıkları

Migration işlemleri için `src/main/resources/db/migration` klasöründe `V1__init.sql` isimli bir dosya oluşturalım ve içeriğini aşağıdaki gibi hazırlayalım. Bu basit çalışma için çoğul eki(plural) formatında isimlendirilmiş games ve rentals tabloları yeterli olacaktır.

CREATE TABLE games (
    id               UUID         PRIMARY KEY,
    title            VARCHAR(100) NOT NULL,
    platform         VARCHAR(50)  NOT NULL,
    total_copies     INT          NOT NULL,
    available_copies INT          NOT NULL
);

CREATE TABLE rentals (
    id                 UUID          PRIMARY KEY,
    game_id            UUID          NOT NULL,
    member_id          UUID          NOT NULL,
    rented_on          DATE          NOT NULL,
    due_on             DATE          NOT NULL,
    returned_on        DATE,
    status             VARCHAR(20)   NOT NULL,
    late_fee_amount    NUMERIC(10,2) NOT NULL,
    late_fee_currency  VARCHAR(3)    NOT NULL
);

CREATE INDEX idx_rentals_game_id ON rentals (game_id);

DDD tasarımında her aggregate kendi tutarlı bütünlüğünü korumaktan sorumludur. Genellikle bir aggregate diğer aggregate'lere ID ile referans verir. Bu nedenle herhangi bir foreign key constraint kullanmamıza gerek yok. Böylece aggregate'ler birbirlerinden bağımsız olur ve aggregate root'lar kendi aggregate'lerini yönetebilirler. Zaten foreign key constraint kullandığımızda aggregate'ler DB seviyesinde birbirlerinin yaşam döngüsüne bağımlı hale gelir ve bu DDD tasarımına aykırıdır.

Docker-Compose Olmadan Asla

Pek çok kişisel ve uzun soluklu çalışmamdan alışılageldiği üzere burada da bir docker-compose dosyası ile PostgreSQL veritabanını ve varsa başka gereklilikleri ayağa kaldırmayı tercih ediyorum. İşte `docker-compose.yml` içeriğimiz;

services:

  postgres:
    image: postgres:latest
    container_name: spring-postgres
    environment:
      POSTGRES_USER: johndoe
      POSTGRES_PASSWORD: somew0rds
      POSTGRES_DB: postgres
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql
    networks:
      - spring-network

  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: spring-pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: [email protected]
      PGADMIN_DEFAULT_PASSWORD: 123456
    ports:
      - "5050:80"
    depends_on:
      - postgres
    networks:
      - spring-network

  sonarqube:
    image: sonarqube:community
    container_name: spring-sonarqube
    restart: unless-stopped
    stop_grace_period: 1h
    ports:
      - "9000:9000"
    environment:
      SONAR_ES_BOOTSTRAP_CHECKS_DISABLE: "true"
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_logs:/opt/sonarqube/logs
      - sonarqube_extensions:/opt/sonarqube/extensions

volumes:
  postgres_data:
  sonarqube_data:
  sonarqube_logs:
  sonarqube_extensions:

networks:
  spring-network:
    driver: bridge

Haftasonu yapabileceğiniz türden olan bu tip çalışmalarda ideal bir pratik olduğunu düşünüyorum. İşiniz bitince konteynerleri kapatır veya silersiniz. Tavsiye edeceğim bir diğer alışkanlıksa temiz kod prensiplerine uymak ve teknik borcu en başından itibaren kontrol altına almak. Bu nedenle Sonarqube servisini de docker-compose içerisine ekledim. Sizden sonraki yazılımcıyı düşünerek kod yazın lütfen :D Konteynerleri ayağa kaldırmak için terminalden aşağıdaki komutu kullanabiliriz.

sudo docker-compose up -d

Uygulama Kodlarımız

Artık sırasıyla uygulama içerisindeki gerekli enstrümanları yazmaya başlayabiliriz. Tabii artık Java dünyasındayız. Efsane paket sistemi URL benzeri bir yapıya sahip. Bu proje açısından baktığımızda root paketimiz `com.example.gamerental` şeklinde. Diğer tüm alt modülleri bu paket altına açacağız. Kabaca aşağıdaki gibi bir dizin yapısı oluşturmamız gerektiğini ifade edebiliriz.

Yazıyı çok fazla uzatmaması için kod dosyalarının içeriklerini buraya eklemedim. Dolayısıyla sizi güzel bir mücadele bekliyor. Kodları ve projeyi buradan çekip inceleyebilir veya sıfırdan kendi senaryonuzla yazabilirsiniz. Kod dosyalarına olabildiğince yorum satırı ekledim umarım anlaşılır olur.

C# Geliştiricileri Açısından

Bu çalışma özelinde bir karşılaştırma tablosu da eklemek isterim. Kodları yazarken veya yazdıktan sonra işinize yarayabilir. En azından Java tarafında kullandığımız enstrüman veya kavramlar C# tarafında neye karşlık geliyor özetleyebiliriz.

Mesele .NET/C# Java/Spring Boot
Proje oluşturma dotnet new CLI Spring Initializr veya curl
Bağımlılık yönetimi .csproj içinde NuGet pom.xml içinde Maven
ORM Entity Framework Core Spring Data JPA + Hibernate
Migration EF Core Migrations Flyway
Value Object record record
Entity kimliği Dahili Guid desteği UUID sınıfı
Çalıştırma dotnet run mvnw spring-boot:run
Test Framework xUnit, NUnit, MSTest JUnit 5
Inheritance : operatörü extends
Interface uygulama : operatörü implements
Paket kavramı GameRental şeklinde namespace com.example.gamerental şeklinde package
Class Yerleşimi Bir dosya içinde birden fazla class olabilir Her class kendi java dosyasında olmalı
Opsiyonel değerler Nullable reference types, ? operatörü Optional<T> sınıfı


Bununla birlikte kodlarda Spring Boot dünyasından gelen birçok anotasyon(annotation) kullanımı var. Bunlarla ilgili bir eşleştirmeyi de aşağıdaki tabloda bulabilirsiniz.

Konu Spring/Java .NET/C#
Bir şeyin Entity olduğunu belirtmek @Entity Genelde bir Entity sınıfından türetmek
DB tarafındaki tabloyu belirtmek @Table [Table]
Bir entity alanının identity olduğunu belirtmek @Id [Key]
Bir entity alanının enum olduğunu belirtmek @Enumerated [EnumDataType]
Repository olarak kullanılacağını belirtmek @Repository Repository deseni ve DbContext
Service olduğunu belirtmek @Service Tam karşılığı yok sanırım
Transaction yönetimine dahil olduğunu söylemek @Transactional TransactionScope veya DbContext üzerinden Transaction yönetimi
Doğrulama (Validation) @Valid [Required], [Range] gibi Data Annotation'lar
Üst tür metodunu ezmek @Override override anahtar kelimesi
REST türünden bir controller @RestController [ApiController]
Request mapping @RequestMapping [Route]
Request body @RequestBody [FromBody]
Path variable @PathVariable [FromRoute]
HTTP Post, Get, Put, Delete gibi aksiyonlar @PostMapping, @GetMapping, @PutMapping, @DeleteMapping [HttpPost], [HttpGet], [HttpPut], [HttpDelete]


Arada kaçırdıklarım olabilir ancak Spring Boot çatısının birçok anotasyon ile işimizi kolaylaştırdığını söyleyebilirim. Sanıyorum ki boilerplate kod yazmak zorunda kalmıyoruz.

Çalışma Zamanı

Projeyi tamamladıktan sonraki olası sonuçlarımı paylaşarak devam edelim. Beni en çok düşündüren şeylerden birisi nasıl build alacağım veya çalıştıracağım idi. .NET tarafında dotnet, rust dünyasında cargo gibi tekil araçlara alıştığım için Java tarafındaki maven, gradle gibi araç çokluğu biraz kafa karıştırıcı olabiliyor. Ancak ne nihyatetinde hedefim projeyi çalıştırmak olduğundan detaylarda çok fazla boğulmadan ilerledim ve aşağıdaki maven komutu ile projeyi başlattım. Tabii postgresql konteynerinin çalışır durumda olduğundan emin olmalıyız ve söz konusu komutu projenin kök dizininde işletmeliyiz.

# Derleme için
./mvnw compile

# Doğrudan çalıştırmak için
./mvnw spring-boot:run

Başlangıçta `V1__init.sql` dosyasındaki SQL scriptleri çalışacak ve veritabanında gerekli tablolar oluşturulacaktır. Daha sonra API endpoint'lerine(interfaces katmanındaki Controller sınıflarımız) aşina olduğumuz HTTP isteklerini göndererek uygulama çıktılarını sorgulayabiliriz. İşte birkaç deneme;

Tabii işin gereği kiralanacak oyunlar lazım. Efsanelerden Super Mario'yu kataloğumuza ekleyerek testlere başlayabiliriz.

curl -X POST http://localhost:8080/api/games \
  -H "Content-Type: application/json" \
  -d '{
        "title": "Super Mario",
        "platform": "NINTENDO_SWITCH",
        "totalCopies": 5
      }'

ve şimdi de bir oyun kiralayalım. Tabii tam bir abonelik sistemimiz olmadığı için abone ID bilgisini kendimiz veriyoruz. Game ID değeri içinse bir önceki denemede eklediğimiz ID değerini kullanabiliriz.

curl -s -X POST http://localhost:8080/api/rentals \
  -H 'Content-Type: application/json' \
  -d "{\"gameId\":\"3c23c76e-3902-40c6-8424-366196f669ca\",\"memberId\":\"de4b0278-b1fd-4dc1-a05b-2509107fd49b\",\"rentalDays\":10}"

Şimdide kullanılabilecek oyun kopya sayısını kontrol edebiliriz.

curl -s http://localhost:8080/api/games/3c23c76e-3902-40c6-8424-366196f669ca

Abonemizin oyunu çok sevdiğini ve birkaç gün geç iade ettiğini düşünelim. Bunu simüle etmek için aşağıdaki gibi ilerleyebiriz.

# Önce iade tarihini bugünden 15 sonraya ayarlayalım ki gecikme ücreti oluşsun.
LATE_DATE=$(date -d "+ 15 days" +%F)

# Bu çağrı sonrasında bir gecikme ücreti oluşmasını bekliyoruz.
curl -s -X POST http://localhost:8080/api/rentals/040cf074-9fc2-4480-9cc5-10a258efe7df/return \
  -H 'Content-Type: application/json' \
  -d "{\"returnDate\":\"$LATE_DATE\"}"

# Son olarak oyun bilgilerini tekrar kontrol edelim. Kiralama sona erdiği için kullanılabilir oyun sayısının tekrar 5 olduğunu görmemiz lazım.
curl -s http://localhost:8080/api/games/3c23c76e-3902-40c6-8424-366196f669ca

Son olarak birde dükkana geri dönen bir oyunu tekrar döndürmek istediğimiz almamız gereken Conflict hatasına bakalım.

curl -i -s -X POST http://localhost:8080/api/rentals/040cf074-9fc2-4480-9cc5-10a258efe7df/return \
  -H 'Content-Type: application/json' -d '{}'

Testler

API noktaları sorunsuz çalışıyor gibi ama kurumsal çapta bir projenin olmazsa olmazlarından birisi de elbetteki birim testler(Unit Tests). Birim testler Code Coverage açısından önemlidir. Bu sayede kodun hangi parçalarının test edildiği yani üzerinden geçilerek sağlamasının yapıldığı yüzdesel olarak ölçülebilir ki bu kodun güvenilirliğinin bir garantisidir. Projemizdeki testler `test/java/com/example/gamerental` dizininde yer almaktadır. Örnek olması açısından Game ve Rental nesnelerinin domain bazlı iş kurallarını test ediyoruz. Projedeki tüm testleri çalıştırmak için kök dizindeyken aşağıdaki komutu kullanmamız yeterli.

./mvnw test

Sonarqube ile Kod Taraması

Dilerseniz birde yazdığımız kod tabanının röntgenini çekip projenin sağlık durumunu kontrol edelim. Öncelikle `docker-compose.yml` içerisinde tanımladığımız Sonarqube servisinin çalıştığından emin olalım. Ayarlarımıza göre `localhost:9000` adresine gidip Sonarqube arayüzüne ulaşabiliyorsak her şey yolunda demektir. Sonrasında aşağıdaki adımları takip edebilirz.

  • `Create a local project` ile ilerleyelim: Project display name ve Project key değerlerini game-rental olarak belirleyebiliriz. Bir değişiklik yoksa Main branch name değerini main olarak bırakabiliriz.
  • `Set up new code for project` adımında Follows the instance's default seçeneğini işaretleyip Create Project butonuna tıklayalım.
  • `Analysis Method` adımında birkaç seçenek görebiliriz. Bunlar genellikle uzak kod repolarına bağlanabileceğimiz alternatiflerdir. Biz local ortamda ilerlediğimiz için Locally seçimi ile devam edelim.
  • `Analyze your project` kısmında öncelikle bir token üretmemiz gerekiyor. Gerçek hayat senarylarında belirli sürelerde token değerinin değiştirilmesi istenir ancak bu eğitim projesinde Expires in seçeneğini No expiration olarak bırakabiliriz. Token değerini oluşturduktan sonra Continue tuşuna basarak devam edelim.
  • `Run analysis on your project` adımında projemizin Java tabanlı olduğunu belirtmemiz gerekiyor. Örneğimizde build tool olarak Maven kullanıldığı için onu işaretleyebiliriz.

Tüm bu adımları tamamladığımızda terminalden çalıştıracağımız bir komutumuz olacaktır. Bunu yine projenin root klasöründe işletmemiz gerekiyor(Token bilgisi az önce üretilen bilgidir ve siz denerken farklı bir token olacaktır)

mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
  -Dsonar.projectKey=game-rental \
  -Dsonar.projectName='game-rental' \
  -Dsonar.host.url=http://localhost:9000 \
  -Dsonar.token=sqp_859e5004eaccc5c0bb1b2a7f3ada6b5b0c26ce54

İşte projenin teknik değerlendirmesine dair ilk MR raporu. Her ne kadar Passed olarak işaretlense de birçok uyarı var.

Ya Sonrası?

Buraya kadar sabırla geldiyseniz ve benzer çıktılara ulaştıysanız sizi tebrik ederim. En azından bu haftasonunu iyi bir şekilde değerlendirmiş olduğunuzu düşünüyorum. Tabii ki bu çalışma benim gibi C# programcıları için başlangıç seviyesinde bir pratik. Dil bağımsız konuşmak gerekirse, DDD yaklaşımının gerçek hayat senaryolarında uygulanması sanıldığı kadar kolay da değil. Özellikle domain büyüdükçe, farklı domain'ler işin içerisine dahil oldukça, iş kuralları karmaşıklaştıkça kurgu giderek zorlaşabiliyor. Örneğin kim aggregate olmalı veya aggregate root olarak düşünülmeli, kim Value Object olmalı, kim Entity olmalı, hangi iş kuralları aggregate içerisinde yaşamalı, hangileri Domain Service içerisinde konuşlanmalı gibi sorulara cevap bulmak kolay değil. Bu nedenle DDD yaklaşımını öğrenmek için bolca pratik yapmak veya uygulandığı açık kaynak projeleri incelemek gerekiyor. Peki bu çalışma üzerine başka neler neler yapabiliriz?

  • Üye nesnesini gerçek bir aggregate' e dönüştürmek. Örneğin bir üye en fazla 3 kiralama yapabilir gibi bir senaryoyu ele almak.
  • Domain bazlı event'ler eklemek. GameRented, GameReturned gibi event'ler kurgulayıp örneğin bildirim göndermek veya iki aggregate üzerinden transactional bir senaryo kurgulamak.
  • İkinci bir bounded context ile çalışmak. Ödeme(Billing) olabilir mesela. Renting diye farklı bir bounded context ile de event'ler yardımıyla haberleşebiliriz. DDD'nin stratejik tasarımını sahada deneyimleriz.
  • Mapping operasyonları için farklı alternatifler aramak. Örneğin MapStruct gibi bir enstrümanı işin içerisine katabiliriz.

Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Orjinal repoya ve kodlara github üzerinden erişebilirsiniz

Yorum ekle

Loading