Readers vs Constructors: Dependency Injection showdown by Adam Warski

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
we can stop okay right hello a great to see you here and my name is Anna Marquis I come from Warsaw Poland and I would like to talk about dependency injection in scala I think there's definitely a couple of approaches which can take and probably read around on the internet you can do you can use constructors you can some say that the reader monad is a replacement for all dependency injection frameworks on the other hand we have play which uses juice by default so maybe I would like to try to clear up some of the confusion which construction and which abstraction to use to use web and so there would be like four slides and then we will do some live coding and let me know if you would have any questions throughout the talk please let me know I will try to to clear things up and okay so so let's start and so first of all it may be a familiar with the whole dependency injection thing maybe not so just to set some common ground and probably if you come from Java you have heard the term and if but like not everyone comes from Java luckily so it may be that introduction would be useful so independency injection imagine you have two services and these services are usual classes but let's talk about services right now and one service called another service right that's quite a common scenario so one service is a dependency of another service or a client and so with dependency injection what we want to achieve if we want to decouple these two services right we don't want one service to be aware of how the other service is being constructed it just wants to use it it doesn't want to know how how to build the service how to create it right and that's a dependency injection also implements inversion of control right so the service doesn't know how to create its dependencies it just wants to use them and this has a number of benefits so the first being probably a hiding implementation details right so one service doesn't we know how the other services is implemented and what it should only know is how to use the interface and other benefits include a testing so you may want to swap and swap services for testing use and more complementation a stub or something like that and also the pending dependency injection helps with separating concerns so we're really creating a service creating a class is a separate concern than actually using it and it also helps you to organize code especially if you have like business like code let's say with some complicated business logic these these buttons help you to organize your code in a in a readable way and there's a couple of ways like especially if you are coming from Java again you probably know these and you can either pass as to how the dependent centers in section implemented at the code level so either use constructor parameters and all the time it's also with getters and setters but probably nobody does it and this way in the more likely or you can use the service locators it's also a possibility so we have like one global object they can register services and then ask ask for services back and so what I would like you to get out of this talk and hopefully I would succeed it's convincing you that you don't really need anything special to do dependency injection because in its essence dependency injection is just using parameters class parameters and method parameters so that's a construct like we all know so you don't need any libraries and frameworks and the libraries and frameworks can help you in some cases so they they certainly help you in avoiding to write new anywhere but they are like not mandatory to do that they bring on some additional features and you should really only use frameworks if you need those additional features and they aren't like if you are bootstrapping and you project probably you don't need any of them and so before we start coding it just so that you know who's talking to you as I said I'm I'm anniversary I'm a co-founder and software engineer software mode which is a software yet another software consultant is color consultancy based in Poland and we do custom software for clients and most of them from the US and from Western Europe and I have some open source projects and like my choir which kind of that's where my interest in dependency injection come from because the library for for the I last came to quick glance and a couple of others I write a blog and I write some blogs and I have a Twitter account which probably isn't it surprising nowadays okay so on to the coding part okay so we will be we will be dealing with cooking and on a very basic level and so we will build three abstractions one on top of another so first to have a key value store which is already written here a big value so it's quite simple it's a support basic crud operations so we have a create read update delete and like what you would expect right mostly this implementation is total is in memory but you could imagine that it could be another implementation which could write to something more fancy like a real database now we are we are we are ready for that because we return futures everywhere so we are ready to be reacted with like the first step and so yeah so this information is quite simple we just use a concurrent hash map as the background so that will be our first abstraction so the second thing that we the second level that we will build on top of this key value storage a will be food storage okay so we will we'll build a food storage and on top of of this food storage will then and build an abstraction to actually combine the various ingredients and some food okay and so the food storage will operate on some primitives I would just area areas I'm over here so that the code will be more readable later so we'll have food name which is a string food quantity will be an end and the food key value storage will be a key value storage from food name to quantity okay so our food storage will be a storing will be basically a kind of a map from from food name to quantity and we will have like two basic methods which operate on the food storage so we can add some food it read some food we need we need the food storage we need the name of the food and quantity quantity and this gives us a future unit so that's a purified affecting function okay so to add some foods our storage what we will do is we first read the current amount that we have so we read now we do calculations so the new amount is simply we add at the current and at the given quantity to the current one all of this man just add past the past quantity and finally we update update and update okay and we use a unit okay so that's a very simple method similarly we can add food we console remove food from the key value storage and given a name and the quantity now the result here won't be a unit it would be a actually quantity because we may remove a small amount and then the request is right if there's not enough so again we read the current an amount and now we do some very complicated calculations because we have to calculate how much we can actually get so in stock is the current or zero taken will be now that the complicated math math minimum and in spoken quantity right and left is in stock - taken and now we update if left is greater than 0 and then we update it with what's left I will storage otherwise we will move it all together ok and we return the amount that we managed to take ok so now we have on top of the key value storage abstraction we have with another abstraction that food storage right we can add a given quantity of food and remove some food from it ok so far so good nothing very fancy I hope that that's not nothing complicated so now on top of that we will yet another abstraction and that will be used to combine ingredients and cook some cook some food ok so we will define a cook sauce method we will be cooking pasta sauce so let's say we want to cook a given amount of sauce so now what what we will need so what what do we need to cook sauce well to cook sauce we need a way to obtain ingredients right and in the end when the sauce is cooked we need a way to put put back the cooked result so what we need is a method 1 method take food witch witch witch witch witch witch takes a food name and quantity and returns a future quantity that's how much we managed to remove let's look similar but now it's not exactly what we have implanted before and we need another method add food which has a similar a signature so it takes food name quantity and returns a future net so purely side-effect in function okay and as a result again we returned the quantity of sauce that we have managed to cook and okay so how do we cook sauce where we take some Tomatoes right take food tomato quantity and we take some other of the vegetables non tomato vegetable same amount and we also take some garlic garlic is always good and garlic half we take twice the amount of garlic that we actually need and now we calculate how much sauce we have cooked and that tomato q divided by two because some of the evaporate and vegetable times 3/4 and finally now we have cooked this off so we can put it like we put back whatever we have cooked into the food storage and using our add food a function so we put put back the sauce and the quantity given okay and we dispose quantity okay so now we have built like this method it knows nothing about food storages it's not nothing about a key value stores and so on it just takes some methods as parameters right which allow it to operate so and so this method to actually be able to cook sauce to prepare the sauce it needs to take food methods to retrieve food end and the adven method to put it back in a mess in a similar way we can implement a cook pasta method right which works exactly the same its uses different ingredients and because it's parts are not sauce ok so now we have these two these two methods so and all looks good so now we have to actually use them in an application to be able to test that that things are working and so one thing that we really need for sure is we need an instance of our key value store right which will u.s. drought and you and we will use it throughout our example and so now I will just prepare to paste and a snippet so first we need to populate the the key value store with some ingredient so let's say we go shopping we buy some Tomatoes vegetable garlic and so on so let's we are calling the add food methods here which updates our key value store with ingredients right and let's shopping and now we can implement cooking so for cooking what we do is we first go shopping then we cook some sauce so to cook sauce what we need to do so let's say we need wants to cook five sources or five of sauce whatever the unit is now we need to pass in the take food method right the take food method and cook sauce if you look here it takes the full name and quantity right it doesn't know anything about the key value store and there's no reason why it should know anything about the key value store so what we do is we pass in the partially applied and take food method which we have a sorry not take add food method which we have implemented earlier okay and so we are passing the key value instance that we have defined here and similarly sorry that was remove food sir remove food and add food similarly partially applied okay and in a similar way we can cook pasta and we cook 10 pasta because we like pasta and we use some string just to see the results that it actually works [Music] pasta and to be able to run our program we will eat things so that way that result and results so cooking it's very fast cooking only one second okay hopefully this will compile and and run and so and yeah you can see that it compiles or not okay so so far what we have done we have implemented so we have one basic abstraction which is the key value store on top of that we have the food storage which for now takes the form of two methods but the methods take a quick value servant and operate on it and finally we have the third level which is the cooking level here we take as parameters the methods which operate which allow us to take food in some way right and finally when we actually wants to run our application we have to pass in the dependencies to the appropriate to the appropriate functions so so far we have used only functions nothing nothing very complicated okay so by the way like in a real application all of these sections would be in separate files here I'm having it all in one file just for just for demo okay so now well we are not happy with our code so far we want to improve it so we have spotted that there are some things that there's some repetition which we would like to eliminate so for example here the the key value parameter is a common for both for both methods and we actually decided to extract it a higher and we decides to create a class fridge which will take the key value parameter and they keep the key value storage and and so that we can use it in both of these methods okay so we have extracted some of these parameters into a 1:1 level higher so now we have a class with with two methods and what justice is parameterized by a by our key value store so in a way you can think of these methods inside this these classes as being partially applied functions right because if we have an instance of fridge we have the first parameter here right so it's already like partially applied and so that we can use these methods over here so now why why did we extract only the key value store and not the food name and food quantity well the role of the parameter is a bit different right the key value store is kind of a service type parameter right and it's probably going to be fixed for the duration of the operation of our application and these are more data like parameters right so they are going to change from invocation to invocation so that's that's why we have decided to split the parameters this way okay in a similar way we can extract these two parameters here right and so we could create a class which had the to take food function and add food functions as parameters but we notice that these signatures are exactly they are exactly the same as the methyl as the methods over here which is a very lucky coincidence because we can now extract a class take a fridge as a parameter and remove those ugly functions of ugly dependencies all together okay and fix the code of course because now now we don't have like direct access we need to go to the fridge and enter and take the food or add food okay so we have extracted some parameters over here and so we have we have SES so we have moved the parameters to plus parameters right we have created like classes are like wrappers for for final four methods which have similar functionality and which have similar dependencies and so now what we ate what we need to do to actually and why doesn't this compile because this will take food its remove food and so now what we need to do is we need to update our end-of-the-world code right where we actually bootstrap and run things so we need to create a new fridge it takes the key value store as a parameter and we need to create a new cooker cooker and which takes a fridge and now we have to do some change more changes here so we add if we call this on the fridge right we have moved the parameter from the it's not law it's no longer the first parameter of the method but it's a method on the class so that's not a very big difference and here we call methods on the cooker and remove these dependencies over here because these dependencies already passed at construction time and over here ok so I called hopefully stir works yes still compile and work let's learn it ok so now what we have done is we have a notice that some of the dependencies are common for for the methods and for the fridge methods and for the cucumetto so we've extracted them as a class parameter right and and also like we've noted that they are like two kinds of parameters these are like the service type parameters and if I like more the data like parameters right similarly here the fridge doesn't change as it's always the same parameter like throughout the operation of our application but the parameters here do differ from invocation to invocation okay and so now to complicate things a bit we will create an IOT enabled fridge so let's say we have a new we have a new dependency and so we will have we'll have a Nike trade just to show you how we can add more dependencies the IOT is very simple it only allows you to notify users with a message we will have a default implementation cloud IOT gateway user name string password string and it implements IOT and we'll just do a future success for print line sending via cloud the given message ok so now our fridge so we have a very simple IOT and service right so now we want our fridge to be able to use this IOT service again it's an it's a service type parameter so we added and like to the common to the common parameters in the class and now we can use it anywhere we like like so here we can say IOT and the send not notify user and notify user and at a given quantity of a given thing was added to the fridge and here we say that it was removed from the fridge ok so now we are almost done if we try to compile we will actually get a compile time error so here we have an unsatisfied dependency right so that's that's a good thing of actually using Scala for these things because you get compile time errors you don't like if we loose for example right we only get a runtime error that the dependency is missing so we need to create the IOT dependency so we do that's bad cloud IOT gateway user name scott password tiger ok and now we can use it here it's an Oracle gateway so we can use it here and so this will almost work ex cept that at the moment when a fridge is constructed IOT will still be null right because this evolved and so there are two way to fix this one is to essentially drive well three ways once within 30 days you will get a warning the other way is to manually construct this in the right order and the third way is to let it evolve and that way the the right order will be figured out for you automatically so we don't have you don't have to use you but you don't have to worry about it and now we can try running it and you can see that we are getting some IOT notifications every time something has happened to the fridge okay so now what we have done here is from from like the new things we have added a new dependency so now our and our end of the world code is getting a bit bigger right we are when we actually want to run the application we have a section where we create the object graph right so here we create the object which is kind of more less static throughout our application right because these reasons we change you have the services which are wired together and they operate latest or like when your application works and so we have a section where we create object graph using Scala and lay the vault and then we can use it to the actual work okay so let's let's that's like so what we have here is the constructor Base dependency in the dependency injection as you can see it's quite simple right it's just using it's one step from using methods for everything and passing around functions but that's that's not very convenient because you get a lot of these function parameters so we can we can use the constructor by dependency injection actually make our life a bit easier so now and let's let's jump to the second part so let's say we have a change of heart and just needs to update my notes okay let's say we have a heart and we actually want to track exactly when we access the key value store so if you look up here at the add food method signature well it returns a u.s. future so we kind of suspect that there's some a synchronous things going on in the background but we don't really know what what what kind of a singles operations right the futures completely opaque and so let's say now we want to track exactly when we access the key value store so before this key value store was an implementation detail of the fridge right so if we look at clients of our fridge class like the cooker right it has no idea that fridge is implemented using a key value store okay so that it was an emblem in an implementation detail and we did we we achieved that using a constructor based dependency injection but now we have a change of heart and we actually wants to make it explicit that we want to make it visible in the method signature that whenever use a key value store you want to you want to have it in the in the signature so what we will do is remove the parameter from here and we will move it to the to the method signature so but we won't edit to the method parameters but instead will return a function which so the result of our method will be a function which needs a key value store to operate and returns a future okay so as we return a function here well we need to return the function right so what we do is we take we take a parameter called the C and then the rest of the implementation is unchanged right it's a function we take a parameter and return some results which use this which use this which uses this parameter and we do a similar thing over here okay and so again we have moved the dependency from the constructor to the results type of the of the method okay so far it's not a huge change however if you look at the signature of the ad food method you can now with a high degree of probability be sure that you have a future here because a food key value store is used right well most probably the key value store parameter is here because it's going to be used at some point so you have like you know that the side-effect in the Etzel method are due to the two using the key value at least in part okay so now let's make this called one compile because we have the clients of the fridge right right now the remove food method returns a function which needs a key value store so we need to take the key value store from somewhere but we don't have it so we will return and we will return a food key value a function here as well and we will do the same the same changes as we did before and now we need to change our code a bit so now in the cooker the key value store stopped being an implementation detail of the fridge instead some an explicit dependency right because we wants to track which method uses the key value store and indeed the cook sauce doesn't need a key value store to operate right it does have that effect because it uses the fridge and so we will return a function here so we take to implement the function we take a parameter and we pass it on to whatever the fridge method returned right so they remove food method that one now returns a function which takes a key value store and then only gives the result so to get the result we actually in how we actually use a we are we actually invoke that function here and the same happens over here for vanya and so FC and we have to pass them and the partner over here okay and finally we have to fix our graphic creation code so we remove it we remove the parameter and we have to add the method invocations everywhere over here so this isn't very nice but we'll fix it in a second and over here as well okay so now it compiles again okay so now what we have achieved and what we have changed we have moved the key value dependency it's no longer an implementation detail of the fridge but it's something hopefully opposite it's an explicit dependency which we for which we want to track usage right so we need we need to know exactly which method we possibly transitively touch the key value store and so now we know exactly which method and so we have kind of affected tracking right it's like an effect tracking system okay so this code isn't very pretty so we have all of these functional vacations over here so how can we make it nicer and here comes in the reader moment so the reader monad and it's just a fancy name for a for a function sorry okay okay so the reader monad is just a case cloud okay which encapsulate a function for a fixed first parameter okay and so the reader monad is a case plus which wraps a function and so just be precise we need to add the dye parameters here and so and if we fix the first type parameter so if we fix T and only vary the second type parameter let's will form a moment so what does it mean in practice in practice it means that you can use the for comprehension to combine a series of such in of that construct okay and in fact we will use a slightly more advanced variant so we have also the reader T which has three type parameters and tu and it wraps a very similar function so here is the original key it's also a Mon AB and it Trump's a function which takes a parameter and returns erupted erupted result okay so the wrapper for example can be future right that's that that will be the wrapper in our case and it also forms a monad if F is a moment okay so - so to combine the result it will use however F is combined later okay so that's if you if you look at this function right it's like it's exactly the same shape as the one over here right and so we will we will wrap we will wrap the functions that we returned from these methods into the reader monitor and this will allow to make our code a bit nicer so what we will do is we will return a reader T future food key value store unit and differ this means that it's what a function which takes a food key value store and return a unit wrapped in the future okay so maybe I will just write it in a comment over here that's so that it's clear is a method it's a function wrapped food key value store future unit okay and well to create the wrapping what we have to do is we have to create the paste that instance right so we simply write with dirty that's the constructor of the that's the constructor of the case class right and then we pass in the function okay as before so no no no no changes a very similar change over here on a wheel tank 1td reader t okay and just to get the braces right okay so they remove food let me up make it like this the remove food again it takes the case class constructor and drops a function okay so so far so good now let's proceed to the cooker so now the cooker well over here these methods now return and not functions they return the web function three dirty instances right so we would like to use them and so I would just copy that so we notice that the function that we return here and also the same like the same shape so we can also probably return the wrapped version that's what we will do and well we could wrap simply wrap the function and so we could simply write reader key over here right and wrap the function and then unwrap the results that we get here and and invoke them but that would be just more manual work instead what the reader monad allows us to those thanks to the power that it's a monad we can use it Anna for comprehension so we can remove that parameter and we can remove all these invocations so and so what the reader monads does for us right so that's what what the flat map implementation in the reader T does it a it invokes all the functions in as we combine them right so here like before you can see it below here we have manually created a function and then invoked all of the dependent functions with that parameter the reader Mona does it for us automatically right so we no longer need to return the functions we can just compose the reader Menard instances that we have stained here great so the code again looks much nice that we don't have all that noise with the functional vocations and so on but behind the scenes it's it works exactly the same it will be a function that once provided the key value parameter will invoke for this function then it will invoke this function with with the person parameter okay that's that's the Monad part which essentially means that we can use that in there for comprehension without that that noise okay and we also need to change some of things down here and so before we head again those function invocations which are just boilerplate so we can remove all of them because now we can combine all of these all of these methods the add food method they returned and they they returned with our instances so we can simply combine them into one big reader instance which when applied the parameter will apply all of these and in the sequence and similar over here although like one thing you don't really see behind the scenes right if we explicitly call the the functions here this has type future or quantity right and now if we remove these we get the reader T of like these these parameters right so we get a slightly different return type and so our cooking value is now a big reader reader instance right which wraps a function which when applied to a parameter will run all of our code in sequence so we can't reuse cooking and Evon arrow it result because it's a reader at the future so thing to do is we need to actually unwrap it so the we are not producing run and if you go to the source you can see it's the case class which is called place you not reader key but it's the same thing don't worry the case lat and it wraps it has a single parameter which is a function just as I told you so and so so that that's exactly what what what we have right and and that parameter is called run okay so to unwrap so to get the function from the reader Monett we just go run and we provide the parameter and it hopefully should work that's fine okay so five minutes that's not a lot but we are almost done okay so the crucial difference here with the with the reader monad we don't really do dependency injection right we because we don't less like there's no dependency hiding we don't hide the dependency quite the opposite to make it explicit okay and so that's like quite a crucial difference between these two dependency injection hides your dependencies the reader Monat makes them makes them explicit and they're both useful I guess that that's the good part you can use both constructor base or whatever based dependency injection and the reader Mona but for different things right so here in our example we still have the IOT dependency hidden right a client of fridge have no idea that the fridge uses the IOT dependency right but they're very well aware of the fact that that it needs a key value store to operate because we have decided to track a track as an effect okay so and you can use the reader monad to track effects in your to track effects in your code and you can use constructors also known as dependency injection for hiding dependencies well now probably I should also take a vinta Martin's keynote and here the whole slide of why on why the we on why the reader monad is bad and it's true that you could do the same with implicit functions and what you will be able to do it with in with implicit functions in dotty however the important thing is that this would be like in three years when Scala three throws out and so until that happens you can use the reader monad for the same tool to to implement this kind of effects tracking so I agree that the implicit functions is syntax is nicer and more lightweight and also faster because you don't have to create the reader wrapper but it's well it's not yet available so so for the time being we have to live with the reader moment okay so yes that's also the main point of what I wanted to show you is that you can have both constructor based dependency injection and use the reader monob in fact you can also combine the two if I will just jump to an ad to a a bit more developed example so here let's say that we have modified our example so that we have a key value store trait right and trim to implementations in memory and cloud right and so now when we want to track the effect that somebody uses a key value store we just wants to track the fact that a key value store reduced not which implementation okay so we can use as before we use the reader monad to track the effect of using a key value store but not any specific implementation just the trait right and if we go now scroll down and here's our end of the world a configuration where we create the object graph so when creating the object graph we can actually use constructor by dependent equation again to create the the dependencies like over here right which we then pass on to the reader Mona so this all like fits fits together okay so just to sum up you can use and you probably should use constructors and constructor base dependency injection or constructor parameters however you want to call it try them implementation details you can use the reader monad to make dependencies explicit and strike a fax and they are both useful and they can work together so what about frameworks right and what about libraries so frameworks give you a short-term profit because when you start your project you don't have to worry about writing this what wiring this object graph but you well and it is handy it is like nice to not have to write all these new invocations and so on however you give up a lot of control in exchange right and with the pure Scala approach you can create your objects as you like in any way betting on configuration based on the environment using new like well it's just code right when using a framework you like commit to doing it the frameworks way so that's why I would always start with a clear like with a pure Scala approach and only later maybe add and add add a framework which can have their benefits right if you need a opie if you need interceptors if you need like scoping that's when frameworks can be useful but that's usually only in a later stage of the project so some some libraries and frameworks to look at well that's my choir which is mine and of course best that's not a framework it's a library which helps you to to create the new took to create the object graph and so we don't have to enumerate all the dependencies it generates the new invocation for you that's grafter which also combines the reader monad and constructor by dependency injection in a slightly different way so I encourage you to take a look at that there's juice which is by default used in play however if you are using play it's very easy to switch to compile dependency injection using constructors there's a whole section in the documentation on how to do that it shouldn't take you more than like 5-10 minutes to remove jews and and and use compile time independent dependency injection and there are some service locator implementations in scala as well for example is called the end and sub cup okay so one last slide with a thank you for your attention the code for the presentation is available on github and yeah that's it thank you very much i don't think we have made me have time for a couple of questions so I haven't seen Mac or I've seen graph here before does Mac work kind of like use macros to generate reader monads or is it more no no no so the only thing back where does is it generates this piece of code so you don't have so if you have like five dependencies here you don't have to enumerate them it just it's a macro which basing on the current environment generates this new invocation for you and that's useful like when you add dependencies more them around you don't have to update this wiring code so that's all it does how would the reader Bernard scale up to having multiple effects in a single function call and so if you have multiple effects where you would create for back case class which would combine them all right the tricky part is if you have one function which has effect a and another function static effect B so I have to kind of glue it by hand so and that also will be nice so with implicit functions because was yeah that's why they work okay so I'm here until the end of the day if you were to want to catch me or you can mail me or write on Twitter or however and so thank you very much again you
Info
Channel: Scala Days Conferences
Views: 4,452
Rating: undefined out of 5
Keywords: scala, play, scaladays, scaladays chicago, scala days chicago, scala days, adam warksi, Readers, Constructors, Dependency, Dependency Injection, talk, scala days 2017, play framework, dependencies, cake pattern
Id: AkOFubm-9L8
Channel Id: undefined
Length: 48min 31sec (2911 seconds)
Published: Wed Jun 07 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.