DrupalCon Amsterdam 2014: Models & Service Layers; Hemoglobin & Hobgoblins

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
walk onto my talk models and service layers hemoglobin and hobgoblins and I promise by the end of it the title will make sense my name is Ross tuck and my job is to keep you awake after lunch so a little bit about myself by way of introduction I'm an independent engineer coach consultant you can find me on Twitter and freenode as creatively well stuck I also have a website and a blog you get the idea okay so I'm here today to talk about a topic that's important for web developers no matter who you are what you do specifically hemoglobin now if you're not familiar with hemoglobin it's a substance in your blood and I mean you specifically in the 8th row you have it in your blood if you don't have enough of it or it doesn't work properly you probably have a medical condition known as anemia because hemoglobins job is to carry iron throughout the body if you don't have enough iron you're kind of pale and sickly looking it's not a good look but this disease isn't limited to just people in Parseltongue objects can be anemic too except instead of iron they're missing behavior or logic this is what makes strong objects let me give you an example this is a model like you'd see in any PHP application you know that right away for two reasons one I labeled it in the top left corner - it's made up entirely of getters and setters you know exactly what the internal structure of this object looks like it's got a name it's got a status it's got a bunch of tasks all right it is basically a big data container there's no behavior here I mean sure in a modern PHP framework maybe you've got an adder not just to get her in a center but for all intents and purposes you could replace this with this and you'd be the same - a little bit of type-checking this is generally regarded to be a bad thing TM but no one will believe you if you say something about objects unless you bring a Martin Fowler quote so I pick this one says in essence the problem with anemic domain models is that they incur all of the costs of a domain model without yielding any of the benefits in other words you sat down and thought up what all the models in your application were you thought up what their properties were how they're connected to each other and so on and so forth but you didn't put actually anything in there to enforce that or actually even use it really in any way which is kind of a waste this leads to the unfortunate conclusion that our industry standard is an anti-pattern now an important note before we go any further I am for all intents and purposes an idiot okay I am out here with some sort of divine revelation about how to build your code because guess what I'm still figuring it out to what I want to offer you today instead of advice is a buffet I want to show you a line you can walk down and sample several different ways of building your application and maybe you like some of them maybe you don't like any good buffet this is essentially judgment free you can decide if you like it or you don't and we're also not going to talk about if it's good or bad and we're not going to go really in depth with any one thing you're just going to try a little bit of each one so that at the end of the day you'll know enough to go further on your own and hopefully make it at your own house okay I'm also not going to talk too much about the models themselves a lot of what I'm going to discuss is often associated with the main driven design which is really really awesome but today I'm going to talk about the stuff around the models instead I'm going to focus on integration over implementation of strong domain models okay now because I'm going to show you several different ways of building something it helps if I have a common set up so that you can follow its evolution so I want to stick with us to do list thing because it's bonkers simple it has a name it has a status and it has zero or more tasks okay and the tasks themselves are even simpler they have a description and a priority everybody with me okay now I'm going to show you how to build this stuff using an ORM that's totally not doctor into a promise and a framework that's absolutely not symphony - okay okay there's some flavor that in here but you can build anything and everything I show you from scratch in some it's something else Drupal whatever you want okay so I'm going to start off with kind of a crud application that's how we build most PHP stuff today right and we're going to begin in the controller because that's where most of the action is when you do crud so we're gonna have this add task action it takes an HTTP request as its first parameter and what we're going to do is create a new task and attach it to an existing to-do list and this is the example we'll use throughout the rest of the presentation okay so I'm going to take my time with this code the first thing we're going to do is we need to create a new task right and here I'm just going to get the desk I'm going to set some stuff on it this is really simple I know in real life you'd have some validation or something like that here you probably using a form library or serializer but it basically boils down the same thing it's on the same layer of code next I'm going to take a to do repository and I'm going to look for the existing to-do list inside of it so we're going to do a find buy ID there if the to-do list doesn't exist we throw an exception a 404 and then we set the to-do list on the task this should be your first hint by the way that something is bass-ackwards because right if the task belongs to the to-do list then why are we putting the to-do list on task I mean it makes sense when you think about it from a database perspective because this is a parent/child kind of relationship and you know that you need the parent ID on the child row but that's the database concern it doesn't really blow all the way up here and what's basically UI logic but we'll talk about that later after we set the whole thing up we need to save it we'll use a repository for that they just flushes it to the database and then we'll have some application specific concerns like updating an audit log maybe we send a new email and then finally redirect the user back to the Edit Page okay so I'll let you look at that real quick because we're going to slice the nicest a lot everybody good goal each now your first impression is probably correct all right and this is not just the stead X although those are important too I mean I can't even fit the closing bracket on the bottom of the slide for God's sake all right but there's some real technical concerns here the model is thoroughly anemic which we've already talked about as being kind of you know bad it is hard to maintain this code will only grow in complexity it's only going to get worse as we add more concerns it is clearly not testable there are way too many collaborators there's no clear point to the object it's going to be a real pain to write anything but an integration test for and then finally the person who's written it has clearly never heard of the single responsibility principle in their life okay now in defensive crud for all the bashing on it there are some good things here all right crud is a very very low barrier to entry if you can normalize a database you can build a crud application it's that simple it's also easy to follow provided that you can keep the entire domain and all its rules in your head because there's nothing in the code outside of maybe your form validation that's going to help you get any of this right it's not encoded anywhere in the design and sometimes all you really want is data entry I mean it's not as often as we think it is in the industry but sometimes it really really is so I might have some kind of really fancy insurance calculator that's well designed well tested but it maybe need some actuary tables that are updated in a database once a year and for that updating of the tables why not build a crud application it's simple it's easy it's not important to my business and then finally crud isn't always a developers fault if you have a bad project manager or a subpar UX person who just hands you designs that are nothing but overview pages and edit forms you have very little choice but to build a crud application in my experience all right but I think we can do better and I think one of the ways that we can do better is by adding something called a service layer to this application now service is like the most overused term in all of IT right now I mean we got a service layer a service container or web service a service oriented architecture domain service stateless server software service platform service whatever source of name delivery service Alondra so we have a lot of services is what I'm saying so I want to be really clear about what I'm talking about here I'm talking about something called an application service not God another service all right but this is basically just a service layer and I'm going to use the two interchangeably the reason I like the term application service I picked it up from this book implementing domain driven design is that it emphasizes the role of this service layer as being tying different concerns together to create what's essentially the application and we'll talk about that again a bit later the way it works in practice though with an MVC app is that you just have a model and a controller normally and the service layer kind of buts in there like an unwelcome house guest all right and you might be thinking okay Ross why would we do that I mean if three layers didn't cut it why is for going to magically be better well if we turn to the good book you'll find a chapter here written by a guy named Randy Stafford who Fowler asked to write about service layers and in there he highlights two main reasons to build a service layer the first one is that you have multiple user interfaces that consume the same domain logic right and that might not have been too common when the book is written I don't know I wasn't there but I would argue that for us today this is practically the norm like how many of you here have a website and a REST API that let you do some of the same things or you have a website and a cron job or a command line admin interface that's consuming the same logic or a queue worker from gear man or Beanstalk these are all different interfaces consuming the same domain logic he also hints at the idea that it's good for in between logic or stuff that doesn't really fit in the controller or in the model like database transactions you don't want to duplicate those in every single control or they don't belong there but they don't really fit in the model either that's a persistence concern so an application service can be a good place to put that stuff and then finally there's another reason you might hear bandied about on Twitter or conference always these days which is that application services help you decouple your domain model from the underlying framework because clearly frameworks are the source of all evil in PHP all right but for all intents and purposes this is kind of true like me personally when I'm building an application I tend to base the controller and the view entirely on whatever framework I'm using because it's easy it's the most productive it's also the least important part of my code the service layer itself sometimes has components or libraries from this framework or from another mood lighting romantic anyways so we have that stuff in here and it uses a couple components event dispatchers or validation library something like that and then finally the model itself is decoupled from the rest of this mess usually maybe except for a couple value object libraries or a couple validators something like that all right so it's kind of a standalone piece now it's a whole lot of jibber-jabber let's talk about actually building a simple service layer all right so an easy way to go about doing this is as a quick rule of thumb just think about what code would I want to reuse if I had multiple user interfaces I would say in the case of our giant controller here it's probably the stuff about you know setting it up and then the Associated objects with it so what I'm going to do is basically an extract class refactoring and I'm going to pull this out I'm going to put it in a new class called to service and it's going to have a method called add tasks and I'm going to require all the different pieces here as formal parameters all right by the way incidentally to do service is a really crappy name for service please name these actually after something that's maybe related to your domain like reminders or journal or something like that but I'm lazy anyways so we're going to have this in here so there's no way that you can invoke this method incorrectly because it requires everything explicitly alright and then we'll just dump that code in here did a simple little service layer now you'll notice that we also took a fair number of the collaborators along with us like the repository the auto log stuff like that I would usually inject this year through the constructor with whatever di layer you're using you know if you've got a factory class that builds your application same thing alright so we've got that in there now one of the things I would normally do as well here is if you've got different collaborators that you're using in the service layer I would begin encapsulating all access to any of those ok and this might seem like a little bit overdone or a bit over engineered but it has some serious benefits because you know what's accessing what now right so if we cut back to our controller having done this refactoring you'll find that it's already a lot more high-level and it reads better now I'm finding a to-do list by ID I throw a 4 or 4 if it's not there I add a task to it and I redirect the user this is a lot simpler this is easy to follow right and if we add an extra concern like some extra piece of logging or whatever then it's not going to mess the controller up so this also begins to enable other things like if we wanted to build a super hipster command line task management application then that's pretty much peanuts now right we just dump it in there because the important code is all inside this service class now this is also in very very simple applications a decent point to begin go ahead and drawing up the rest of the code and in terms of don't repeat yourself so stuff like it's not found exception if we want that thrown everywhere you could conceivably go ahead and begin putting that inside the service layer so you could do something like a find by ID dump the exception in there and you didn't have to modify the entire application now keep in mind once you start doing this that no trace of your user interface layer should exist inside the service layer all right so we're not using an HTTP exception here anymore we're using a specific named exception okay now if we turn back to the same UI code again here it's once again a little bit simpler we try and find something by ID we add a task to it and we redirect user that's a step forward okay now you might be asking what why don't we go ahead and move this find by ID stuff into the add task method as well well that's true you could but you begin to sort of close off some doors at this point for example if on the command line interface I want it to refer to it by name rather than ID you know I still have to pass it in there instead so you want your user interface your service layer to be close to your use cases but not too close now that Joe makes sense oops okay so we have that stuff here all right now that's not the only way to go about doing it though for example we could instead of passing around the object itself that we want to modify we could also pass around the ID instead and this is just as good we could say find ID by name and then we pass that into the service layer as well usually you would do this as some sort of value object so you get really strict type checking and this is a great maneuver as well if you want some extra isolation so it depends a little bit of what you want to do but whether you use objects or whether you use IDs just be consistent on what your service layer accepts pick one pick the other but not both okay now this is going to feel really really crazy because you're like why am I just taking this stuff and then wrapping it with one-line functions again what's what's the benefit of this and the easiest benefit that I can show you without getting into anything deeper we're going to is that it makes it really simple to add extra behavior without having to modify several callers so I could add a cache to this method without having to go hunt and peck or everywhere in the application now that's a lot of technical advantages let's talk about some indirect ones as well I think this has had a great effect on the readability of the code every class feels more high-level they're easier to isolate they're easier to test there's also a certain level of jr. protection here you've got this great big interface running down to the middle of the application and that means that you could divide up your team a little bit easier a little bit better you could also for example you know change something on one side without having it break something on the other side hopefully at least you're having the beginnings of that here it's also got a great element of discoverability if I'm looking at a crud application for the first time I'm just looking at your database tables basically I don't really know what your application does I don't know what it allows you to do whereas if I look at the method signatures on your service I have a pretty good idea so at this point you might be thinking hey mission accomplished we're basically done we've built a simple service layer there's just one little detail we need to wrap up but our model is still what's the technical term dumb as a box of rocks we've done absolutely nothing to help our model which is what we started with this kind of a premise here so I hate to go Return of the King on you but that's a fake ending we're not even a third of the way through yet okay so this leads the question where is my logic every application has some level of business logic and if it's not in the model then where is it well what happens usually in PHP is you start using service classes and that begins to leech the logic out of the model all right so I would argue that this stuff right here is an important chunk of business logic it doesn't look like it because it's composed of just setters but it's really an implicit way of saying some really important things like what is the relationship between a task and a to-do list what are the required things that I have to give in order to create a task you know it has to be a description and priority that's important stuff here but you can't see it because it's in the form of setters all right so if we turn once again back to the good book you'll find a description of what it is we've written it says organizes business logic by procedures where each procedure handles a single request from the presentation that's close to what we've done except it's not talking about a domain model is talking about something called transaction scripts which are kind of a halfway point like on one end of a scale you've got crud on the other end you've got domain model and transaction scripts are this nice little place in the middle all right and that's not a bad place to be actually I mean transaction scripts are very simple I mean you've seen how we can implement one in just a couple minutes and they're definitely more flexible than a crud application at least the problem with the transaction script is that they don't scale flight as well and I don't mean performance scaling which I'm actually not that interested in I'm talking about complexity scaling like as our application gets harder and more complicated and adds more features it will get Messier and it will have more concerns to deal with so if that's the kind of stuff that we're worried about being in a model what belongs in the service layer then well if it's not this stuff then I would argue it's this stuff this is the kind of thing that you might want to put in a service layer okay ultimately a service layer is meant to be about orchestration this is the key word here it is not meant to do anything on its own it is meant to tie together a bunch of different concerns that are standalone but tie them together into an application and that's why I like the term application service personally all right so things like database transactions security may being notifications or logging perhaps even bulk operations if you want to get fancy these are things you could conceivably put inside of a inside of a service layer the key pattern here is facade we are not adding behavior to any of these models we're just tying them together right so you've often heard the term fat model skinny controller think fat model skinny service layer as well all right so with that stuff in mind let's do some rethinking about what it is we've built if we look at our service here you can kind of break the stuff down into two categories we have write methods which change something like add tasks and we have read methods like find by ID find by latest list and the reads outnumber the writes so let's give them attention first so we're going to remodel our reading by refactoring our repository again okay so if we look back at our service layer here this is this is pretty simple code it's easy to follow but this stuff right here is basically adding behavior all right we want to try and make our service layer as thin as possible and this doesn't really belong here so we need to get it out I mean the question is where should it go we're clearly using some sort of like repository or database layer here which doesn't throw exceptions when something is not found so how are we going to shoehorn that behavior in I mean it's not like we can just go in and you know create objects that work how we think they should oh wait we can so let's try that for a change all right so let's create an interface to do repository these are the methods that are important on it these are the ones we're going to use all right so we'll start off with that and we're just going to go ahead and create a to-do database repository that implements that interface okay and then we're gonna have a fine by D and we'll basically plot the exact same code in there that's again a simple extra extract class refactoring alright and this feels better it's much more consistent here it's only in one place it's only in one way now you might be thinking that's cool Ross but I'm not made of time or money I don't have time to run around with a raw database connection and do all this object mapping myself that does not interest me I'm busy doing it with doctorin or propell or whatever it is you know that you want and that doesn't throw exceptions so how can I make that happen well you want to know how to fix that boom fixed all right all we did was instead of using their repository directly we wrapped it in our repository and I know you're thinking like the repository section that's crazy but honestly it's a matter of not which object it is it's a matter of interfaces yeah that's what we really care about and this is the interface that we care about the to do repository is the important interface in the circumstance all right this is doctrines repository interface and it is a very very good interface for doctrine I like doctrine a lot that is not a knock on it but between this interface and this interface this is the one I want to support this is the one I deal with and this is the one I care about so that's what you should be using all right so if we cut back to the to do service having made these changes but a Bing but a boom much simpler now finally this list is a little bit more complicated because you can make an argument that this stuff right here is orchestration we are orchestrating how a cash and a repository behave in unison but at the same time it kind of doesn't make sense though because the service class is meant to tie things together and this is still kind of adding behavior which is not what we want just think about it from a unit test point of view again here we would be testing two different things all right so boom let's extract that code let's bring it to the repository or cleaner much simpler you could also go a step further here though because we're using a single interface for this stuff that's separate from a concrete implementation I could do something like this I could create a caching to do repository which is basically a decorator object it takes an actual back actual inner repository which could be the database one a block one whatever you want and just encapsulate that okay and that seems like a lot of objects so how would we tie this together well do it in your data and your di layer so you might start off with like a doctrine repository you put your own wrapper around that to get the interface you want and then you have a caching thing on top of that which you can turn on or off if you so choose and then you encapsulate the whole thing with a service all right and I know that sounds crazy all right it looks totally over-engineered but if you go in for me and you go home and try it I guarantee you'll notice a couple things one unit testing gets a lot lot easier too you have a lot more composability in turning like features on and off and three you'll never want to go back to the old way promise ok if you ever have doubts about this just remember the inverse biggie wall which is commonly stated as moe classes Moe decoupling and reduced overall design issues admittedly not as catchy but ok now in particular finder methods do proliferate at a crazy rate especially if your database objects are too big so you may have a design issue you want to go in and shrink those back down again but a different way of doing it would be to consider something like a criteria pattern so instead of like individual search methods you have one that takes multiple parameters to kind of describe the search you want to do that's the common advice anyways but it turns out that building your criteria a pattern thing here from scratch is surprisingly difficult so you might want to use something like doctor and criteria in order to give you a head start but it's not that bad now we still need to do something about improving the writing in our application but let's take a brief interlude here and talk about what probably seems like an extreme proliferation classes in our application at this point because it's it's really going all over the place right especially we're only focusing on one object right now the to-do list but that's already got a service it's got a repository and as we continue to add stuff like a task without our I need a service it will need a repository as well you'll need tags and then you just melt some cheese on top of this sucker and you've got a big steamin pile lasagna code okay which if you've never worked a little zhonya code it is the worst type of code all right it is the opposite of spaghetti code where you basically have so many layers that are stacked up on top of each other that if you want to modify anything you have to cut through all of them at the same time true story I once worked on a on an application where if you wanted to add a new field from the database level all the way up to the user interface then you had to modify the code in seven different places it was the worst okay so don't ever build this please instead try and reason about what you're doing don't just follow some template because some guy at a conference told you what to do one way you could approach this differently is to be more intelligent about how you design your models something the DVD guys do a lot is create aggregates which is basically where you have an aggregate root that's the to-do list in this case and it basically manages the lifecycle of models that only make sense when used by it so you might have a single to do service here with lots of collaborators like a repository an audit log whatever but you're talking about a group of models rather than an individual one and this can cut down on what seems like a crazy number classes now over time you might be tempted to keep making these bigger and bigger like you say well all to-do lists are actually done by a user so this all belongs into the user service technically but that's probably too big all right you want to keep it a bit smaller so take the user service one way the to do service another there and if they have to communicate with each other that's okay you can have references between them or something like that but don't just like tie them together because that are that way in the database I mean the user could be split off in the future to a an external REST API or something and then you're kind of what's the technical term screwed okay so if you're going to do this and you're going to let them communicate between each other you know it goes really really well here interfaces interfaces go great with everything don't just pass the raw user pass in a value object pass in an ID pass in some kind of alternate implementation of a user something more specific to the to do list whatever you want all right but interfaces are your friend I remember that services aren't only for entities they're basically facades and facades are meant to simplify any interface so the scale can differ anywhere from like it's one class to ten classes to 20 classes to whatever all right just remember the quality of implementation matters if you have a really crappy service layer that nobody likes working with I guarantee you your colleagues will find you in a back alley one night and stab you with a shiv made from the plastic of your own keyboard don't be that guy so with that in mind let's talk about remodeling the writing in our application when we last left that stuff we are here on the add task method and we were talking about how this stuff right here is the business logic in our application okay so we're going to bring that stuff to the model where it rightfully belongs all right and that can be pretty straightforward again we're just going to have a method add tasks we pass these things into it and it's set up now and the important thing is that we both set up the task and we do the binding to the to-do list here okay now it is still possible to build this thing incorrectly inside the model so the task should still enforce its own integrity so we could require it with a constructor that way you can't instantiate it unless you have the required stuff now that's great in theory but there is a catch here many of your own rooms still need a reference on the child object the task that refers to the parent because they can't magically figure out that one is you know the child or the other they need the ID the reference etcetera etc so if you have to do this then you can pass it in the constructor you can hide it in some way that's okay it's a lesser sin but if you do do this do not compound your mistake by putting a getter and a setter for the to-do list on the task all right because that way lies madness all right so we're just pretending it's not there now once we turn back to the service having done this again it reads a little bit shorter it's a little bit more compact and it feels more like this is about orchestration right we're now tying together a bunch of different concerns the domain model is one of them but we're also tying together these other things and again that emphasizes the idea that this class is what brings stuff together to make an application okay so this also paves the way once we have these things in place to begin adding real business logic to your models and by real business logic I mean the type of BS examples you only see in talks like this like if I'm more than ten tasks we'll change the status to unrealistic who has these problems in your life I don't know but we'll pretend that this is a valid one a good hint that you're on the right track though is when you can write meaningful tests for your models if you can write your unit tests that actually cover something inside your models it means there's logic in there so you're on the right track okay but you won't be doing this long before you notice that you have some problems making everything work together I mean the model is the boss the model should be in charge but the model also has a very limited scope so right now we're fixing that by for example mentioning every single concern here inside the to do list service class so we just have another list if we wanted to add some extra piece of logging or whatever it would have to be injected here and put at the bottom the class as well so that's only going to grow over time it's going to add more collaborators more complexity so on and so forth also what happens when we have conditional logic inside of our models like how are we going to send an email every time we get over ten tasks for example are we going to like duplicate that check here inside the study that's silly that's the opposite of everything we've been trying to do and then finally what happens if you have to communicate like over a network like send these things automatically to a printer or whatever that's that's like async that could be really really hard to do and the service shouldn't be necessarily caring about that so we need something new something better something event ear yes the main events that would work now if you're not familiar with the main events they're a really really common pattern that you're pricing and jQuery you know that thing that runs in browsers it's an observer and we're just going to use it in a fashion you might not have seen before all right so I'm going to throw a couple new lines of code here on the screen at once don't flip out or anything all right here we go we're gonna have this raised method and then we're going to toss in a new task added event and that's going to take whatever the important things were that just changed okay so let's talk about the event first because that's the easiest thing here it's really just a message okay we're going to pass these things in here through the constructor and then we're going to probably put some getters on here so you can read it but it is basically a simple immutable little message object it is no real logic of its own and that might seem kind of hip hip hip equivocal is the word I'm looking for it might seem kind of hypocritical because I've been to ranting about an e me the whole time but think about this more like a data structure than an actual object okay so we're going to set up this task added event now that's pretty straightforward but what about this raise method what is that thing a moe bobber well we take the to-do list here we're going to add a few more lines of code bear with me we're going to have a pending events array all right starts off empty and the raise method just depends an event to that array and then finally we have an extra method here release events sometimes called DQ or collect all right and it just returns a copy of whatever events are in there and then resets it so it kind of like unloads all the pending events it returns this stuff all right now the only thing a good point here this is an excellent use case for a trait it's not enough code for a base class certainly doesn't justify it but it's just enough you don't want to duplicate it everywhere so trait would be a good move here now the only thing we're missing at this point is a dispatcher because we're not going to bind every single listener to every single model that'd be hugely inefficient and total waste all right so we're going to take a dispatcher here now we would probably do that inside the service layer so we would kick out all these individual concerns like the auto logged learn stuff like that and we would just replace that stuff with dispatching these events and then things like sending the email for example could be relegated out of the service and into an email listener object where it just waits for whatever the proper event is and then triggers the same stuff all right and this is really really loosely coupled at this point it might seem like a lot of boilerplate but at the same time you can reuse these listener classes for all sorts of things they often have a common set of dependencies and the listener classes themselves are meant to be thin they're kind of like controllers in their own right so they're just referring the work off to something else that's actually doing it okay so this works out really really well then and now we can also begin to dispatch multiple events if we want for example you can send 0s with the method you can send lots of events with the method and it's easy to tie this stuff together just add events when you need them so there are a couple nice things about domain events as a whole we get to keep the logic here in the model where it belongs which has been a central theme it prevents a big ball of mud from appearing within our service so as we get more collaborators and more little bits of logic that accumulate there over time we've now reduced that to kind of a fixed amount of functionality and individually the pieces are all thin and easy to test if I'm writing unit tests for the model I check that it raises the events if I'm testing the service layer then I'm checking that the events get the floor from one thing to another and then if I'm testing the listeners here I'm testing that they pass them off to the appropriate collaborators every piece here is really really small and it's also a great way to communicate over a network because you can simply serialize these little immutable data structures pump them off over a network for whatever piece of communication and then have things happen out of process that's one of the reasons why we only pass scalars or simple value objects here instead because you can't change those you don't let events change in the past unless you have a time machine there's some less nice things about domain events though and they basically all boil down to exactly the same thing humans hate debugging events especially if it's an event on an event on an event on an event in practice that doesn't happen nearly as often as you fear but if you are going to use the main events it's worth spending a few hours to build into development logging maybe some debug commands you can see whatever listeners are just kind of hanging there waiting for stuff to happen that gives you a much better overview of the system as a whole okay so we've been talking an awful lot about how to get the model on the service layer communication right but that's only half of the integration story here we also need to talk about how to consume the stuff from our controller and view alright so let's talk about that now the big danger that we have at this point here is that we are allowing full fledged access to the model in all the controllers we are just returning the entire model and letting you do whatever you want with it and in most cases that's going to go correctly we're going to pass it back into the task into the task service and you know add the task but if you have a new developer who's just coming to the project for example and they only follow their autocomplete they're like oh this is like a database map or I get the thing back and oh wait there's an ADD task method here and I just invoke that directly and oh I want to rename something too and oh wait I need to pass it in to save and there's like more ways to get this wrong than there are to get it right and that's the sign of a bad API so let's try and think about this in a different fashion all right because right now what we have is essentially some implicit communication or maybe even coupling between the model and the controller if you change one it's possible to maybe break the other the service layer is not being a good layer here it is basically letting the model run around and poke its nose into different places in the application where it doesn't belong alright it's like a bad puppy so you should instead do as a wise man once said and keep them separated at the hips so one of the ways we can do that is beginning to isolate these things from each other so we can bring in something into play called a view model if you're into mvvm stuff that's different than what I'm talking about here don't worry about it all right so it's a really really simple pattern all we do is instead of passing back the to-do list and the find by ID method we're going to wrap that in some other little simple object and I'm calling it a to-do DTO here which is short for data transfer object all right it's just transferring data it's again more like a message or a little thing like that okay and it's only going to give you a subset of the functionality here and it could be a decorator it could be copying stade it could be built by a cron job doesn't matter okay it's totally different thing and it's usually pretty much logic lists it might have a queue helper methods but that's kind of where it tops okay now another way of thinking about view models is also to bring structure to things that you often have implicitly like who here has ever had to build a crazy report out of some 14 line SQL query with a thousand joins in there right it happens all the stinking time and usually pass that back as some sort of data array that's totally untyped and you format it in one particular template and nobody can read or understand any more so a better way of doing that might be to actually put some form put some structure in that thing you know so create an annual goal report object that expects something in a particular format put your helper methods in there write unit tests for it right if you can do that kind of thing right here for a shapeless blob of data you can do the same thing for your models it's a really really powerful concept but at the end of the day it ain't rocket science okay so what I think is more interesting is if we reverse it and we used eto is not for output but for input instead which leads to us going commando by which I'm clearly referring to the 1980s onward Schwarzenegger classic all right case anybody is wondering now this is almost the same thing but in the opposite direction we're going to create a simple little message object which is called a command and I'm super lazy in this scenario and I'm just using public properties you might want a static factory or some getters and setters in real life but as I said I'm really lazy okay so I have this little command object and I'm going to fill it in in the controller all right so I'm just binding all that data from the request directly to it and at first glance this might seem like more lines of code than you we had in previous examples but it's simpler code and if you were using a serializer or using a form library this would be peanuts right so we're going to fill that command object in and then we're going to dump it into the deduce service but not into an ADD task method we're going to dump it into an execute method instead okay now the way this whole thing works as a flow is that the controller fills this message in it dumps it in the service and the service acts kind of like a router all right there's one Handler object out there for every command just one Handler okay but there's a whole list of them and the services job is act like a matchmaker and find this command soulmate you know the one hand where it was meant to be with all right so it pipes it on through to there all right now how does it do that well we replace the add task method with the execute as I said but it needs an actual strategy for doing that to be honest with you I don't care I mean you could do it get class in the command and map it in an array you could have command get name and then have that be the name of the handler you could have the command execute itself fowler likes this as long as there's no dependencies you know that you actually need extra which there always are but anyways this is this is you know whatever strategy works best for you and your framework I don't care all right so what goes in a handler then well it's basically everything that's left over which in this case turns out to be not much we just look it up in the repository we add the tasks and maybe we dispatch the demand vents but that's pretty much it and this might seem like a lot of warmer plate but the entire service now is completely reusable completely composable and this stuff can again have some more dependencies so you could use the same handler for multiple operations multiple actions easy-peasy alright so the once you start doing this for a while you realize that well this works the to-do list service but we could actually extend this further I could have instead of like a different command dispatching thing for every single service I could have one generic command bus for all right operations in my class in my entire application I mean you still probably services or something like that for doing the reads but the command bus can handle all the writes and you just need one really because it's all routing under the hood now there's a really really smart guy named Benjamin Everly who wrote a couple years ago that if you have a service layer he likes having this kind of command dispatcher interface and the reason for that is that it's really really simple because the interface is just one method so you can easily extract that into an interface and then write your own implementations of it you can have a whatever works for your application here but you could also create something that lazy loads from your di layer directly you can add validation behavior here as a decorator again so like if the command is not valid in some way then you throw an exception otherwise you pass it on down the chain and so if I want you know like specifically symfony validation on command I can just drop some annotations on here and I'm done the possibilities are practically endless you can do anything and everything you want with it all right because it's a very very simple interface some other possibilities I've seen in your life logging log every single command that comes through database transactions or good fit if you've got a unit of work so you can inspect the identity map on something you can do the domain dispatching the main event dispatching the capture all that changed entities and send their domain of its in their events out alright so lots and lots and lots of possibilities here so commands in general fewer dependencies per class more layers but simple layers and very very easy to test we're really cutting this stuff down to its core now this isn't this is not an either/or scenario though I can use view models and commands at the same time in fact that will give us complete isolation here so every time I want to make a right I would shoot a command into the service layer and every time I want to make a read then I would get a view model back this is really cool and one of the things I like the most about this approach for PHP is especially is that it gives us crud for the framework but domain model for the chewy Center and that's because frankly there's a dirty secret that a lot of people won't tell you about when you start doing DDD stuff like this which is that your average PHP framework really really likes you to have getter setter based models I mean things like forms templates validators they all love getter setter based models because they can do mat a very easy meta programming and figure out how to operate with that on the other hand we've got a service layer in place I can use the domain model for things like tough logic capturing semantics testing that's a better fit so I can get more the best of both worlds approach ok so if you're using commands and view models here at the same time over time you begin to might maybe possibly notice that they'll start to diverge you'll see that maybe your commands are sending in fewer properties than your view models or reading or that they don't really begin to resemble each other very much that can be a natural flow of your application or it could be a sign that perhaps the modeling is actually different for commands and for you which leads us to the last section of our talk a really hot buzzword right now see QRS now see QRS is kind of hard to show you because on the surface it looks the same this is the exact same controller I showed you in the last example what's different here is the concept of seek us or command query separation this wasn't discovered invented what have you prefer by a guy called Bertrand Meyer very famous Oh appear and he theorized that all operations any method you call can be classified as one of two things it is either command which changes data but returns nothing new or query which reads data but changes nothing in the process of doing so and don't confuse the terms command inquiry with like the command pattern we saw in the last section or query like a database query he means any method call whatsoever all right he says that these are two different things if you like rest then think about it like the difference between a get in a post okay now a few years later and by that I mean a lot of years later back in 2009 or 2010 a guy named Greg Young came up with this idea of CQRS or command query responsibility separation and his insight was that if you look at your average model then you can see that many of the methods here already break down along the same lines so we have a rename and add tasks and those are commands and the stuff on the bottom or are naturally queries but what if these are actually two different responsibilities as in terms of single responsibility principle well then you would have two models one that covers the reads and one that covers the writes all right so you might actually split it into two different objects like this where the top one is making the changes and throwing domain events for that stuff and then the bottom one there could be multiple read models is receiving those demand events and updating itself in order to generate like new visualizations on the fly okay so if you're having a hard time picturing this think about my to-do list application where the marketing guys come in and they say hey we would really like to have this feature where for every to-do list I can easily see all the users who were ever involved with this to-do list in any way shape or form you'd be like holy crap that's that's actually kind of hard to figure out and it's kind of norman's intensive and you know we sort of did a lot of work and separating the two things out so just slapping a get participating users method on here it's kind of screwing the pooch so CQRS gives us an alternative to that what we could do is create a to-do list model on top where we just you know make the changes themselves we add the tasks but the stuff on the bottom that could be generated from a cron job we could be doing it over an API we could be you know building these things asynchronously it doesn't matter right we can build that view in any way we prefer it could be a combination of data base stuff and remote API stuff doesn't matter all right in practice this often means that you have like an ORM entity for the stuff on top because the right model is the one with the logic and this one in the bottom is maybe an SQL query or some combination of data okay the important concept here is that read and write are two different systems like a user in a shopping cart they have a relationship with each other one uses the other probably but you want to bring that same kind of split into play here what does this do to the surrounding classes well a lot of it looks the same actually command pattern is often used here already with secure s like many people think that they're part of the same thing so you would often have a handler like this you would often have a service for doing reads like this and they would be side by side identical to the previous examples the difference here is that the repository on the top and their posit or in the bottom are two different repositories the one on the top does the right models and it usually has only very simple finder functions usually just defined by ID and the one on the bottom is you know got more complex query stuff okay and they return two different models one returns the right model one returns a read model and this has some surprising effects throughout your application consider this code we do this all the time we create a new to-do list we save it and then we get the database ID back from it so we can redirect the user to a URL with that ID in write pretty standard this will not work if you take CQRS seriously why because the actual writing of a new to-do list is a command and commands can't return state all right even an auto gem created ID that you're setting on a model you passed in here is extra state it is maybe sideloading return value but it is basically doing that okay so you cannot do this instead what you most often see is people using you you IDs because these are unique they're generated by the client the rest of the stuff can pass it in and it still knows what the ID is at the end of the day I would redirect the user back to you know a URL with the UUID in it instead so there are a lot of different possibilities are ways to work around this but the the work of generating that ID is now in a different place this is really a low level so maybe can get a better picture of it here if i zoom out this is a diagram i straight-up stole from martin fowler's website because if i was trying to do it again i would just be duplicating it so straight up stole it alright what he says here is that in general you have a command model what i call a right model here on the bottom and a user comes along and makes a change in the UI alright and that goes in right here which is basically our command bus that gets piped into a handler that makes some changes on the model and then that stuff gets saved to the database sometime later the same user or different user comes along and makes a read and that goes through the query model over the read model right and that comes back out and gets displayed in the UI now Fowler says that in this case the query model in the command model are communicating with each other through the database like they have an agreement or contract about what those tables in the database look like but they are still two different systems one of the cool things about CQRS and one of the things that might make this split a little bit easier to visualize is the fact that we could actually use two different databases here we could use like MySQL here for the integrity for financial transactions and we could use Redis here in order to do it and deliver that in the most high-speed way possible or we could do it the other way around or we could have like one write database there's always one consistent write database but we could have in any number of read databases all right however you want to do that now how does that stuff replicate between there because there is no magical Redis to my scroll adapter that I found yet well the most common ways domain events you know the right model fires them the read model listens to them and updates its own projection but it's not the only way I mean you could use DB views like we had in the first example you could use a big honking cue I don't care but the main events are the number one way that people use if you'd like to try and put this into action the Benjamin ever League I mentioned earlier total man crush he wrote a CQRS library or rather reported it from C sharp and you can take a look at that that was kind of the original one in PHP the guys over at candidate labs here in the Netherlands wrote a new one recently more for event sourcing maybe called Broadway it is really good and I'm still reading it but I like the look of this a lot and then finally Greg Young the guy who coined the term CQRS in the first place has a couple hundred lines example that he feels illustrates the principles of the whole thing it's written in c-sharp but we're all big grownups here we can we can handle that okay now what are the pros and cons of CQRS because there are serious ones let's let's talk about the crop mccollins here first it is a big mental leap it is a very different style of modeling than what we're used to and if your team is not ready for that that could cause some problems it may not be the best thing to roll out it is usually like pretty much always going to be more lines of code arguably simpler code but more lines more pieces and in my opinion that is not necessarily for every domain Judy de Haan who is a really well-respected Oh appear you know that because the sidebar on his web site has like forty recommendations about how awesome he is he says that unless your domain has an inherent race condition you probably don't need CQRS that's his take on it at least but often what you see in applications that do use it is that it's mixed like one high-speed section of the application is using CQRS and another one is using crud to just fill in database stuff right so it's not an all-or-nothing approach now what are the pros of using CQRS well it is easy to scale and this time I do mean it in the performance sense like because you would now have eventually eventual consistency in your application you can control the speed at which changes replicate users it's very powerful it's a great way if you're under a lot of pressure love speed alright it also bears complexity well and I mean that if you have a very complicated domain model this can be a useful way to model it or to picture it it allows you to separate these things into smaller chunks and it makes certain allowances that were traditionally very hard it has great support for doing a sink which is more and more important all the time and it is probably the single best pattern we have for implementing event sourcing right now I'll been sourcing you might not have heard of it's still kind of making the rounds it it's kind of side the scope of this talk but I'll talk about it briefly because it goes really really well secure s like many people think they're often the same thing the fundamental idea here is that instead of storing the current state in the database like name is equal to Ross age is equal to 29 hat is equal to fedora instead I would store the domain events that were triggered leading up to the current state so I would have like Ross was born Ross went to school Ross got his first hat or all you know and then if I wanted to know what the current state of things they are I would take all those events out of the database and I would replay them on top of the model one after the other and you might think that's really really slow but in practice it's not and by doing this you actually enable a whole lot of really powerful amazing stuff like if your performance obsessed you can kind of offset all the differences here by using it to generate snapshots like every ten events you know do something you know record a quick load version it's not often necessary and kind of controversial but you can do it it is amazing for debugging right how did the clients screw it up this time let me look in the database and see everything you did that led up to it audit logs if you've ever built an application with a real audit log you know how hard that is and here it's basically something you get for free it is a goldmine for business intelligence all right you can see and and follow user flows whenever any time in the past or maybe projected in the future I don't know I have a theory personally you might be able to use this cap style to overcome network partitions because you can bundle the demand up Vince up on one side and then send them over at a later date I've seen some people talk about using this to build self-healing patches where you change like a calculation object and you replay the events that occurred previously and you get a new outcome which is the correct one all right that's kind of bonkers but think about it afterwards it'll make sense or you could google it or you can ask me in the hallway and I'll point you to some really smart people who know a lot about it okay so that's all the time I have for you today I just want to leave you with this quote from Emerson he says a foolish consistency is the hobgoblin of little minds that might sound like Emerson's trying to insult your I am and I assure you that's absolutely not the case what Emerson was trying to say is that you should believe whatever the best thing to believe is today and tomorrow if new facts come to light or new discovery is made you should switch over and believe whatever that points to instead and you shouldn't feel bad about changing your mind because you are believing whatever the best thing is day to day I mean NIT we often call this strong opinions weekly held but maybe a better way to put it as strong techniques weekly held when we're using PHP 3 it was a lot of a procedural and imperative code and it wasn't pretty but it got the job done in PHP 4 to 5 we were began doing simple ello I mean lots of inheritance and again not all of it good but better and PHP 5.3 and up we're living in the era of composer man I mean it's it's all about tying stuff together and making it work and pretty soon we'll be in PHP 7 and I don't know what that means yet but I think it's going to be pretty cool some of what I'm talking about today might seem crazy but try it in little doses not the whole thing at once if you want the most bang for your buck and you're building credence try building a little transaction script thing I think you'll like what it does if you want to go a step further roll out your first domain events I really think you'll like with that what that does to your code all I can do is assure you at the end of the day people are doing this it is working for them and it can work for you too thank you very much I have no idea what time it is but the middle wink here Sean McCool's blog is a great place to get more info thank you very much these people thanks to these people and I'd appreciate your feedback thank you very much
Info
Channel: Drupal Association
Views: 15,752
Rating: 4.9867988 out of 5
Keywords: drupalcon, Amsterdam, 2014, drupal
Id: ajhqScWECMo
Channel Id: undefined
Length: 56min 5sec (3365 seconds)
Published: Tue Sep 30 2014
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.