SF Scala, Adam Warski: Framework-less Scala Dependency Injection framework
Recording: SF Scala, Adam Warski: Framework-less Scala Dependency Injection framework
so hello thanks for coming and thanks for having me here my name is adam varsky i come from poland and i'm visiting here for a while and i would like to talk about how to do dependence injection without any frameworks in scala so i have got a couple of slides and then we will do some live coding and yeah we'll see how it goes so if you have any questions during the talk please just ask them you know anything just tell me okay so uh well dependence injection i guess it's quite a popular pattern right most of us probably heard about it use it's in some form either in java or maybe in scala but especially in java like there's this whole uh thing called you know dependency injection containers and frameworks and it may seem that dependence in injection is in fact something complicated and i would like to convince you that that is actually quite simple so if you think about it uh especially when we are using dependency injection through constructor parameters uh the dependency injection is really just using parameters right it's it's nothing much that's a fancy name for passing values to a constructor or passing parameters uh to a constructor so it's really very simple um and you know to use parameters we don't really need any any frameworks which do some magic for us so maybe we can just use scala instead um so one of the things that's like a number of features in most dependency injection frameworks or containers and one of their features is that you don't have to write new anywhere right uh so maybe we we shouldn't be so afraid of writing new in some places of our code but but maybe we can avoid it as well okay so that's just like a very quick introduction and now a very quick introduction about me so uh yeah i'm a software engineer i'm a coder and the co-founder of software software is in my personal opinion and of course which is very objective one of the best software houses from poland we do a lot of things in java and scala we are a type sort of a type safe consulting partner and we have a flat company structure so everybody is a ceo and yeah that's like it's it's it's not that funny like you have a lot of responsibility right um yeah and so personally i've been doing some open source stuff for some time i've written a vibrate module called anniverse for entity auditing um i maquire which i'm going to talk about today elastic mq i write a blog sometimes hopefully about interesting stuff so i invite you to visit and i have a twitter account which probably isn't that weird okay so let's let's start coding so first of all i'll create a new class let's call it as f i'm not not very not a very original name so it will be an application so first of all i like to see an example we will need an example object graph right so whenever we do dependence injection we need some form of an object graph so that we can actually you know express the dependencies between the various parts of the business logic and so that then we can satisfy the dependencies using our library container or something like that so i'm going to uh our example will be a food based um so we will be we will want to create an instance of a restaurant so that we can eat a steak with potatoes so yeah yes yes the code will be on on github yeah it is on github actually okay so we will have a number of classes so first we will have a field class we'll have a digger class so on the fields we'll grow potatoes and digger is used to dig out the potatoes and then we'll have a potato farm and which has two dependencies a field and a digger so that's the agricultural part then we have a cow pasture and a meter we transform cows into meat and finally we have a restaurant restaurant takes potatoes egg takes meat it takes the meat and it has a single method order steak with potatoes um and we will just show the instance and welcome to this here's your order okay so like typically uh i have written all the classes here in like uh in one place but typically would have like a number of packages right would like have so probably this these three would go into one package right and that would be another package so our logic would be divided somehow but just so that you can see the example on one page it's all written here right but normally we have our business logic divided into a number of packages and number of classes and so on right so another thing i'm going to do just for demonstrational purposes is i'm going to make these case classes so that we can see that we get like nice to string representations automatically without actually writing any code but that's only for the demo okay okay so now so we have these uh these objects right they have some dependencies and so like the dependencies from a graph right and so we have an object graph so we want to create that object graph somehow and so our goal of course is to call the order steak method on the restaurant so in order to do that we need to have an instant instance of a restaurant right with all the dependencies so like the simplest thing that we can do and that works in a surprisingly large number of situations is we can uh simply create the new instances by hand so that is going to be called manual dependence injection okay so let's do that right so we create a field first we create a digger okay a potato farm and with the field and the dagger a cow and me terry and it depends on the copish right so we pass all the dependencies inside parameters and finally restaurant and depends on the potato farm and the meter right and now we can call our method and hopefully it so let's just see if this color compiler works and if this runs and hopefully it does so now when doing manual dependency injection while all of the classes are like spread right somewhere in various packages in your application uh this part where you actually create the object graph it's going to be written somewhere like as far as possible right like at the end of the world so you actually create the instances only when you absolutely have to so like the main entry point of your application you create the instances you you satisfy all of them uh by passing the parameters and and then you can run your application right so this part over here that's like spread out right and this typically when you do manual dependency injection you just write it as late as possible right so you want to avoid creating the actual the actual implementations and actual instances as as long as possible yes and we can see that it actually worked right uh i'm not sure why that's smaller one than the upper phone but yeah you can see that uh the two string got generated nicely and the object graph is generated nicely okay so that's manual dependency injection and as i said uh if you you know it works in a surprisingly large number of situations uh you don't we don't need any containers right we don't need any kind of reflection annotations and so on to actually do it it's very easy to understand which is also i i i think it's always good to have code that's easy to understand and there's no like you know when you deploy like a jar using i don't know spring or something like that right so what happens is uh spring scans the jar looks for classes with annotations uses reflection to determine uh what parameters each of these classes has and so on so this pre that's pretty complicated here it's much easier and it and it works even with large object graphs okay so now um a couple of improvements uh so first of all there's one thing that's not so good here uh let's say we change the order of uh creating a new instance of the potato format and the digger so what happens if we run the code now is we get an r here so can you see that actually at the bottom can you see it at the end right okay so we get another here now we get another here because we have created the potato farm before digger was even declared right so at that point that reference is null okay so now we can solve it using scala quite easily we just define declare these as lazy ones okay and when these are laservals uh scalar at run time figures out the right initialization order so we can now define our dependencies in any order and we can use them in an order and things will work fine okay so that's just like a like a thing to remember when you when you're using when you are doing manual dependency injection declare your your dependencies as lazy values okay so the second thing we can improve we can simplify the new object creation a bit using mcquire so let's uh let's add the macquarie extends the mcquire trade with sorry uh so now what we are going to do is we are going to replace the new invocations with the with the wire macro okay so i tell you in a second how it works let me just do the replacing probably some smart recaps regex would do it for me but i'm not going to write it now um okay so uh so now we have uh replaced all of our new invocations with with the wire macro so now we only tell uh we now only specify that we want to create a wired instance of some class we can see if it still works hopefully it will yeah so we get the same object created as before right okay so what happens here wire is a macro if you go to the definition there's no sources for some reason but yeah it's a macro it has i don't know you have to believe me it's a macro and so what it does is it runs at compile time and it generates the new instance creation code at compile time so actually if you would decompile this class as it is now and what where and what we have seen before with the news it would be exactly the same right so the compiled version is the same so uh like this will be at compile time this will be replaced by new potato farm field digger okay so what wire does is wire looks at compile time at the class it has full access to the abstract syntax 3 because it's a macro so it looks at the class that's passed over here it inspects the constructor it looks what dependencies it has and then it tries to find each dependency right so here the dependency the first is field so it tries to look up an instance of field in the enclosing scope and so then the enclosing scope is then closing object and any inherited members and and the same happens for all the other dependencies if there's more than one for a given type there's an error if there's none for a given type there's an error and so it's like it makes it a bit easier uh to do manual dependency injection because you don't have to actually write down the new invocations right you only say which class you want to wire so if there is like five or four dependencies it can get a bit tiring to actually write them down always another thing is when you add a dependency you only have to add it in one place so for example if we would have potato eating cows so now the cow pasture depends on the potato farm yes it doesn't happen often that causes potatoes but ours are special so now we have only added the dependency here right and when we run it the wire will automatically generate the correct constructor call without any need to update the wiring code okay so we can see over here that our cow pasture now depends on the potato form so we can still do manual dependence injection we can do dependency injection with wire we can use you know we can mix them as we prefer actually when talking about mixing um another good side of manual dependency injection is that it's all scalar code right there's nothing special so if you have some special object which needs to be created in some special way you you don't need to create like a provider or a factor or something like that you can just write it you know like new complicated like some complicated digger creator dot create right so you can you can do like you can write any scalar code to create your instance in in there right it's just color okay so yes okay so the question is what will happen if i will have two instances of field that will be a compile time error because when trying to wire potato farm and the macro will not know which one to choose no so the question is will it work without lazy lazy doesn't have an impact you know lazy it's just a it's more of a runtime thing to actually initialize in the right order but yes and so the question is what if the constructor needs two fields so then right so one you can just do it manually right in the new invocation uh two you can well they probably differ in some way uh if it's exactly the same type then you have to do it manually but maybe you know you can you know you can wire field with grass or something like that right so we can add some like trade tags to actually differentiate them stuff like that what if i have complete classes today right so if it's like a field subclass here so uh when we wire we have to wire the concrete class right but then of course when we are looking up dependencies for example for the potato farm right so it will look for uh it's like a subtype check so it it it doesn't so when when here we are looking for uh a dependence of type field right it will find the field because it's a subclass of of the field yes well here you need to specify like the concrete implementation but the dependencies can be on trades of course yes exactly so that's what i want to talk about now so yes so so far all of these dependencies are in fact singleton right there's only one instance created uh like in in the scope of this application is a singleton scope right so sometimes we we may want to have more scopes uh so we can do it using scala as well so let's say exactly that we want to create a new potato farm uh each time it's used so that's it's a very simple thing actually to see it first i will just add a print line here so it's like when the potato farm is created new potato farm and we're very happy about it right so when we run it now yeah we we get one message right so now we can change it to a dependent scope if you know cdi or spring dependent means that each user of the dependency gets a new copy so instead of a lazy var we simply do a def and now we get to okay so simply using scala helps as well okay so now we are evolving our application and you know so we have more classes like we have 50 classes and now our wiring code which we have written like at the end of our application has 50 lines or more it's a big ugly method and we don't like it of course right so we want to somehow divide things split things and actually when did i start because i don't know how much time they have left i have time okay um so yeah so we want to split our uh wiring code into into modules so um so yeah so let's introduce modules so uh now we want to split our wiring code into pieces so we will do it through modules so we will have three modules right we can uh we can imagine then that's the first module like the crop module or the crop package right that's the aggregate the livestock package and we have the restaurant package right so we have now our we have three packages and each package will come with a module which will define the wiring of objects in that package okay so let's first define the modules crop module and the the livestock module and the restaurant module okay so now we have three modules expressed as trades okay so we can actually put these modules next to the classes in the packages and now we have to create the application crop module with restaurant module okay and yeah so that's like um these are some concepts that are the same as in the as in the cake pattern that's why i call it the thin cake pattern because it's only part of it part of it so yeah we like create our application by composing all the modules that we want and then we invoke right we get the restaurant so unfortunately that won't work uh right we got compile-time errors from acquire and that's now even smaller and but what it says is that it cannot find the value of type potato farm uh at that line right so over here we are trying to wire a cow pasture however in the scope of that trade there's no potato form right because the potato farm lives in in the crop module and the livestock model has no idea about the crop module okay so we have to somehow express dependency between these two modules so there are two ways to do it the first way is to simply provide an abstract member right so we can simply write here potato form that this module needs a potato form right we have no idea what kind of potato format can be any implementation we just add an abstract member to the module right and now when we combine these two modules these two are going to be combined as well right this is that's the implementation and that's like the declaration of the of the dependency between the modules okay so now the second tie the the second way we can express dependencies is now the restaurant module of course needs to know uh about the meter and the potato farm as well right and so we can here we can simply extend the trade with the crop module uh with livestock model right now that's a bit ugly because it creates very stiff connections right we depend on specific on specific implementations here but it works as well as we can see we get our a graph wired right so personally i usually use this first style right so i like have a package in the package i have a number of classes where with it with with some business logic and uh you know for any pieces that come from other modules i just add abstract dependencies over here okay so actually i think i have didn't i started at seven no i did okay so i have to finish that's good to know okay so um i also have a play example so maybe i'm just going to mention it that you can do the same you can integrate my choir with play so the main point here is that usually in play when you create controllers they have to be objects so they are global and i don't think that's too nice so now there is a way to turn them into classes and so for example here we have a controller which depends on the restaurant right it's the same restaurant as on the other example so now we can have like fully accessible controllers where we can provide mock implementations and so the only thing you need to do is in global so you need to create a global object which you probably have anyway have a player application and there you have to override one method get controller instance and mcguire provides a utility macro with which you can generate like a lookup map for for for your dependencies okay so it's quite easy to integrate that with play so we can it's you you don't even know you of course don't have to use the wire macro and so on you can just do manual dependence injection and uh still have a non-global controllers like proper classes and test them and so on okay so um roots yeah okay right yeah so it was like uh so if you go to macquarie website and that's like step by step what you need to do but you're right you need to add an ad before the controller is in your roost values right um okay so just some closing slides now i have numbered them in a very smart way and so which one goes first okay so uh three things three things i want to mention one one is the cake pattern which you can also use for dependence injection i think actually in two weeks there's going to be a talk right so i'm going to be back home then so i won't be able to be here maybe that's better but anyway a cake pattern it has its good size and its bad sides and the good sides is that is pure scala as well it has some very natural way to use abstract types um and it also provides a nice way of declaring dependencies a bit similar to what we did in modules so it like takes the module idea a bit further however what i don't like about cake pattern is that it creates quite a lot of boilerplate like you have to surround each of your class with like a container trait which you don't have to do here and i think in in the think cake pattern approach you keep the classes quite quite simple to understand and you don't need the ceremony around um okay so yeah yeah do you think there's things that the cake can do that you know the black wire can't do so the question is is there anything that the cake pattern can do that my my choir can't so one such thing is um that abstract type members uh because in the cake pattern in like in the component you can have an abstract type so here you could do it yeah you could simulate it with the type parameters right but like there are some differences like at least until dot is done there will be some differences so there are some differences in how you can use that okay so just to sum up my choir and my choir is uh so like the main point is that i would like to convince you that dependence injection really doesn't you don't have to use any kind of frameworks containers you can just start by writing everything by hand uh and it usually works very well there's a little bit of extra typing but the gain in simplicity is is much more valuable you can also use mcguire to generate the new instance code for you it is like it's helping you have also in my choir there's some helper objects to implement scopes like a session scope and a request scope it's also quite simple things like the to implement a request scope you just need like 20 lines of code or something like that you can also do interceptors in a similar amount of code and and yeah you can integrate with play for example um okay yeah so i just talked i just told you about the scopes okay so the project is open source on github it's a apache to license the presentation is over here feel free to use it in any way you like like the there's a bit more covered in the in the github code than we covered here but there's like five steps with which developed the application so it should be quite easy to follow and i have the stickers which i wrote about so i have two stickers one of them is an i'm proud of my code sticker right so who isn't so uh the i'm proud of my code is a mod of our code review tool which we use internally and we try to see if other people are interested as well so feel free to take these stickers and the second is we have a weekly scholar newsletter where we each week there's a bunch of interesting hopefully interesting scholarly links and last week there was a comic or two weeks ago a scala comic so what can be better yeah and i have stickers for that as well right these are quite nice like these so as well if you would like some please take them and yeah and i have some voters for codeblock as well uh they're like half year licenses so if you would like to take them please do and maybe they answer more questions yeah so who do you know any companies or teams so i know one there's software and but i know there's a at least one large company that is using it but they don't want to publicly admit it because it's their policy apart from that i know there are users because sometimes i do get back reports which is i guess a good indication but otherwise i i can't name any like thanks great to hear all right well i hope it works for you yeah so why is this whole industry of defense injection and i've never used spring right it's gigantic why would people come up with something like that so i mean i mean some people are fond of basically going all these dances to rebel they're calling some stuff i mean what is the and they're individually smart programmers what is it about corporations which make them do this that's a very good question but i think it's mostly psychological question and not a computer science question i mean can you think of the reason why you know the company would not do this i mean where does manual manual independency so it is a bit harder into doing java because you can't do the modules that easily though i did manual dependency injection like small projects in java and it and it worked though like the methods to create the object graph they were huge in the end so that one that was not so nice and apart from that i don't know i've i've i've been like a big cdi fan at the point and seen before that but i was young so hopefully that's it of all and second the ability to move the mass out of the call to xml files makes the digital code that general code is good right so i guess at the point everything got moved xml and now everything is in annotations right so annotations are like a programming language on their own in a way yeah earlier you showed one trait depending on another trait you use extends instead yeah you could use self types sure the next question i had is how precise is the type search does it have to be exactly that type or could be a more derived type so the question is how precise has to be the type no so so the check is a subtype check simple so it can be any subtype yeah yeah uh do you have any support for like say higher cardinalities or anything like that like say if you're looking for a list of i'm comparing it to something like spring we can define say multiple instances of the given service so the question is is there any support for injecting a list of dependencies of a given type no there isn't however there is a bug or a feature request however we call it it would be quite easy to implement but it's not yet done but there are no like technical obstacles okay i guess that's it thank you very much