Domain-Driven Design in Rust-Projekten: Wenn Typen zu Ihren Verbündeten werden
Haben Sie jemals eine Rust-Anwendung geschrieben, in der die Geschäftslogik in einem Meer von Strukturen, Traits und Makros unterging? Ein bekanntes Gefühl, wenn Sie beim Betrachten des Codes nicht auf Anhieb sagen können, was eine bestimmte Funktion im Kontext der Fachdomäne tut? Wenn ja – willkommen im Club. Domain-Driven Design (DDD) soll dieses Problem lösen, und Rust mit seinem Typsystem ist ein ideales Spielfeld für die Umsetzung von DDD-Prinzipien.
In diesem Artikel werden wir Evans Buch nicht nacherzählen. Stattdessen zeige ich Ihnen, wie Sie Domain-Driven Design in Rust-Projekten anhand realer Beispiele anwenden. Sie erfahren, wie Sie mit Wrapper-Typen, modularer Architektur und ausdrucksstarken Strukturen den Domänencode sauber, sicher und verständlich gestalten. Los geht's!
Warum Rust und DDD ein ideales Paar sind
Domain-Driven Design erfordert eine strikte Isolierung der Domänenlogik von der Infrastruktur. Rust bietet dafür leistungsstarke Werkzeuge:
- Typsystem – ermöglicht die Modellierung von Geschäftsregeln auf Compiler-Ebene. Ein Fehler? Der Compiler lässt ihn nicht zu.
- Ownership und Borrowing – garantieren, dass Aggregate und Entitäten nicht versehentlich an unerwarteter Stelle geändert werden.
- Modulsystem – trennt klar die Domänenschicht, Infrastruktur und Anwendung.
Aber es gibt auch Fallstricke. Rust verzeiht keine „fetten" Objekte mit viel mutablem Zustand. Daher erfordert DDD in Rust einen anderen Ansatz zur Modellierung als beispielsweise in Java oder C#.
Kernidee: Machen Sie ungültige Zustände nicht darstellbar
In DDD streben wir danach, dass Geschäftsregeln in der Datenstruktur selbst kodiert sind. In Rust wird dies zum Mantra: „Wenn ein Zustand unzulässig ist – machen Sie es unmöglich, ihn zu erzeugen."
Anstatt zu schreiben:
struct User { name: String, email: String, age: u8,}Erstellen wir Typen, die bereits zur Compile-Zeit Korrektheit garantieren:
pub struct Email(String);pub struct Age(u8);pub struct UserName(String);
pub struct User { name: UserName, email: Email, age: Age,}Scheinen das nur Wrapper zu sein? Tatsächlich ist dies der erste Schritt zu DDD in Rust, wo Typen zur Dokumentation und zum Wächter von Invarianten werden.
Modellieren der Domäne: Value Object und Entity
Die zentralen Konzepte von DDD sind Entity und Value Object. In Rust werden sie unterschiedlich implementiert.
Value Object in Rust: Unveränderlichkeit und Wertgleichheit
Ein Value Object hat keine Identität. Zwei Objekte sind gleich, wenn ihre Felder gleich sind. In Rust passt dies perfekt zu Strukturen mit #[derive(PartialEq)] und unveränderlichen Feldern.
use std::fmt;
#[derive(Debug, Clone, PartialEq)]pub struct Money { amount: i64, currency: Currency,}
#[derive(Debug, Clone, PartialEq)]pub enum Currency { USD, EUR, RUB,}
impl Money { pub fn new(amount: i64, currency: Currency) -> Self { // Geschäftsregel: Geld darf nicht negativ sein assert!(amount >= 0, "Amount must be non-negative"); Self { amount, currency } }
pub fn add(&self, other: &Money) -> Result<Self, String> { if self.currency != other.currency { return Err("Cannot add different currencies".into()); } Ok(Self::new(self.amount + other.amount, self.currency.clone())) }}Beachten Sie: Money ist unveränderlich. Jede Operation gibt ein neues Objekt zurück. Dies entspricht der Philosophie von DDD in Rust: keine Setter, nur Methoden, die neue Instanzen erstellen.