Pages

Thursday, October 13, 2016

Domain Driven Design Through Onion Architecture

architecturethis is a technique that i started using a few years ago and it's a techniquethat I feel since i started using it has increased my code quality dramaticallyin particular domain driven design was sort of the kickoff . for starting downthis path but it wasn't really until I started learning onion architecture inconjunction with domain driven design that i really started to feel like mycode quality was improving dramatically suddenly things were far more readablefar more understandable and far easier to maintainso throughout this discussion we're going to use the example of bowlingscore sheet if you've ever been to a bowling alley you may recognize one ofthese images or you may have seen something similar to them at the veryleastso it's an example that most people will probably be able to look at andunderstand to some extent it's also something that's got a reasonable amountof depth to it so it's a good example to choose for exploring domain-drivendesignso what is domain-driven design while to answer that first we have to answer thequestion what is a domain the domain is a sphere of knowledge for most softwaredomain refers to the business knowledge that your software is trying to modelok so based on that what is domain-driven designthis is a technique that emphasizes cooperation between technical expertsand domain experts domain experts can also be called business expertsthese are the people who know about the business rules that you are trying tomodel and this could be people like marketing specialists account executivesoffice managers etc they may or may not have any technical knowledgethese are the people like I said who have knowledge of the business theydon't necessarily have knowledge of the technology now when dealing with thedomain experts as a technical expert is very important to have a language thatyou can communicate in so that everybody understands what you're talking aboutandcall that language the ubiquitous language the ubiquitous languagebasically is taking the real world business rules and the real worldbusiness items and reflecting them directly in codeit's it's a shared language that can be used by both the developers and thedomain experts and everybody will understand what you what you'rereferring toso some examples of this might be things like a customer or a purchase or in ourbowling example it could be a player or bowling pinso let's talk more specifically about our bowling domainwell who r domain experts in the bowling example this is going to be people likethe alley staff or the players if there is a professional bowlers involved thatmay include coaches and it could even include the maintenance staffwell then what is the ubiquitous language in our bowling domainwell there's going to be a lot of terms in a bowling domain but let's just givea couple of examples i mentioned before things like player and pinthere's also a ball spare strike split pin center and again these are termsthat anybody who has knowledge of the domain will be able to understandso if we write our code using these terms then we will be able to speak tothose domain experts about our code and have them understand what we are tryingto say one of theproblems when trying to model the domain is that oftentimes domains can be quitelarge so even if we look at our bowling domain there's a lot going on therepotentially you have all the terms that i mentioned like pin and player etc butif you start expanding that to the bowling business then you run intosituations where ok now I've got a deal with customers as well because we've gota cellwe've got to sell products to our customers and then you also have to dealwith other things like you may have maintenance concerns that have to bedealt with and all of that gets very cumbersome and very difficult to dealwithso what we do is we split our domain into something called bounded contextand bounded context allow you to subdivide the larger domain into smallerdomains each of which can have their own ubiquitous language and their own modelsome concepts may be shared between the bounded context but they may actuallylook very different from one context to the nextso looking at our bowling example we might have a bounded context just forscoring and so this is the bounded context that we've chosen to use as ourexample but a scoring bounded contacts deals with only a single gamenow you may have a league of bowlers who need to keep track of their statisticsacross multiple games and so for that you might have a separate statisticsbounded context then you also have to have lain reservations so that might beanother bounded context which might also be separate from the sales boundedcontext and then we could also have a maintenance contextone of the keys to this is that some of the concepts in one context might appearin another contextso a player for example may appear in the scoring context but it may alsoappear in the statistics context however that the player in a scoring context maylook very different from the player in a specific statistics context and therules surrounding that player might be very differentnow when building a domain using domain-driven design there's differentarchitectures you can use theirsfor example the traditional layered architecture and the traditional layeredarchitecture has multiple layersusually there's at least three layers that are present that and then those maybe subdivided into into smaller layers as well so the two major or sorry thethree major layers that are generally present is the presentation layer andthen the business or domain layer and then the data access layerthe basic idea though is that your user interfaces on the top and your databaseson the bottom with the domain in between onion architecture takes a slightlydifferent approachI don't want to be clear here and say that you can use traditional layeredarchitecture to build applications using domain-driven design you can use otherarchitectures if you want to build using domain-driven design onion architecturejust happens to be one of the choices that you could make and it happens to bea reasonably good oneso onion architecture I have also heard it called other things like ports andadapters and hexagonal architecturerealistically I think ports and adapters and hexagonal architecture are supersets onion architecture is sort of a subset of those those otherarchitectural styles you can and as I said domain-driven design and our onionarchitecture are not necessarily the same thing you can you could actuallypotentially use onion architecture with out using domain-driven design althoughsome of the concepts of domain driven design obviously have to be present inthe domain layer of the onionthis is a another simplified is a designthere are other onion architecture diagrams that you will see which willpresent additional layers much like with the layered architecture each layer canbe subdivided into into smaller piecesthe other thing that I want to mention is that I i have traditionally seen thisas consisting of four layers the infrastructurethen there is a services layer that or application services layer and then adomain layer and a core layer now I personally don't like the termapplication services and the reason for that is because domain-driven designalso has a concept of a service and it is subtly different from the concept ofan application service so it gets confusing when you're trying to talkabout the the services and you're not you have to always qualify to say I am Italking about the application services or am I talking about the domainservices so i think it's more straightforward to simply call thatservices layer or that application services laterapi and I think it also represents what that layer is trying to doone of the things that I like to do when building my code is that I like torepresent these layers in through the use of packages or libraries so in mycode i will have a package named API and a package name domain a package namecore and a package name infrastructurethe other thing that i should point out is the arrow that points from theinfrastructure to the corethat represents the fact that each layer can see the underlying layers but theinner layers have no visibility or knowledge of the outer layers so whenbuilding using onion architectureyou start off with the centerpiece the core and the core is basically thebuilding blocks that you use to build your applicationit's not specific to any particular domain or technology it includes thingslike lists Maps in skala you might have case classes you might have actorslenses but these are not necessarily I mean I mentioned a lot of scallopedterms just because that's what I'm used to but this this can be done in otherlanguages as well you can do it in c-sharp you can even do it innon object-oriented languages although in an object-oriented language you haveto get a little bit more creative with how you define things but it is stillpossibleone thing to point out is that the core will never include technologicalconcepts like rest or SQL or databasethese are these are concepts that do not belong in the core and the other thingis that the core in fact can have no knowledge of any of the other layers soit doesn't know anything about your domain it doesn't know that your domaineven exists or that your API exists or that your infrastructure existalright so i'm going to skip one layer and go one layer higher to the API andI'll explain why in a moment but the API is basically the entry point into yourdomain and it should use domain termsit should also only expose immutable objectsthe reason for this is because it prevents people from using the API togain access to a back door to the domain so if you had returned mutable objectsthrough your API then you're the people using that code could gain access toparts of the domain that you perhaps didn't intend to expose by providingonly immutable objects it prevents them from manipulating the domain except inthe ways that you intendedthe API has access to the core and the domain but it doesn't know anythingabout the infrastructurenow the reason that I skip to this layer is because often this is where i willstart writing my code so i will write an API method usually just a skeleton andthen i'll write a high-level functional tests around that method once I've donethat then I'll start fleshing out all of the logic that I need in order to makethat high-level functional tests pass and that will drive me to flesh out thedomain underneathI'm a at this point while I'm writing the API I may write a few smallskeletons in the domain so i may just have a couple of quick class definitionsmaybe even a method definition or two but i likely won't have any logic inthem at this pointthat will be when i start fleshing out the domain and writing the test aroundthe domain that i will add the logicso let's talk about our bowling API what's that going to look likewell it's good probably going to have some methods in it like create gameadd or remove player get players set Penn State skip player set lanethese are all things you might find in the bowling API layernow let's talk about the domain so stepping down one layer from the APIagain I'll classes and methods in the domain must be or should be namedaccording to the ubiquitous languageso this is where you're going to see classes that are named things likeplayer or game or pinthere are some exceptions to that there are some domain driven design specificterms like repository or service and you may see them present in here as well butgenerally speaking you should try to name what all of your objects in yourdomainaccording to the ubiquitous languagethe key thing to point out here is that in the domain is where all of yourbusiness logic goesso if you have any business rules that they belong herethey don't belong in your database they don't belong in your API they belong inthe domainthe reason for that is that by controlling your domain through the APIand by putting all of your business logic into the domain that means thatyour application is portable you can extract any of the technology bitswithout losing any of your business logicso let's talk about our bowling domain we've talked about a lot already butlet's talk about it more specifically in the context of us are scoringapplication so in our scoring application or in our scoring contextwill have terms like game player in frame strike spare these are all aspectsof the scoring contextnow there are other bowling terms that would appear in the overall bowlingdomain but may not appear in the scoring contextthis would include things like pinsetter customer lane reservation then there arealso non domain terms these are things that would belong in the infrastructureand that is things like user interface and database so in our domain forbowling we might have a class which would be called game and it might have amethod on it like add player and our player than might have a class calledplayer with a method called set nameso those are examples of some of these things you might see in a bowling to menow this isn't required but one of the things that I like to do when building adomain as i like to avoid having primitives in my domainso for example instead of having a player named be a simple stringI would have a container class 4 player named this provides me a home forvalidation logic if i ever need to add any and it also provides me strongtyping on those fields now itScala is actually a particular particularly good language for doingthis kind of modeling because of the presence of things like case classesit makes it very easy to make those little wrapper classesit becomes even easier when you start talking about implicit classes andimplicit methods now when building a domain we have a few building blocksthat we can draw upon this includes things like value objects entitiesaggregate roots repositories factories and servicesso let's talk about value objects value objects are immutable they have noidentity to out to value objects of the same type with the same attributes areconsidered to be the samethey're often used for things like message passing and in fact they areparticularly useful in the API to expose your domain concepts without necessarilyexposing the mutable aspects of the domainnext we have entities and entities are potentially mutablein an entity's case it is actually uniquely identified by an ID rather thanby its attributeswhat this means is that the state of the entity may change but if you have twoentities of the same type with the same ID then they're considered the sameregardless of what other attributes they have now an aggregate route is a anentity that binds together other entitiesone of the key features of an aggregate route is the external objects are notallowed to hold a reference to an aggregate roots child entitiesso if you need access to one of the aggregate roots child entities that youmust go through the aggregate routethe other thing is that all operations in general on the domain should wherepossible go through an aggregate Grootthere are some exceptions to this factory's repositories and services areexceptions but wherever possible if you can create if you can require that anoperation go through the agar your routethat's going to be better for your domain now repositoriesthese are actually one of the more important concepts in domain drivendesign in my opinion and the reason is because they have they helped toabstract away a lot of the storage concerns and such that could otherwisepollute your domainrepeat now a repository could be a file-based it could be a database itcould be memory it could be rest api s or it could be any combination of thosethe important thing is that they're just a way to abstract away from some someform of storage now a repository should not be confused with the data store arepositories job is to store aggregate roots underneath that repositoriesimplementation may actually have to talk to multiple different storage locationsin order to construct the aggregate so a single aggregate route may be drawn froma REST API as well as a databaseas well as files and all of those might need to be read in order to constructthat a new route and so you may you may wrap those in something called the datastore but the repository is sort of a further layer of abstraction on top ofall of those individual data stores usually the repository is going to beimplemented as a trait or an interface and then the logic for the repository isgoing to be defined in the infrastructure the factories are similarto repositoriesnow in the case of factory they abstract away new object construction and afactory could potentially return an aggregate route or it could return anentity or perhaps a value objectoften times when you need a factory method for an aggregate root it will berolled into the repository so your repository might have a finder createmethod on it and again like a repository a factory is often implemented as atrait or an interface with the logic defined in the infrastructure nowmrs. are a little different in that a service is basically there to provide ahome for operations that don't quite fit into an aggregate routeso if you have a situation where you have a you have an operation and youcan't decide which aggregate route that goes into perhaps it actually operateson multiple aggregate roots or maybe it just doesn't fit into any of yourexisting regular routes in that case you can put that operation into a servicehowever we should not bewe should be careful not just to jump to putting everything into services if youfind yourself in a situation where you have an operation and you don't knowwhat aggregate route to put it into the first thing you should ask yourself isam I missing an aggregate route and try to figure out if perhaps there aredomain concepts that you haven't considered that should be brought intoyour domain so let's talk about the aggregate root for bowlingso in our bowling domain we have a few candidates for aggregate root out theremay be others but for the time being let's just narrow it down and say thatwe we're going to look at the possibility of player and game being anegative your room well how do we decide which one is the a group your routewell one question we can ask ourselves as which entities are going to bepresent in most or all of the API operationsis there a single entity or is there multiple we may have a situation wherethere's actually multiple entities present in multiple operations in whichcase we may actually have multiple aggregate roots on the other hand we maylook at our domain and say you know what this one entity is present in everysingle operation and that might make it a good candidate for an aggregate routethat doesn't necessarily guarantee it's the aggregate route but it's a goodcandidate anyway so let's look at our two examples player and game well someof the operations in our domain would include add player set lane set pinstate etcnow each of these is going to have to involve a game you need to add a playerto a game and you need to set the lane for a game and you need to set the PennState in that game so game is definitely an interesting candidate but let's notrule out player just yetso another question we can ask ourselves is which entity if deleted would resultin the deletion of some or all of the other entitieswell if we delete a player does the game still existswell we do know that a game can have multiple players so certainly deletingone player from the game is not going to delete the game but if we deleted allplayers from the gamethat certainly might delete the game so there's a possibility that deleting allthe players with the leave the gamewell let's flip that on its head if we delete the game does the player stillexists nowyour instinct might be to say yes the player does still exist even when thegame does not but the key here is not to confuse a person and a playerso yes if we delete a game the person still exists but if they're not assignedto a gameare they really a player i would probably say no and therefore i wouldsay that if you delete the game then you delete the player as well and because ofthat i would say that game is a very good candidate for next for an aggregateroutenot good enough candidate that if i were to build this right now i would probablystart with that and see if i have to add extra aggregate roots later or see if itstarts to get more complex and that's one of the other keys is as you make asyou take the steps through this if you ever reach a point where somethingstarts to feel awkwardthen it might be time to reevaluate your domain and deter determine are youmissing an aggregate route or have you perhaps chosen the angry but for now wecan just say that the game is the groupalright so let's step into the infrastructurenow the infrastructure is the outermost layer of the onion and theinfrastructure includes adapters for various things like databases userinterfaces and external services like rest api etc it will have access to allareas of the API the domain and the corealthough generally speaking most operations accessing the domain shouldgo through the API the exception to this would include things like domaininterfaces that have infrastructure implementationsso speaking of our bowling infrastructurewell we've got concepts like a pin center now we've talked about thepinsetter as a domain concept in bowling but keep in mind that we are limitingourselves to a very specific context and that context doesn't necessarily requirea pin center so in our domain a bowling expert will know what a pin setter doesand in the overall bowling domain perhaps it belongs therebut from a score keeping perspective you don't really care how the pins get resetin reality you don't even care if they get reset as long as someone tells youwhether the pins are up or down you can calculate the scores and it doesn'treally matter too much how they got thereso in our scorekeeping context we can consider the pinsetter to be a part ofthe infrastructureeven though it might be part of the overall bowling domainanother example of bowling infrastructure might be something likethe user interface for the lane screens we saw in the beginning there were fourdifferent user interfacesnow we shouldn't have to be tied to those user interfacesif if we want to swap out a different user interface that shouldn't require usto affect anything in our API or our domain and so that's why that belongs inthe infrastructureand database is another good example we might start out with an SQL database andthen something changes we decide to use Mongo or Cassandra or we may even decidethat we don't want a database at all we want to file system and we should betied to those in any way inside the domain or the API and so again that'swhy the database layers belong out in the infrastructureok so let's put this all together now so assuming we've built our bowling domainand we've done itwell using a domain driven design and onion architecture architecture conceptslet's go through a few different scenarios so what happens as i justmentioned what happens if a new database technology comes along that you feelwould be better suited to your requirements for a bowling applicationare you free to use it or if you tied yourself to the technologyanother question we could ask ourselves is what happens if a company wants youwant to use your score keeper application but for whatever reason theydon't have automatic pinsetters maybe they use people to reset the pinsperhaps they have their in this nostalgic bowling alley and they want todo everything manually so they have people doing all that work whileare you going to be able to simply drop the portions of the infrastructure thatare tied to pin setting and continue to use your domain and your API or have youtied yourself to the notion of the pinsetter well what happens if that samecompany perhaps requires that bowlersor maybe those human pin setters I have to input the state's manually of thepins so that scores can be calculatedcan we adapt to this or let's go way out on a limb and say what if you're bowlingsystem became so popular that a video game publisher asked you to develop abowling game for themwould you have to completely redo implement the scoring system or couldyou relatively easily reuse that existing code if we have carefullyfollow you followed domain-driven design and onion architecture principlesthen it should be maybe not trivial but it really should be possible to do allof these things