DDD Voorbeeld: Zonnestelsel simulatie

 
10 februari 2008

Ik ben in de vrije uurtjes bezig geweest met het uitwerken van een voorbeeldapplicatie om een domein gedreven stijl te illustreren. De case die ik zal uitwerken in deze post is een applicatie om zonnestelsels te simuleren. Het doel is om een systeem met een centrale ster en een instelbaar aantal omringende (random geplaatste) planeten te laten ‘draaien’. Hierbij moet rekening gehouden worden met de normale natuurwetten, te weten:

– F = M * a (3e wet van newton)
– Fg = M1 * M2 / d^2 (Graviteitsformule)

Dat gaat een pittig model worden zou je denken. Dat blijkt mee te vallen; door slim om te gaan met collaboration kunnen we dit probleem met twee eenvoudige domain classes oplossen. Ik introduceer daartoe twee classes: HeavenlyBody (een class voor ster danwel planeet) en Universe (een composiet van hemellichamen en natuurwetten).

Allereerst is het belangrijk om te beseffen dat alle planeten en de ster krachten op alle andere planeten en ster uitoefenen. In een volledig lege ruimte is er namelijjk aantrekking tussen lichamen – en dat is dan ook meteen de enige soort kracht die er speelt.

Model
Wat we dus nodig hebben is een class voor hemellichamen, maar tevens een class die een referentie heeft naar alle hemellichamen. In dit geval kies ik voor een class Universe en een class Heavenlybody. Aangezien sterren en planeten beide gelijk gedrag vertonen kan ik hier net zo goed eenzelfde class voor gebruiken. In pseudocode zijn de belangrijkste methoden:

HeavenlyBody.Move()
{
this.position.Add(GetForceVectorAt(this.position,this.mass));
}

Universe.GetForceVectorAt(Point p, int Mass)
{
foreach(HeavenlyBody body in bodies)
force += CalculateForceBetweenMasses(body.Mass, Mass, body.position, p);
}

Universe.TimerPassed()
{
foreach(HeavenlyBody b in bodies)
b.Move();
}

Door inzet van deze drie simpele methoden kunnen we in onderstaand model een collaboratie samenstellen die onze simulatie gaat draaien. Ik heb voor het gemak twee hulpclassen “Point” en “Vector” toegevoegd die enkele makkelijke methoden bevatten om vectoren op te tellen en dergelijke. Dit zijn typisch value objecten (of toch niet… –> daar kom ik nog op terug).

Clock
Zoals in de plaatjes te zien is zijn er koppelingspunten voor een klok. Er is een Universe.TimerPassed() methode die het model herevalueert en de nieuwe posities van de hemellichamen bepaalt. Voor de hand licht om hier een extern component voor te introduceren (hoewel we wellicht Einstein hier nog op moeten naslaan :) ). Ik ga er in elk geval vanuit dat een clock infrastructureel van aard is en het model op gezette intervallen prikkelt.

UI
Om de simulatie te kunnen volgen, starten en eventueel te manipuleren stel ik het volgende scherm voor:

Aangezien we de UI lostrekken van ons model laat ik het formpje een aantal events afvuren die vervolgens door een bootstrapper gekoppeld kunnen worden. Ik maak wel het te draaien Universe bekend (geef mee via constructor) zodat de UI zelf kan subscriben op alle Moved events van de planeten. Daarnaast zijn er dus een aantal events die door de UI afgevuurd kunnen worden (zie ook de knoppen in het screenshot).

Database
Om de cirkel rond te maken moet het mogelijk zijn een model in een database te bevriezen. Ik kies in dit geval voor een O/R bridge die ik in het verleden zelf heb geschreven: UltralightORMapper. Deze bridge werkt zonder handmatige mappings en generereert automatisch een DB voor een gegeven assembly.

Bootstrap
Nu is het nog zaak de boel aan elkaar te hangen – we hebben immers nu 4 losse projecten in de solution, en geen van deze vier is executeerbaar. Ik kies ervoor om de bootstrapping en lijm voor de eenvoud door een hosting console applicatie uit te laten voeren. Ik maak in deze applicatie een class “World” aan (lekker verwarrend in deze context :) ) die de benodigde onderdelen instantieert en events aan elkaar knoopt.

Voor de volledigheid post ik hier de volledige class.

public class World
{
Universe universe = Universe.Create(int.MaxValue, 500, 10, 0.01);
PlanetDemo.Components.Timing.
SynchronousClock clock = new PlanetDemo.Components.Timing.SynchronousClock(0);
Form1 form;
UniverseRepository rep;
public World()
{
rep =
new UniverseRepository();
form =
new Form1(universe);
form.Create+=
new CreationEventHandler(Creation);
form.StartStop +=
new EventHandler(form_StartStop);
form.Terminate +=
new EventHandler(form_Terminate);
clock.Tick +=
new EventHandler(clock_Tick);
universe.Tick +=
new EventHandler(universe_Tick);
form.ShowDialog();
}
void universe_Tick(object sender, EventArgs e)
{
form.clock_Tick(sender, e);
}
void form_Terminate(object sender, EventArgs e)
{
rep.Save(universe);
Application.Exit();
universe =
null;
clock.Dispose();
}
void clock_Tick(object sender, EventArgs e)
{
if(universe!=null)
universe.TimerPassed();
}
void form_StartStop(object sender, EventArgs e)
{
clock.Start();
}
Universe Creation(object sender,int planetcount)
{
universe.Tick -=
new EventHandler(universe_Tick);
universe =
Universe.Create(int.MaxValue, 500, planetcount, 0.01);
universe.Tick+=
new EventHandler(universe_Tick);
return universe;
}
}

Wat opvalt aan deze code is dat alle aanknopingspunten die we in de componenten hebben gedefinieerd hier aan elkaar geknoopt worden. Zo worden form events aan het domein geknoopt en vice versa, database acties gehangen aan het sluiten van de applicatie en de klok tick events aan de Universe.TimerPassed() methode gehangen. Een opvallende is dat de Universe class ook weer een tick event heeft waar dan de UI component weer op subscribed. Dit maakt dat we de UI ook volledig los gekoppeld hebben van de clock component.

Wat uiteraard nog rest is het beschikbaar maken van de source code: Bij deze.


Werken met ?
Kijk dan bij onze mogelijkheden voor zowel starters als ervaren engineers.


Categorieën: Development, .Net

Tags: ,


Reacties (4)

  • Koen Rouwhorst schreef:

    Een collega wees mij op dit artikel. Een goed voorbeeld, het heeft mij geholpen om het DDD beter te begrijpen. Bedankt!

    Geplaatst op 03 februari 2010 om 13:54 Permalink

  • Mooi voorbeeld! Thanks!

    Geplaatst op 11 februari 2008 om 22:40 Permalink

  • Jaap Taal schreef:

    In de pseudocode van de HeavenlyBody.Move methode wordt een kracht bij een position opgeteld. Dat kan natuurlijk niet, maar in de echte code wordt netjes rekening gehouden met de snelheid en massa van de HeavenlyBody.
    Ik ben benieuwd naar je punt over value objecten ja of nee.

    Geplaatst op 11 februari 2008 om 17:45 Permalink

  • Prachtige post met een inspirerend voorbeeld.

    Geplaatst op 11 februari 2008 om 9:48 Permalink