Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Domain Driven Design 101


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
  • Login to see the comments

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 /><br />ALT.NET mailing list<br /><br />DDD Step By Step<br /><br />Domain Driven Design Quickly (e-book)<br /><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 /><br /><br /><br />