Successfully reported this slideshow.

Domain Driven Design 101

35,234 views

Published on

Domain Driven Design (DDD) is a topic that's been gaining a lot of popularity in both the Java and .NET camps recently. Entities, value types, repositories, bounded contexts and anti-corruption layers -- find out what all the buzz is about, and how establishing a domain model can help you combat complexity in your code.

Richard Dingwall is a .NET developer and blogger with a passion for architecture and maintainable code.

He is currently working at Provoke Solutions as lead developer on a six-month project introducing test-driven development (TDD) and domain-driven design (DDD) to a large ASP.NET ERP system.

An hour-long talk given at Wellington .NET user group, Sept 23 2009.

Published in: Technology, Business

Domain Driven Design 101

  1. Domain Driven Design 101<br />
  2. Agenda<br />Why<br />Building blocks<br />Repositories, entities, specifications etc<br />Putting it to practice<br />Dependency injection<br />Persistence<br />Validation<br />Architecture<br />Challenges<br />When not to use DDD<br />Resources<br />
  3. Software is complicated<br />
  4. We solve complexity in software by distilling our problems<br />
  5. publicboolCanBook(Cargocargo, Voyagevoyage)<br />{<br />doublemaxBooking = voyage.Capacity * 1.1;<br />if (voyage.BookedCargoSize + cargo.Size &gt; maxBooking)<br />returnfalse;<br /> <br /> ...<br />}<br />publicboolCanBook(Cargocargo, Voyagevoyage)<br />{<br />if (!overbookingPolicy.IsAllowed(cargo, voyage))<br />returnfalse;<br /> <br /> ...<br />}<br />DDD is about making concepts explicit<br />
  6. Domain Model<br />
  7. Ubiquitous language<br />
  8. publicinterfaceISapService<br />{<br />doubleGetHourlyRate(intsapId);<br />}<br />û<br />A poor abstraction<br />publicinterfaceIPayrollService<br />{<br />doubleGetHourlyRate(Employeeemployee);<br />}<br />ü<br />Intention-revealing interfaces<br />
  9. publicclassEmployee<br />{<br />voidApplyForLeave(DateTime start,<br />DateTime end,<br />ILeaveService leaves)<br /> {<br /> ...<br /> }<br />}<br />
  10. Domain Expert<br />
  11. Entities<br />
  12. Value Types<br />
  13. publicclassEmployee : IEquatable&lt;Employee&gt;<br />{<br />publicbool Equals(Employee other)<br /> {<br />returnthis.Id.Equals(other.Id);<br /> }<br />}<br />Entities are the same if they have the same identity<br />publicclassPostalAddress : IEquatable&lt;PostalAddress&gt;<br />{<br />publicbool Equals(PostalAddress other)<br /> {<br />returnthis.Number.Equals(other.Number)<br /> && this.Street.Equals(other.Street)<br /> && this.PostCode.Equals(other.PostCode)<br /> && this.Country.Equals(other.Country);<br /> }<br />}<br />Value Types are the same if they have the same value<br />
  14. publicclassColour<br />{<br />publicint Red { get; privateset; }<br />publicint Green { get; privateset; }<br />publicint Blue { get; privateset; }<br /> <br />publicColour(int red, int green, int blue)<br /> {<br />this.Red = red;<br />this.Green = green;<br />this.Blue = blue;<br /> }<br /> <br />publicColourMixInTo(Colour other)<br /> {<br />returnnewColour(<br />Math.Avg(this.Red, other.Red),<br />Math.Avg(this.Green, other.Green), <br />Math.Avg(this.Blue, other.Blue));<br /> }<br />}<br />Value Types are immutable<br />
  15. Aggregates<br />
  16. Aggregate root<br />*<br />
  17. Repositories<br />
  18. publicinterfaceIEmployeeRepository<br />{<br />EmployeeGetById(int id);<br />void Add(Employeeemployee);<br />void Remove(Employeeemployee);<br /> <br />IEnumerable&lt;Employee&gt; GetStaffWorkingInRegion(Regionregion);<br />}<br />Repositories provide collection semantics and domain queries<br />
  19. Domain Services<br />
  20. publicinterfaceITripService<br />{<br />floatGetDrivingDistanceBetween(Location a, Location b);<br />}<br />
  21. Specifications<br />
  22. classGoldCustomerSpecification : ISpecification&lt;Customer&gt;<br />{<br />publicboolIsSatisfiedBy(Customer candidate)<br /> {<br />returncandidate.TotalPurchases &gt; 1000.0m;<br /> }<br />}<br /> <br />if (newGoldCustomerSpecification().IsSatisfiedBy(employee))<br />// apply special discount<br />Specifications encapsulate a single rule<br />
  23. Specifications can be used…<br />to construct objects<br />
  24. var spec = newPizzaSpecification()<br /> .BasedOn(newMargaritaPizzaSpecification())<br /> .WithThickCrust()<br /> .WithSwirl(Sauces.Bbq)<br /> .WithExtraCheese();<br /> <br />var pizza = newPizzaFactory().CreatePizzaFrom(spec);<br />Constructing objects according to a specification<br />
  25. Specifications can be used…<br />for querying<br />
  26. publicinterfaceICustomerRepository<br />{<br />IEnumerable&lt;Customer&gt; GetCustomersSatisfying(<br />ISpecification&lt;Customer&gt; spec);<br />}<br />vargoldCustomerSpec = newGoldCustomerSpecification();<br /> <br />var customers = this.customerRepository<br /> .GetCustomersSatisfying(goldCustomerSpec);<br />Querying for objects that match some specification<br />
  27. Anticorruption Layer<br />
  28. Your subsystem<br />Anti-corruption layer<br />Other subsystem<br />
  29. Any 3rd party system that I have to integrate with was written by a drunken monkey typing with his feet.<br />Oren Eini aka Ayende<br />
  30. Bounded Context<br />
  31. publicclassLead<br />{<br />publicIEnumerable&lt;Opportunity&gt; Opportunities { get; }<br />publicPerson Contact { get; }<br />}<br />publicclassClient<br />{<br />publicIEnumerable&lt;Invoice&gt; GetOutstandingInvoices();<br />publicAddressBillingAddress { get; }<br />publicIEnumerable&lt;Order&gt; PurchaseHistory { get; }<br />}<br />publicclassCustomer<br />{<br />publicIEnumerable&lt;Ticket&gt; Tickets { get; }<br />}<br />
  32. Dependency Injection<br />
  33. publicinterfaceINotificationService<br />{<br />void Notify(Employeeemployee, string message);<br />}<br />An interface defines the model<br />publicclassEmailNotificationService : INotificationService<br />{<br /> void Notify(Employeeemployee, string message)<br /> {<br />var message = newMailMessage(employee.Email, message);<br />this.smtpClient.Send(message);<br /> }<br />}<br />Far away, a concrete class satisfies it<br />
  34. publicclassLeaveService<br />{<br />privatereadonlyINotificationService notifications;<br /> <br />publicLeaveService(INotificationService notifications)<br /> {<br />this.notifications = notifications;<br /> }<br /> <br />publicvoidTakeLeave(Employeeemployee, DateTime start,<br />DateTime end)<br /> {<br />// do stuff<br /> <br />this.notifications.Notify(employee, &quot;Leave approved.&quot;);<br /> }<br />}<br />Dependencies are injected at runtime<br />
  35. Persistence Ignorance<br />
  36. …ordinary classes where you focus on the business problem at hand without adding stuff for infrastructure-related reasons… nothing else should be in the Domain Model.<br />
  37. publicclassCustomer<br />{<br />publicint Id { get; privateset; }<br />publicstringFirstName { get; set; }<br />publicstringLastName { get; set; }<br /> <br />publicIEnumerable&lt;Address&gt; Addresses { get; }<br />publicIEnumerable&lt;Order&gt; Orders { get; }<br /> <br />publicOrderCreateOrder(ShoppingCart cart)<br /> {<br /> ...<br /> }<br />}<br />Plain Old CLR Object (POCO)<br />
  38. [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName=&quot;AdventureWorksLTModel&quot;, Name=&quot;Customer&quot;)]<br /> [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)]<br /> [global::System.Serializable()]<br />publicpartialclassCustomer : global::System.Data.Objects.DataClasses.EntityObject<br /> {<br /> [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]<br /> [global::System.Runtime.Serialization.DataMemberAttribute()]<br />publicintCustomerID<br /> {<br />get<br /> {<br />returnthis._CustomerID;<br /> }<br />set<br /> {<br />this.OnCustomerIDChanging(value);<br />this.ReportPropertyChanging(&quot;CustomerID&quot;);<br />this._CustomerID = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value);<br />this.ReportPropertyChanged(&quot;CustomerID&quot;);<br />this.OnCustomerIDChanged();<br /> }<br /> }<br />privateint _CustomerID;<br />partialvoidOnCustomerIDChanging(int value);<br />partialvoidOnCustomerIDChanged();<br /> <br />This is not a POCO.<br />
  39. Architecture<br />
  40. Traditional Architecture<br />Presentation<br />Business Logic (BLL)<br />Infrastructure<br />Data Access (DAL)<br />
  41. Onion Architecture<br />User Interface<br />G<br />Application Services<br />M<br />Domain Services<br />Database<br />Domain Model<br />Services<br />File<br />system<br />Infrastructure<br />Tests<br />etc<br />
  42. Onion Architecture<br />EmployeeController<br />User Interface<br />G<br />Application Services<br />M<br />IEmailSender<br />Domain Services<br />Database<br />Domain Model<br />Services<br />File<br />system<br />Employee,<br />IEmployeeRepository<br />Infrastructure<br />Tests<br />SmtpEmailSender<br />etc<br />NHibernateEmployeeRepository<br />
  43. Validation<br />
  44. Validation Examples<br />Input validation<br />Is the first name filled in?<br />Is the e-mail address format valid?<br />Is the first name less than 255 characters long?<br />Is the chosen username available?<br />Is the password strong enough?<br />Is the requested book available, or already out on loan?<br />Is the customer eligible for this policy?<br />Business domain<br />
  45. publicclassPersonRepository : IPersonRepository<br />{<br />publicvoid Save(Person customer)<br /> {<br />if (!customer.IsValid())<br />thrownewException(...)<br /> }<br />}<br />validation and persistence anti-patterns<br />
  46. The golden rule for validation:<br />The Domain Model is always <br />in a valid state<br />
  47. publicclassNewUserFormValidator : AbstractValidator&lt;NewUserForm&gt;<br />{<br />IUsernameAvailabilityServiceusernameAvailabilityService;<br /> <br />publicNewUserFormValidator()<br /> {<br />RuleFor(f =&gt; f.Email).EmailAddress();<br /> <br />RuleFor(f =&gt; f.Username).NotEmpty().Length(1, 32)<br /> .WithMessage(&quot;Username must be between 1 and 32 characters&quot;);<br /> <br />RuleFor(f =&gt; f.Url).Must(s =&gt; Uri.IsWellFormedUriString(s))<br /> .Unless(f =&gt; String.IsNullOrEmpty(f.Url))<br /> .WithMessage(&quot;This doesn&apos;t look like a valid URL&quot;);<br /> <br />RuleFor(f =&gt; f.Username)<br /> .Must(s =&gt; this.usernameAvailabilityService.IsAvailable(s))<br /> .WithMessage(&quot;Username is already taken&quot;);<br /> }<br />}<br />separation of validation concerns with FluentValidation<br />
  48. Where validation fits<br />EmployeeController<br />User Interface<br />G<br />Application Services<br />M<br />IEmailSender<br />Domain Services<br />Database<br />Employee,<br />IEmployeeRepository<br />Domain Model<br />Services<br />NewUserForm,<br />NewUserFormValidator<br />IOverdraftLimitPolicy<br />File<br />system<br />Infrastructure<br />Tests<br />SmtpEmailSender<br />IUsernameAvailabilityService<br />etc<br />NHibernateEmployeeRepository<br />
  49. Making Roles Explicit<br />
  50. Challenges<br />
  51. When DDD isn’t appropriate<br />
  52. Benefits<br />
  53. Books<br />
  54. Links<br />Domain Driven Design mailing list<br />http://tech.groups.yahoo.com/group/domaindrivendesign/<br />ALT.NET mailing list<br />http://tech.groups.yahoo.com/group/altdotnet/<br />DDD Step By Step<br />http://dddstepbystep.com/<br />Domain Driven Design Quickly (e-book)<br />http://www.infoq.com/minibooks/domain-driven-design-quickly<br />
  55. Any fool can write code that a computer can understand. Good programmers write code that humans can understand.<br />Martin Fowler<br />
  56. Thanks for listening!<br />http://twitter.com/dingwallr<br />http://richarddingwall.name<br />rdingwall@gmail.com<br />

×