Functional architecture - The pits of success - Mark Seemann

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right hello everyone I think we might as well to get started if more people are arriving then just please come in but I think I might as well start on time so that because I have a lot of stuff to cover so thank you all for coming my name is Marc seaman if you want to know more about me there's an about page there on blah blah couplet idk I should somehow see if I can get rid of that the mouse pointer there excuse me no let's see if that works no you just have to live with that let's see if I can put it down here somewhere okay now you don't see it all right so I've also on Twitter at play if you think that's interesting I think Twitter is quite useful actually so I'd love feedback there so I'm going to talk about functional architecture today and specifically about these things called pits of successes and then I'm going to cover in just a moment and if you're here and maybe you're a little bit afraid that I'm going to talk about a lot of complicated you know sophisticated things well to some degree I am but I'm also not going to assume that you may have you know heard about those things and I'm also not going to assume that you already know how to read and understand f sharp code or Haskell code so even though I'm going to show you a little bit of code I'm not assuming that you actually know how to read that so don't worry about that I'll talk you through the important points of that I'm also going to show you a little bit of she char code I assume that you understand that so with that out of the way let's just get started with there talking about the pits of successes so imagine that you have some sort of goals that you need to reach in your software development project and I'm not only just talking about some sort of feature that you need that you need to implement but you may have a higher goal of saying I want to enable my codebase to be flexible and maintainable and all of those things while I deliver features and I want it to be long-term maintainable so that I can also deliver features requested by my business three years from now and not you know go to my stakeholders and ask for rewrite which you know happens quite often so that's the sort of things that I'm going to try to talk about today's because that is basically what software architecture is about that those sauce decisions also about scalability and all sorts of other things it was so on but one of those things that actually what we call non-functional requirements so you can imagine that you envision that goal as having to be you know roll a boulder on up the top up to the top of a tall hill so if you've ever done any if you've ever started you know physics you know just classical and Newtonian mechanics you'll know that if you have to roll a boulder on top of a hill you'll need to exert some force in order to get it there so you'll need to expend a lot of effort to actually roll the boulder up the hill and if you are you know able to actually roll it all the way on top of the hill you can put it there and if you're very very you know precise in doing that you may actually achieve an equilibrium and it stays there but you probably also realized that this equilibrium is extremely unstable so if you don't place it exactly in the right spot or if you have some sort of disturbance to the system it's going to start trying to roll down one of the sides and you have to you know you know exert more if I didn't order to prevent it from doing that so you you have to be constantly vigilant and keep watch of what it is that's going on in order to be able to achieve your goal your goal is not something you it's not something you only achieve but you also something you need to keep on watching you know whether it's it stays there and and one of the problems with that is you know this is what I often feel it's like to be a software architect or elite developer on a team is you have to be constantly vigilant because if if you just look away for a moment you know if you are sick for a couple of days or if you go on vacation if you just go to too many meetings what's going to happen is that you know you just let it go for a moment and then it you know everything rolls downhill again and all the effort that you expended on actually getting to your that goal that is basically wasted effort now and I you know I I feel like a lot of software development feels like that or at least that's what it used to feel like for me so you know I spent like at least a decade doing object oriented design and and being all sorts of you know playing all sorts of different roles you know from an individual contributor to either you know a team lead to architect and such things and you know I did this a lot where I was constantly you know looking out for things that could cause the boulder to roll downhill again in order to try to prevent that from happening and I sort of just you know you know I thought that that was just what software development was all about this was just you know an intrinsic part of the of the of the game if we if you will and then about five six years ago I started to you know learn about functional programming and I got interested in this in fractional programming for a lot of other reasons and the more I started writing you know code with functional programming the more I realized that a lot of these things that were very very difficult in object-oriented design actually turns out to be quite easy to do with functional programming it almost happens by itself so again if you can try to reimagine your goal and saying well it's not that you want to be on the top of a hill that's not your goal it goes just to reach an equilibrium you can say well let's try to invert this and say you just want to have an equilibrium so you just roll your boulder down into the pit and it just you know automatically rolls down and stays there and even if you have a disturbance to the system it's just you know settles back into the equilibrium again so this is what we call a stable equilibrium and this is really what functional programming feels like for me these days and I just wanted to share a little bit with you why I think that is happening so I should preface this right away by saying that this talk is based on my experience with writing F sharp code so if sharp is one of those programming languages that have a nice logo as you can tell which makes it much more cooler than the other language so so so a lot of this least-experienced that I'm going to talk about here today is based on my experience with doing F sharp programming and I've done lots of you know just playing around fooling around with F sharp but I also written real production code that actually runs today in F sharp as well so that is certainly possible I'm also basing some of this talk on my experiences with the fooling around with Haskell and Haskell is a programming language that I don't have production experience with but I've you know played around with for quite a bit of time now I need to say that these two languages have a couple of things in common they are both statically typed function programming languages and the type systems that they use are what we call a hindley-milner type system and the reason why I say that this is that there are other programming languages other functional programming languages out there that do not belong to this family of languages like a very popular one is is Scala that's right that runs on the JVM which is also statically typed as far as I understand but it's not a Hindi milliner type system and you also have something like Clojure which is dynamically typed and you have air lock which is sort of I don't even understand if it has a type system or not you know every time I ask someone they say yes it sort of has and said what does that mean you know it sort of has types that's not really helpful but anyways so you may reach slightly different conclusions if you're talking about those languages instead of these languages but these are the languages that that I'm going to talk about right okay so so let's see what we can do here with these with these things here so I'm going to cover three pits of success so the first one I'm going to cover is something called ports and adapters and I'll talk about what that is when we get there I'm going to talk about services entities and value objects basically how do we think about data and behavior and third I'm going to talk about testability which is really fun to talk about so it's not that I only that I've only identified three bits of success during my all the these years of working with functional programming I've actually identified you know like eight or ten of them but I don't have time to talk about all of them so I just picked the ones that I thought was the most important ones and that is those three right so let's just get the dive right into talking about ports and adapters so port and adapters is a well-known well described software architecture and it's a very will it's a very so it's a pretty good architecture for doing enterprise as a software so I've written lots of code we're using the ports and adapters architecture it has various names so one of the names that it's known as is port and adapters it's also known as hexagonal architecture for reasons that remain unclear to me because it doesn't really look particularly you know hexagonal this but and some people also call it the onion architecture because it you know has concentric layers just like an onion in any sense say this is basically what we know as layered application architecture done right so it's sort of like you know it's a little bit of that also but it you know it's related to that sort of architecture and I'm not trying to tell you that this architecture is bad I'm just going to tell you that my experience with this architecture it is that in ood it's difficult to achieve but actually the goal is great I really like this architecture because it solves a lot of problems basically what we are trying to do with this architecture we're saying in the middle there the boxes in the middle represents your domain model your business logic this is the stuff that you actually care about this is why you're building the software because you have some sort of business logic that is important to automate so this is actually what or to you know deliver value to your organization and then you have you know layers that protect that because you need that business logic to you know interact with all sorts of real world systems like you need to draw graphical user interfaces you need to write things into databases you need to send emails and all of those sorts of things but you don't want your business logic to be too dependent on all of those technical concerns because they may change so you want to protect you know the valuable parts of your software from all the incidences all the technical details so that's basically what this sort of of application architecture tries to achieve and that's perfectly fine my experience with this though is that this one it's like rolling that Boulder of the hill is that in the sense that it it's really difficult just to explain to a team of software developers never heard about this before why you would want to do this because it goes into a lot of things about you knows the object the design freedoms principles of object-oriented design like the solid principles for example and you need to think about dependency injection and you need to think about all these things that you know in order to understand those things you have to read thick books about you know Robert C Martin who is the original person or the person who originally described the solid principles he has a book called HR principles patterns and practices and this is seven hundred pages long book that describe the solid principles and gives you examples of how that is and why it's interesting and so on you know and I wrote a book about dependency injection which i think is a good book but it's 500 pages and you know the book that these books are so big should be a telltale sign for us to say well if we need that many pages to explain a concept maybe we should start to think about if there's a better way is that really the best we can do so the first problem with this is that it really takes a lot of effort just rolling the boulder off the hill in the sense that you need to make everyone on a team understand why it's important but then once you're there it's it's even more difficult to stay there because every small disturbance into the system might actually destroy the entire system for example you know if I'm Co if I go on vacation or just sick for a couple of days maybe what's going to happen then is that there's some urgent issue in your production system that you need to address so you go in and say well I'm you know in order to address this urgent issue we'll just call the database directly from the domain model because you know we know that we can't do that but we'll fix it later okay and you know that later is never and and that just causes this thing to you know unravel very quickly again so it's not really particularly you know stable so so it's not that it's not impossible to do with Java or C sharp but it's really really difficult to do now what about functional programming does functional programming help and it does let's try to understand why that is but basically what I'm saying it's not that the architecture is bad but basically that just that it's difficult to achieve this architecture with the at least with the object-oriented languages that we normally use whereas you know with functional programming it sort of tends to fall into this sort of architecture all by itself so let's try to understand why that is so let's let's start to talk about what is the design ideal of functional programming well in factional programming we really like these things called pure functions and why do we like to pure functions we like pure functions because they're easy to reason about they're easy to manipulate and they they are you know easy to compose and they have all sorts of nice qualities about them it also turns out they're easy to test for reasons that I'll explain later so so we really like our pure functions that that is the design ideal of a function in a function programming code base so what is a pure function we need to understand that a pure function is a function first of all that is deterministic it always returns the same output if you give it the same input the other thing about a pure function is that it has no side effects a side effect may be that you know you send an email it may be that you you know write files or disk you delete a record from a database it may also just be that you change you know a value in memory so it might not be something that involves IO but anything that is observable from outside of the function if it changes the state of something outside of the function we call that a side-effect so it needs to do neither of those it can't do they can't do side-effects it can't do non determinism if it's deterministic has no side effect we call it a pure function so so you probably go then but but side effects you know all software development is is basically all about you're doing side effects you know when we create software we want to you know print unique new UI on the screen of the user that's a side effect we want to send off emails that's a side effect we want to put things into a database that's a side effect why would we ever you know be how would we ever be able to do something if we can only do a pure functions and we never say that you can't that you can only do pure functions we just say we like pure functions so we want to maximize how many pure functions we have but we absolutely acknowledge that we also need to have impure functions because we need to write to databases we need to update your user interface and we need to do all of those things we just say we prefer the pure functions because they're easier to develop they're easier to maintain so there's a couple of relationship between those two things that you that you need to understand first of all if you have a pure function and you want to call another pure function from that pure function that is absolutely okay you can do that you know it doesn't make your calling pure function more or less pure that it calls another pure function that's just a way of composing things makes absolutely sense if you have an impure function and you want to call another impure function from that impure function you can absolutely do that doesn't change whether it's more or less impure that's possible you can also call a pure function from an impure function you know if if the function is already impure it doesn't become more while is impure by calling pure functions and you know pure function is something as simple as two plus two so you can definitely do you know two plus two equals four even though you're in an impure context you're trying to save something into the database that's absolutely possible you know going the other way on the other hand is not possible and there various ways it's not possible in Haskell for example it wouldn't even compile I'll show you what that looks like in a moment in F sharp it's not that you can't sort of do it if you imagine that you have a function that's pure at the moment but then you want to change the implementation a little bit and the change you introduce is a call to an impure function what's actually going to happen then is it will compile but that you know your function that was you know pure before now also becomes impure because it's calling into another impure function so it just you know moves from one place to the other one you could you could say so you can't go the other way you can't call impure code from pure code that that's really important let's try to see an example what that would look like and why that is important because this is basically the explained explanation for why we tend to fall into this ports and adapters architecture when we do functional programming right okay so let's look at an example so one of my favorite examples is you know a scenario where you try to implement a system for doing with you know restaurant reservation so you know this these exist already but this is just too you know it's a nice rich you know problem domain so it's nice to talk about so you imagine that you want to make a reservation on the restaurant you go to you know website on app fill in some details and then you hit a button to send off your reservation and in this particular scenario what we can imagine then is that there's a JSON document that your you know user interface somehow posts to an HTTP based server and the HTTP based server receives that JSON documents and wants to deal with it so we will go through five steps here the first thing we need to do is is to validate the incoming JSON because Jason is not particularly strongly typed so we'll need to validate is it actually well-formed do we understand what is being said here if it's valid we can move on if it's invalid we'll need to you know return the response we'll get back to that later on but imagine that we've determined that this is a valid you know will form JSON document it means we understand what it's trying to say means we understand the the date on which the reservation is being attempted that that means also we'll need to look into our database to say how many or you know how many existing reservations do we already have for that date how many reserved seats do we have from that date that date yeah from that date let's read that from the database now we have enough information to make a business decision you know do we actually have enough excess capacity to accept the reservation if we have we can save the reservation into the database so that it becomes part of the decision when the next you know request comes in we might be short-circuiting various ways in various steps here because if we can't validate we no reason to go through the rest if we decide that we can't accept the reservation there's no reason to save it into the database so we might short-circuit at various points but I'm not showing those arrows but what we might want to do anyway do you know independent of the decision we reach we want to say well let's translate our decision back into an HTTP response that can then be sent back to the client so those are the five steps that we need to go through and you will notice that three of them are green and three of them are not red but purple and the three obviously are the pure functions and the the purple ones are the impure functions but you also noticed that they're sort of into lead so we have those three pure functions and we have those two impure functions and we somehow need to you know make them in you know mix them together and we'll wonder how we can do that so let's try to look like a look at what that would look like so I'm going to show you some Haskell code and I'm not assuming that you know Haskell at all what's really important here is just the top line so actually everything but the top line is the only reason why so the top line is the function declaration and it's type and the rest of it is actually the implementation of the function all the whites and and so on beneath that and you don't really I you don't really need to read that just read the top line here I just put the other limb function body there to you know show off that I can actually write Haskell code haha but it's not important let's look at the top line there the validate reservation the blue and the orange stuff here so validate reservation just declares here's a function it's name is validate reservation the orange stuff then is the type of the function it says it takes something called reservation rendition as input this is sort of our you know Haskell representation of the JSON document it returns something called either arrow reservation its output it's not particularly important exactly what that means what's important here to realize is that this function is pure and it's guaranteed by the compiler to be pure why how do we know that well basically because in Haskell all functions are pure by default so unless explicitly stated otherwise they are pure and this one does not explicitly state that it's impure and therefore it's pure and because the type is is declared as being pure if I try to do something in the function body there that was impure it wouldn't compile but I'm not trying to do something impure in the function body there so this compiles and it's fine now what does it look like if if we want to explicitly state that a function is impure well it looks like this so I'm just going to show you a function declaration without it's it's its implementation so this is the next function next function in that little sequence diagram that I showed you get reserved seats from DP so it takes a connection string and its own time that's basically just a representation of the date and then it returns something called IO of int and this is impure and the reason why we know that is because those two letters IO indicates that this is an impure function so basically what it's returning IO of interest is an integer so it returns the number of already reserved seats for that sone time but that integer you know originates from an impure context because it's not deterministic you know reading from a database is not deterministic you could read from a database you know calling the same database query twice and you'll get two different results because the state of the database changed in the meantime so that's not deterministic so so that is an impure operation but basically what I owe of into says is that there is an integer but it's it's packaged up inside of an impure context so you can almost think about this impure context as being sort of like a little bubble or a little space that is sort of you know sits around that integer and when you're outside when you are in the pure context you can see the the bubble or box or whatever you want to call it where that integer is inside but you can't see what the integer is so it's a little bit like Schrodinger's cat maybe I don't know not really but anyway so you know that you have something that contains an integer but as long as you're in a pure context you don't know what that integer is so how do we get that integer I'll get back to that but you know I talked about how you can't call an impure function from a pure function but I'd like my business logic to be impure because basically I just like as much as possible of my you know code to be pure so what I want to be able to make a decision so I could write a new function called check capacity and I could just if I just for a moment forget about this you know distinction between between pure and impure I could say well in my ideal dream world I'd like to have an integer which represents the number of already reserved seeds and I also need you know an integer that represents the capacity of the city of the restaurant so you know that might be just another number like the restaurant could hold ten people or I can hold a hundred people or whatever so I need those numbers and then then I also need the reservation itself if I have those three pieces of information I can make a decision that is a pure function how do we know that because there's no IO involved in the type declaration there so I'm sort of just you know brushing aside that I don't actually have an int for the reserved C's I have an IO of int so how do I get it out well I'll get back to that but it turns out to be possible but you know if if I did that though I decide maybe if I decide to accept the reservation I could say well all right let's save the the reservation into the database again now that is another impure operation we can see that because it's it returns IO of nothing but those empty brackets there we call those unit and both F sharp and Haskell has this thing called unit and it's basically void like it ought to be because unit is a real value so you can use you know unit as a return value but you also use it as input so you can take a return value and you know pass it on its input to another function if you need to do that and that turns out to sometimes be and be interesting to do but but if you think about those empty brackets as being sort of the same thing as void in c-sharp or Java you know that if you have a method in Java Java that returns void it's it it pretty strongly indicates that there must be a side effect of calling that method because why would you call a method that doesn't return anything you probably want to want to want to achieve some sort of side effect so that's a that's possible also in Haskell but it's only possible to have side effects in an impure context because side effects are per definition impure so we have you know IO of unit now the final thing we need to do is to say well if we reach the decision so far we want to translate it back to an HTTP result so we could do that as well again you just look at the top line there we can see there's no IO involved in that so that's a pure function as well this is just a translation from our internal representation of you know did we accept the reservation or not and then just something that can that we can you know translate to you know HTTP responses that can go on the wire that's basically what we're doing here alright so so we have those five functions I wrote three pure functions two impure functions I somehow need to again you know interleave those so that they can all talk together because I need some of the impure information in order to continue with some of the pure the pure operations but I said I can't Corp your functions no that's I can't call impure functions from pure functions so how do I resolve that issue well it turns out that you know at the boundary of the system every system is impure because you know when you get a request coming in to a system or someone presses a button on a user interface or whatever that is non-deterministic side-effect behavior anyway so everything that happens you know at the entry point of a system is by definition impure anyway so so we could imagine that we have some sort of entry point where you could say well if we if you put all those functions into some sort of web framework we could imagine that we have some sort of framework that that you know contains a dispatcher and it says well alright here here's a JSON document that arrives at this particular URL it ought to be dispatched to a function that can deal with that and that is an impure function so we need to have an impure function that could deal with an incoming request anyway so let's use that in that function to compose all of those other things together so in this case I've just called it post reservation and if you look at the top line again the orange stuff you can see it takes reservation rendition as input returns IO of to be result of unit as output so very clearly this is impure as well this has side effects or non-deterministic behavior inside of it and that means we can do everything when we're in impure context we can call pure functions and impure functions so we're pretty much all almost there and we can just walk a you know through a couple of the details so don't worry about all the haskell ii scary stuff like all the strange looking arrows and dollar signs and so on that's basically you can think about that as glue that enables you to compose functions together and i'm not going to walk you through all of it but basically just if you look at the you can see there for indented lines if you look at the top indented line that one that starts with our arrow whose host either and so on you can see after the dollar sign it says validate reservation candidate that's a function call the validate reservation was the first function that i showed you and that's a pure function and it returns a value and we do gloomy stuff to that value in order to you know be able to address it later on and then if you look at the next line you can see there's more haskell googly there but after the dollar sign it says get reserved seats from DP and a couple of you know input arguments to that and so again you know i'm in a pure context i'm inside of the impure bubble if you will so that means i can call both pure and impure functions or everything goes here and that means now that i that sits on the left-hand side of that arrow that is the result of calling get reserved seats from DP and you saw before when i declared the function i said it which says io of int but i'm now you know open the door and went inside of the io context and that means the AI is now no longer an eye of it it's just an INT isn't because i'm already in the impure context and that mean i can pass that as an argument to check capacity so that's the next the next last line there it says hoist either you know Haskell goo boo goo host either dollar and then says check capacity 10 I R so 10 is just because I hard-coded the capacity of the restaurant very small intimate restaurant you can only see 10 people but then I is just the number of already reserved seats that's just an integer at this point so even if check capacity is a pure function I'm calling from an impure context so that all works and then there's some more crap going on there now if we try to visualize what's going on here we can try to draw it in with concentric circles funny how that works out and so basically what happened here is to say well we get the incoming request it's being dispatched to this function called post reservation which is an impure function that's the one that I just showed you on a previous slide now the first thing post reservation does is it says let's try to validate the incoming data if we can do that let's try to call the database to get the number of already with seats reserved seats from the database with that information we can now call check capacity which is another pure function if we decide to accept the reservation we can come save reservation and then we can you know no matter what we decided we can return you know translate things back into an HTTP result and you will notice that I've drawn in the concentric circles and you'll notice that I've made some of the things purple and I'll make some of some of the things green by purpose because the green stuff is the pure stuff and the purple stuff is the impure stuff and it has to be like this in Haskell because it's the only thing that compiles because I have this constraint that I can't call you know impure code from pure code and I have to structure my DJ in my application this way so you will often hear Haskell a--'s talk about you know IO happens at the boundary of the system yes because Haskell you know forces you to do it in this way there's no other way you can actually do it but if you sort of look at this a little bit you will see that that is very very close to being ports and adapters this is basically the portion adapters architecture in the middle there check capacity that is my business logic in the concentric circle about that validate reservation to h-2b result this is what you'd call your application model or your service layer whatever people tend to call it this is where you adapt your business model to the outside world and then you have the the impure stuff on the boundary M and you could even imagine if you just slice it through horizontally you know through the middle you basically have you know your normal horizontally layered application architecture because you have your you know you are into to the to the outside world on top and then you have your application layer then you have your domain month then you have you data access layer there and the bottle in the bottoms you can also you can almost imagine that there's sort of a can you know shape you know sitting down here that represents your database and then you have you know your horizontally layer the application model there but the thing is it just automatically falls into this model because that's basically all it can do in Haskell specifically so if I was a team leader on a Haskell project and and you know I went on vacation and then you had the same sort of urgency as before where you say I really really want to call the database you know from within my domain model from within check capacity because I really need to solve the problem fast you could try to do that but basically that would mean you can only do that if you change the type of the function to become impure and changing the type of a function is a breaking change and that means now all the rest of your code is not going to compile and and at that point it might actually be easier to actually do the right thing instead of trying to fix all those breaking changes so you know it really just helps you to do the right thing up front and you can do the same thing in F sharp as well.if sharp is not so strict about its distinction between pure and impure but this is the basically the same composition as as I showed you in Haskell before it doesn't really enforce it but there are ways that you can do do that if you're a team lead or Software Architect on an f-sharp project and it's not particularly it's not particularly difficult to keep track of whether you're doing the right thing it shops sort of tends to fall into this this thing this way of writing things fairly easy as well I have a blog post that shows all of this code if you're interested in that so so you don't really have to take a lot of notes if you really want to dive into it and I have that on my blog on my blog and if you can't find it then ping me on Twitter right so that's really nice that we sort of just fall into this pit of doing the right thing having the right software architecture all by itself so let's move on to talk about services and data now when people you know initially learn about object-oriented programming and object-oriented design they they are often taught that up Jake our data with behavior and there's this concept called encapsulation that says you should not really expose the internals of your objects what you really should do is you should have your objects expose enough operations so that a user of the object can just ask it to do things you know tell don't ask if you will so so so that's this whole idea about this whole design idea about encapsulation and object orientation and if people are you know neutral to enterprise development and they're just out of college or wherever they you know learned object-oriented design they've probably come out and they'll do designs like this so they say I need to keep track of a user in my system I have lots of uses in my system and the users have ID they have an IDE and they have a user name I also need to persist my user in a database so I'll do you know create read update and delete that can talk to my database and you know this is a well described design pattern it's called active record and you know if you read Martin Fowler's book you know patterns of enterprise application architecture it's actually described in that book so it sort of like almost seems like this is some sort of of well-known good thing but it's turned out that you know in the last decade at least we've pretty much experienced and learned the hard way that this is probably more of an anti-pattern that it's a pattern at least this is sharpen and and in Java it seems like it still does some good things in Ruby I don't know but Ruby is static is dynamically typed so maybe other rules you know are at play there so I'm not pointing fingers at Ruby at all but I'm just saying well I'm talking about statically typed languages here at least in c-sharp and Java we've learned the hard way that that is actually not a good way to design systems even though this seems very object-oriented you know a user is an encapsulation of data about the user together with all the behavior that belongs to the user this seems like object orientation this you know by principle there's nothing wrong with this but the problem is that you say well I need to do things with users and later on you figure out I need to do other things with users as well you know because I want to be able to send an email to an user so I'll also add that you know feature to the user object and what often happens with these things is that the more behavior you to function to a class like this the more behavior it also attracts so you know you'd end up with a couple of very very big classes in a typical source so source code base that you know where the class is just you know attract more and more of this sort of behavior and they become less and less cohesive and we end up calling those big classes got classes and that is a well-known anti pattern as well so we learn the hard way that this actually leads to some when something that is not particularly maintainable so what should we do about it well there is say there's this book called domain driven design by Eric Evans and it's sort of like two books and one one of the book one part of the book talks a lot about this thing about actually talking thinking about the domain and the business language that you're trying to model and so on but there's also a part of the book that is very concrete and talks about various design patterns and how to think about objects and Eric Evans makes a pretty good distinction very interesting distinction in this book that I think is very variable that I have served me very well doing object-oriented design in the last ten years at least more and he says basically you can think about your objects as falling into two categories and one of the categories has two subcategories so basically he says well the first category of objects we call those entities and these are the these are the objects that you learn about in university and so on so if you if you ever had you know an introduction to object oriented analysis and design you know the question is but how do I do object oriented analysis and design and the you know the naive answer to that question is well just take the you know the problem specification and just look for all the nouns and the nouns will describe your objects and we know that that's not really true but there's some truth in that is some of the nouns would actually be some of the entities you are interested in entities are objects that have you know inherent identity that have you know long lasting identity so these are the the business objects you want to keep track of these are your users your contracts your orders if you're keeping track of a fleet of cars that is your know your car objects or whatever it is that you're even track of these are entities and you probably have those and that's nothing wrong with that and then he also talks about well there's another sort of object that also contains data we call those value objects and that's another well-known design pattern but basically what what Eric Evans then says is that these objects are basically just carriers of data and they don't really in they don't really encapsulate any business logic because we've learned that if we try to put business logic inside of these things business logic changes so it's not a good idea to put business logic that changes you know at a different you know cadence that the data changes it's not a good idea to you know put those together so instead you should put your business logic into collections other objects that are collections of behavior and it calls those things services and you probably you know heard lots about you know services this is basically just another word for manager you know so you have like your user manager and things and all of a sudden you also have your manager factory services and so on but that's basically the stuff that he's talking about here so what to see you saw that we looked at before what does it look like if we factor it to do what Eric Evans tells us to do well it looks something like this and we'll say all right here's a user it has an ID and a username and I even went to the effort of making this immutable which I hadn't needed to do but I just you know wanted to do that and that's it it's just the data and then we can say well what if we need to you know make create read update and delete operations on that oh well for that we have the repository pattern so you can have like your sequel use of repository class that implements I use of repository that does all of that now that's another world well described design pattern that everyone by the way it gets wrong but that's a different story and if we want to send emails we can have another you know service that does that email sender so my experience with doing this is that that actually works you know if you if you need to do object-oriented design and development of a maintainable non-trivial system this actually works this is a way to keep a code base maintainable for years but the problem is you know you have to read thick books in order to understand why you have to do this and how to do it and you need to give everyone on the same page so again we're bit of that situation where it feels like rolling Boulder of the hill and again if you look away for just a moment then someone comes by and totally ruins everything and then you're back to start again and not even that but also you'd say but that's not really object orientation anymore this is certainly the anaemic mark that's an anemic domain model right this that's a very common criticism of this sort of code it says well that's actually not object-oriented code anymore because you've separated you separated data from behavior that's not what object orientation was originally about oh maybe you know everyone tries to second-guess what Ellen came and when he coined the term but I don't know but basically what we taught about em you know C sharp and Java that's not the sort of object orientation we were originally thought taught so what about functional programming does it make it better you betcha because you know functional programming basically wants you to do this it says there that's data and there are functions that operate on data and that's it that's that is how you are supposed to work with with with your stuff this is how you're supposed to write code in functional programming so if we look at this same example again we can say what is the user look like this is an F sharp example of the user it's just a one-liner it's almost declarative well ladies declare it so if you say a user is a type that has an integer ID and as a string username and that's it this is a strongly type type declaration but it has no behavior and well in F sharp you can at behavior because if sharp is what we call a multi paradigmatic language so you can make it more object-oriented if you want to but you shouldn't really feel the urge to do this this is fine this is what it wants you to do in Haskell for example there's a little bit of a different syntax of doing this but but it's basically the same concept and in Haskell in Haskell you can't add any behavior to data data is data it could be as deeply nested and very you know it has it has a complex structure if you will but it's just data it has no internal Associated behavior so we should feel good about this this is not anemic or anything this is how functional programming should look like and if we want to do things with the user if we want to save it into a database or something we can create a module which is a collection of functions that either you know take user as an input or return user as output or maybe even do both I don't know these are impure by the way but that's not really the point but the point is a module is basically just a service but it doesn't have to pretend that it's an object because it's just a module and you could have a module for sending email as well so that's that's basically just what what if sharp wants to want you to do and this is what Haskell wants you to do so it wants you to do what Eric Evans spends 400 pages trying to teach you how to do is basically if you ever had learned Haskell or if you ever learned if sharp you know the functional parts of f-sharp this is what you are supposed to do this is what the language wants you to do so you're falling into this pit of success again that's saying well these two things should not go together they should be separate but they can you know work with each other and that's fine much more maintainable code base you get out of this alright so let's talk about testability because testability is always fun to talk about you know Kent Beck introduced the concept of test-driven development back in mid-90s so 95 96 something like that I can't remember the exact date and it was really really controversial when he started doing that and I picked a test-driven development up in around 2003 it was really really controversial back then there's you know there's been more acceptance of test-driven development now into the you know to the degree where most people actually seem to think that it ought to be you know that's actually you know a well-known best practice now but it turns out there's still sort of controversial and you know from time to time the controversy you know pops up again so a couple of years ago you know David Hyneman Hansen wrote an article called TDD is dead long live testing and basically buddy I'll try to cover what he said in the moment but basically said well it's not that he's against testing but he just thinks that test-driven development actually leads to a lot of problems - what what he calls test induced damage now david hire my hanson can be quite controversial himself and some people really don't like his style because he's you know swearing a lot and he's saying things in a very provocative manner but you know i found over the years that if you actually try to listen to what it is that he's trying to say to you he's really a free-thinking spirit he just not just go with the flow he goes against the grain and sometimes it seems like he makes an you know and you know an effort out of doing that but basically if you say something that goes against the grain it's worth the effort at least to try to understand what it is he says because he's a very intelligent young man I can I can say young man now because my hair is gray so let's try to understand what it is that he says he says test-driven development leads to tests induced damage that is his claim I'm not saying it's my claim is that is his claim let's try to understand what he means by test induced damage so here is a class and the cold capacity checker that tries to check the capacity of one of these restaurant reservation system so this is nice testable she sharp code we have a class called capacity check out it implements an interface called high capacity checker it it uses dependency injection to have a capacity that's just an integer but also an IEEE reservation repository and will need to have the constructor in order to depend to inject those in and save them in fields for later usage then we have on the bottom here has capacity which is the actual value that this class provides it says well given a reservation do we have capacity yes or no true or false so what we do here and the last two lines of code there we say well we'll ask the repository to get the number of reserved seats for that date that gives us a number of reserve seats reserved it's just an integer and then we say then we just do a comparison you know a less than comparison and says well if the capacity capacity is less than that you know what we already have reserved plus what is being requested then the answer is is false otherwise it's true so so david Heine Maya Hansen's point here would be why would you ever write code like that you know the only valuable thing here is those two lines of code in the bottom the rest is just noise why would you do that and then you say well because of testability alright fair enough so I'm not even saying that this is bad in the sense that you know if you ask me to write a non-trivial maintainable system in c-sharp that has you know test coverage this is what I do because I I don't know of a better way of doing it in c-sharp so I'm not really pointing fingers at you know this is I'm not saying this is bad practice you may need to do this but that is that is a high cost to pay to have you know testability because that really it causes your code to be spread out instead of being you know going well together and it's difficult to manage and difficult to your navigated code base like that and you just have another lot of noise that you don't really need to have what would it look like in another language like an f-sharp for example now here's something it's not even similar because this actually does much more it makes the same sort of boolean comparison but then it also instead of just returning true or false it actually returns a failure with some more information about the failure if you know why it failed if that's what's the case or in return success you know if if the boolean external so if I just wanted to do it entirely similar to the other one just returning a boolean I could actually have done that in two lines of code but now it turns out to be full now this isn't you know apart from that this is exactly the same thing because this this is a function called check and it takes three arguments it takes a capacity argument that's just an integer it takes get reserved seeds which turns out to be another function and then it takes reservation as the third argument so what you often see in functional programming is that you will see functions taking other functions as argument this is what we call higher-order functions this is a completely normal thing to do now get reserved seats is a function it plays the exact same role as that repository that you saw before so on the first line you can see it says let reserved seats equal get reserved seats reservation dot date that is that is a function call I'm calling the function get reserved seeds with the reservation state and I get in I'm getting out of that function called the number of already reserved seeds that function is just being passed in and it's sort of polymorphic because I'm not asking for a specific function I'm just asking for a function that can take a date as input and give me an integer as output any function like a does you can do that fits that description and that will compile now is this function that we're looking at here is that pure or impure it turns out to be important in just a moment so therefore the question is this pure impure well it turns out that most of the stuff that goes on up here is actually pure the only thing we don't really know about is we don't know what get reserved seeds actually does so it may be pure it may be impure now if get reserved seeds is sort of like the repository like it's actually querying a database to get the number of already reserved seeds that's an impure operation because it's not deterministic it depends on the state of the database so in that case it would be impure and if it's if it's impure if that is a database called then all of this function also becomes impure that's how we know how that's how it works in the char on the other hand if get reserved seeds is a pure function then this function is also pure because there's nothing else in that implementation that could make it impure why is that interesting it's interesting because it turns out that pure functions are always testable and I'll get back to why that is but let's just look at what would your tests look like what would a unit test look like of this function here's just one example I'm only going to show you one example just to give you an idea about it so the scenario here is that I want to test that it returns the correct results if there are no prior reservations in the system so the system just booted up there are absolutely no reservations the restaurant just opened and I want to check the first reservation that comes into the system that's basically the scenario here so I'm saying well I need to set up a little bit of context I need to define what the context is so I say well what's the capacity of the restaurant I've just you know hard coding that in this test case 210 because well that suggests an easy number to think about I like the number 10 and the number 1 and so on so that's just arbitrary but it's just to have a test case then I say for the in on the next line I say let get reserved seats underscore equals 0 the underscore means that I don't care about the input this is a local function this is a function that only belongs to this test case but it always returns zero no matter what the input is that is a pure function because it is as deterministic as possible it always returns zero that means there are never any real sort of seats in this particular scenario that's a pure function and then I have this let get read reservation and then a lot of crap you know with with a lot of stuff yes by the way this is my email address you know all the spammers already have it so you might as well get it as well so so that's just you know that's a little bit that looks a little noisy because there's actually a lot of code there but it's but it's just data and we could put that in some sort of factory if we wanted to do that but I just I'm just showing you a self-contained example here and then you know on the on the third to last line there it says lit actual equal capacity check capacity get reserved seats reservation I'm taking those three arguments that I set up in a range face and just passing them as arguments I'm getting an actual result back I can and I can now you know declare what did I expect and compare those two things to each other and this test will either pass or fail that was easy so you might wonder now is it coincidental that this was easy to test and it turns out it's not there is a reason why this was easy to write and the reason is that as long as a function is pure it is intrinsically testable and let's try to understand why that is in order to get help to understand that let's refer to Jessica care and just care sometimes talks about a concept that she calls isolation and she says isolation is you know when the only information a function has about the external world is something that is passed into it via arguments then the function has isolation and that is a good thing by the way this is so you know a desirable quality of a function and so it's basically what we're saying is a function does not have any implicit knowledge about the outside world everything it needs to know in order to do what it is that it wants to do needs to be passed in as arguments now is a pure function isolated yes always because a pure function you know it must be deterministic otherwise it's not pure and by deterministic we're saying it should always return the same output given the same input it is only the input that can influence the output of a function so therefore a pure function is the subset of all functions that have isolation they you know the f-sharp function that I just showed you may not be pure we talked about that but it has isolation because it makes no attempt of you know using it you know implicit knowledge about the world everything it tries to do is based on its function argument so it has isolation it may not be pure maybe it is we don't know but at least pure functions the ideal of functional programming or pure functions have isolation why is isolation interesting let's talk about unit testing my sense of isolation you know important in the context of unit testing well you know let's try to see if we can come up with a definition of unit testing basically you know if you have ten experts in unit testing and you ask them to come up with a definition of what a unit test is you'll get 12 answers so so it's not really possible to get you know one definition of a unit test but here's one that I use a unit test is an automated test that tests a unit in isolation from its dependencies and most experts and unit testing where they disagree is where all the controversy is is it means it's basically around you know what is a unit and I'm not particularly interested in when a unit is the thing that I'm interested in in this definition is the word isolation we want to test the unit in isolation of its dependencies this is the same sort of isolation that jessyca car talks about all right so let's try to understand why you know my David Heine my handsome maybe up onto something and why it's not a concern in functional programming so David Hina Maya Hansen says well isolation is an important quality of your code if you want it to be testable and then you know you try to combine that with object-oriented programming and bang you have test induced damage and why I think that David Hina Maya Hansen draws the wrong conclusion he says and that's the fault of test-driven development whereas I'm more and more beginning to see I can do test-driven development with it with functional programming and it it just it doesn't it does not to leave to lead to tests induce damage so therefore the problem must be somewhere else and I think actually Opie is the problem so let's try to understand why I object-oriented programming is it's a problem here now the idea the design idea the philosophy behind over P is encapsulation this is what we are aiming for this is what we were taught is important in object-oriented design if we want to be testable we now need to aim for this quality of isolation because otherwise it's not really testable now again we are we're in this situation where we're trying to roll a boulder up the hill it is possible to sit there in the intersection between those two you know sets and say here is testable oh-oh-oh D it is possible but again you know it requires a lot of effort and it requires a lot of diligence and a lot of you know constant monitoring of what's going on because otherwise you'll probably add to one of the sides and what's often happening is if you don't if you don't have a focus on you know test-driven development or testability you may add to the side of the encapsulation that's basically that what happened the first ten years of the dotnet framework for example you know the first ten years of the base class library internet it was infamous for not being particularly testable it's been it's much better now but but it was really difficult to test you know base class libraries the first ten years of you know mid-life the.net framework lifetime now on the other hand if you are focusing on test-driven development you often err to the side of isolation and and then you get something that is you know that has this test induced damage that that David Heine my hands and talks about but in functional programming is different because what's the ideal function in functional programming well it's that pure function that we already talked about that is the design ideal it has isolation and because it has isolation its testable there may be other ways to make things testable but if they have isolation they tend to be testable but there is no you know there's no intersection that you need to fit into basically you know if I had a team of you know developers who are already good at doing functional programming I could basically just you know say to them if I was the lead developer or software architect I could say just write as many pure functions as you can do good functional programming design write pure functions and I'll go on holiday and I'll come back and then we'll see then we can start testing it that might actually be possible because you know if they write pure functions they are testable so what I've tried to do here in in this talk is to talk about you know in my experience there are there were lots of things more than three things that I've experienced during my career as architect and lead developer and so on where I say this requires a lot of effort this is really difficult to do in c-sharp and probably also in java but it turns out that you know if i try to rephrase the way that I approach my things and use you know functional programming instead all of these things just sort of automatically you know fall into a successful place it just automatically happens so we looked at three things we looked at ports and adapters we looked at services entities and value objects we looked at testability all of these things turn out to to work very well it's not the only three I know of others but I didn't have time for you know to describe anymore if you want to know more then well you can come and talk to me afterwards I will not have time to do Q&A right now but I'm happy to talk to you afterwards otherwise you can also visit my blog I you know block a lot about these things I also have blue psychosis I have blown psychosis on you know test-driven development unit testing functional programming all sorts of things and you know since I go under the pseudonym blur I just created this bit lead slash blur plural sight so that's just a link to all my courses if you don't have a flow side subscription you can come and get a voucher I have I'll get it out in the moment I have vouchers that'll give you a one month a trial subscription to pull aside for free so you can come and get those if you need them but otherwise I'll let you go for lunch in ten seconds I just want to say if you if you if you have questions then come and talk to me now or in the hallway or if you see me around in the conference or send me an email or whatever I'll be happy to answer any questions that you have but some of you want to go to lunch now and I think you should do that so thank you all for coming and have a nice lunch
Info
Channel: NDC Conferences
Views: 92,332
Rating: 4.8936372 out of 5
Keywords: NDC, NDC Sydney, Mark Seemann, Functional architecture
Id: US8QG9I1XW0
Channel Id: undefined
Length: 60min 10sec (3610 seconds)
Published: Fri Aug 26 2016
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.