SBTB 2023: Paul Chiusano, Typed and compositional microservices in Unison
Recording: SBTB 2023: Paul Chiusano, Typed and compositional microservices in Unison
so I'm Paul I am one of the co-founders of Unison Computing we are a small public benefit Corp we're on a mission to uh make software a lot simpler to create and more accessible to more people and also Advance the state-ofthe-art in in what's possible we have two big efforts that we've been working on one is the Unison programming language which some of you all may have heard of uh that is open source and the other is the Unison Cloud platform this is our first product it's going to be shipping soon I'm going to be talking about both these things today but I'm also going to try to make the talk relevant to you know people working in other uh language ecosystems so what am I going to be showing a new approach to building microservice backends which is a lot simpler than what folks are doing these days and what are folks doing these days they are building things like this and what what even is this okay in the center here is a this is a microservices graph um these are sometimes called uh Death Star diagrams which I think is hilarious uh each point on this circle represents a different service lines between points represents uh calls between services and you know even if you don't have a good billion microservices like this even if you just have you know a handful or a dozen uh building and operating a Contraption like this is pretty difficult uh so each of your services has to be packaged up deployed um all of those uh service call boundaries you generally have some layer of parsing and serialization converting to Blobs of Json or protuff or whatever else and you know to try to kind of make this easier there is this delug of uh different Cloud native Technologies that's all this stuff in the background here that you're supposed to kind of use uh in some combination to assemble a system like this but it's complicated and it's complicated in kind of a strange way too so a lot of the work you end up doing is not programming so uh you know let's compare uh if I have say a list of things in memory on one computer and I want to sort it how do I do that I call a function in my programming language and that experience is pretty great uh you know I call that function with the same uniform syntax as I call any other function uh I often have a type Checker make sure I've called the function correctly you know I can introduce new functions new abstractions and it's basically sunshine and rainbows uh okay but then in this world of you know building uh microservice backends you you have these other kinds of things that you have to do you know like for instance uh how how do I connect an autoscaling group to my managed kubernetes cluster and how do you do that well you know you don't just call a function it's uh it's it's some basically uh let's see here you uh step one download the cluster autoscaler yaml file uh using this curl command okay we we're kind off to a bad start here uh open up the yaml file find and replace angle bracket your cluster name with your cluster name also consider an and it's just like what even is this is just strange uh so why is there this distinction between sort of the normal uh experience of programming where things are logical and and and make sense and fit together and are cohesive and consistent and this just kind of garbage fire of of random gobbley that you have to to uh put together to to build things in the cloud so that's a question what why why is why does that even exist I'm going to come back to that but uh let's let's put aside the negativity for for a minute here and try to think positive and what is the dream so I think the dream has three parts so part one deployment should be dead simple you should be able to deploy services with a single function call in your programming language uh I you know I don't want to have to package things up and and build and ship around these multi-gigabyte containers um I just want to focus on the logic of my service and then I want to call a function and say hey make it so in the cloud and I just want it to work okay so that's part one of the dream part two is service calls should be super easy as well I would like to able to make service calls with a single line of code I don't want to have to convert things to and from Blobs of Json or protuff or whatever else I also want my service calls to be typed so I want the type Checker to tell me oh you passed the wrong kind of argument to this service I basically want it to be as easy as making a local function call uh now that said I do want the type Checker to to keep track of where these remote calls are happening because that's kind of an important thing to be aware of but I don't want a bunch of busy work on either side of that service call boundary that is just an annoying waste of time okay and then the the third part of the dream is uh I would love the same experience for accessing you know the database the storage layer uh I would like that to basically be as easy as accessing in memory data structures okay so all of these things are possible I'm going to show you a demo actually a few demos but before getting to uh the demos uh I want to just get into H how do we actually do it so let's take each of these in turn we're going to start with the mo most difficult one uh how do we deploy services with a function call in our programming language so zooming in on this a little bit so here is our deployment function we're calling the deploy function we're passing it my service now thinking thinking this through a bit so my service it could reference anything in in the programming language right it could close over some static data it could reference other functions and so table Stakes for being able to do deployment at runtime like this is all things in the language need to be serializable otherwise we don't even have a way of transferring the bits of of my service to wherever it needs to be deployed to okay so that's the first requirement but that's not quite enough because uh my service it can have dependencies right it could call other functions and so the the second requirement is we we need to be able to at runtime enumerate the dependencies of my service uh because it's not good enough to just deploy my service we also need to make sure all its dependencies get deployed as well okay so that's the second requirement but that's not quite enough either because if you sort of think about this well it seems possible that wherever we're deploying my service 2 could have a conflicting set of dependencies and so we need some story for just completely eliminating dependency conflicts as a possibility uh but anyway seems like kind of a tall order but if we can do all three of these things then we are good to go and we can deploy services with a function call okay so how do we do it so zooming in a bit further here uh suppose my service you know it it it does some stuff and then it calls factorial of 9 what is factorial it's just a function I defined here factorial of n is the product of the numbers 1 through n so here's the key question how how do we actually encode the reference to factorial here so we need to somehow send the code for my service to wherever we're deploying to and how do we encode the reference to functions like factorial and so one one thought you know that comes to mind is like oh well let's just try sending the name factorial and just kind of hope for the best and this doesn't work because this is basically assuming that the name factorial is going to always mean the same thing everywhere no destination that you're deploying to is going to have a conflicting idea of what factorial should mean and that just kind of doesn't work so what does work so here is the big idea uh this this is the Unison programming language is based around this entire idea and it's kind of the the key thing that that gives us the the superpowers I'm going to be showing so this is super important slide so the idea is factorial is going to have a hash um a82 J uh now it's also going to have a name you know for showing the code to a human but uh it's going to have a hash and that hash is in turn going to be based on the hash of the product function the range function and all of the and and the actual structure of the factorial function so that hash is going to uniquely identify the exact version of factorial we're talking about and it's also going to pin down the exact version of all its dependencies uh by the way in practice these hashes are going to be long cryptographic hashes we don't have to worry about collisions but I'm you know trying to fit it on a slide so uh anyway with this uh with this idea we now have a nice way of doing uh deployment so what we can do when we're when we're serializing my service is we're actually going to send instead of the name factorial we're going to send that hash a 82j and wherever we're deploying to it can it can have a cash that's keyed by those those hashes it can say hey I already know about all those hashes I'm good to go or it can say oh I'm missing some of those hashes can you just sync them on the Fly and so we just have this very simple little negotiation that we can do as part of deployment to just make sure the code and all its dependencies get synced and that's that's the gist of it um so using using this idea we can now do deployment with a single function call so um this is some Unison syntax I'm not going to talk too much about Unison language syntax here but just showing that we are calling the deploy HTTP function and one of the you know we're passing it my the logic of our service as just a regular function argument and that is going to just be deployed and made available in the cloud just just like that with a function call okay so sounds pretty magical right yes okay let's uh let's see if it actually works does it work uh okay so let's see how legible okay uh great so uh on the left here I have uh just VSS code running uh on the right I have Unison uh watching for changes to this this file uh Unison also acts as an LSP server gives us things like autocomplete uh you know real- time error highlighting and so forth uh the other thing it does is when I save the file it's going to parse and type check it uh also prints out the result of these watch Expressions these lines uh starting with greater than okay here is our Hello World Service uh it made it slightly more interesting so I'm actually capturing a URL parameter here and I'm doing some logging so it's you know enterprise Enterprise ready and uh here is the deployment function here uh you know I'm calling deploy I'll say more about that in a second but let's actually go ahead and try running it and what this is actually going to be doing it's going to be packaging up that code oh my gosh not connected to the internet great oh my gosh yeah capital L lak side okay SRC it's funny I was literally just outside connected to the Wi-Fi but um yeah you have to choose the [Music] password okay so um when you try to deploy to the cloud and you're not connected the internet that does not work just uh Pro tip okay let's uh let's try that again uh once more with feeling um Okay cool so that that actually worked this time so um it's uh you know serialized the service and and deployed it uh to the to the cloud and also you know uploaded all its dependencies and exposed that as an HTTP endpoint which uh let's just go ahead and hit this and we can say I don't know hello scale by the bay we can say you know hello Paul okay so it's you know it's working uh you might be wondering where did those all important log messages go and here is where they went so if you go to uh app. un. Cloud you can see uh you know the the calls to to the service you can also stream your log messages locally to your local console if you prefer okay so one thing I was going to show before we keep going so when you deploy a service you actually get back a a value you get back a service hash so that's what that uh that portion of the URL is that that long sort of good looking thing um anytime you change the service that hash is going to change um so it's also sometimes handy to have uh Services have names that have a stable identifier and so this line here you can see we're we're assigning the service name hello to that hash so service names can basically point to service hashes and this is Handy for for things like testing you can deploy a service test uh test it by hitting the hash and then assuming it test pass you can promote to production by uh assigning a service name to point to that hash so we could actually hit the the same service by name as well as by hash um so here I'm hitting it by the name service name hello okay great so uh success uh part part one of the dream accomplished Let's uh move on to the next part of the dream which is making service calls super easy so just kind of zooming in on this a little bit uh imagine this is some sort of hypothetical Syntax for calling a service what do we need to do to make this happen well we're certainly going to need to be able to serialize the arguments uh and we're also going to need to serialize the results that are coming back from the service moreover we need to be able to do that without dependency conflicts because the caller and the call E uh may be sort of on different locations have a different environment and so you know we need kind of a way of doing that but it we basically already figure that out for the case of doing uh service deployment and we're just going to employ the exact same trick so here is how that shakes out in unison um okay so there is a function now deploy uh it takes a function from A to B it returns a service hash AB so service hashes are actually typed so this is very important when you call a service hash AB you give it an A and you get back a b you can also call a service name uh as well if you want to uh yeah refer to a service by its service name instead of by its service hash uh other thing I was just going to point out so your services can have effects so Unison has an effect system it it keeps track of where you're doing these service calls doesn't make you use a completely different syntax uh just sort of keeps track of them and the types and that's sort of useful information uh to be aware of when you're programming so um our album service let's suppose this is a service that you give it a song it gives you a list of albums that that uh song appears on and that's going to have you know a type that reflects that and uh here's what like a service call would look like and uh by the way so my daughter is a huge fan of Taylor Swift and maybe one day she will watch this talk and think it's funny that I use this as an example but uh Cruel Summer is an amazing track I'm just saying uh okay but moving on let's see demo of this actually working uh okay so we're going to be deploying multiple microservices that are that are all talking to each other now um so I'll sort of just Breeze through this code uh here's the Declaration of the song type the album type a little uh encoder to Json of the album because we're going to be returning list of albums as Json here is our the logic of our service it's kind of silly we only know about this one song Cruel Summer and all the other uh songs we're just going to kind of give up but uh yeah the deploy you know this just like on the slides uh deploy it does return a typed service hash and then here is the main uh HTTP Edge service which just calls that album service and it it it really is just that simple it's one line of code we don't do any manual sort of encoding or decoding of the arguments or the results we just call the service and you know it it just it works so or hopefully it will work so let's try actually deploying this um so we're going to run uh music app. deploy so this is now going to be it's going to be deploying these two different microservices one for the uh album service and one for the HTTP um sort of edge service so let's go ahead and uh hit the HTTP service I don't know how legible that is but anyway we look up Cruel Summer and we can see that uh it does indeed appear on the album Lover by Taylor Swift amazing so it's happening we have multiple Services being deployed just like that and they're communicating and it's super easy uh okay we're on a roll we're we're two for two here uh let's let's uh let's look at the the third part of the dream oh one one one thing I was going to point out so the nice thing about this approach is if you ever want to change how your service graph is factored you know how the functionality is is broken up into different Services that's that's actually a nice experience because you actually have the type Checker to guide the process so it's a lot like kind of refactoring just sort of local inmemory code where you also have the type cheer to help you along you don't have a pile of boilerplate and you know encoding and decoding converting to Json that you have to shuffle around while you're doing this you just kind of focus on the logic of what you're doing and it's it's pretty great in fact while I was implementing this uh extremely Advanced uh application that I just showed you I actually had a a type error where I was like passing the wrong kind of uh thing to to the service and I got just a regular type error and I fixed it in five 5 seconds and it was it was good so okay let's move on to the last uh part of the dream which is a nicer approach to uh talking to durable storage now that said Unison can still talk to external databases and so forth uh that's same as any other programming language but uh we want it to have a nice UNIS native way of of accessing storage so I think you can maybe guess where this is going uh about how we're going to solve this problem and it's it's literally we're going to use the same trick that we Ed before so serializing a service when you're doing a deployment we're it's the same kind of thing if you are serializing you know values of your language to the storage layer and so we're going to apply the exact same tricks that we've done before here's an example of how that shakes out in terms of the API uh so you can declare these typed tables the tables can have any key type any value type here we have one mapping songs to list of albums here's just a little bit of example code populating you know a row of that table and then uh within your services you can literally just do lookups in these tables as easily IL as if you know the T it was just sort of in memory data structures and this all works but so I'm going to show you a quick demo of this and also point out a few interesting things about this um okay so scrolling down a bit so this is music app 2 U I've declared a a table mapping songs to list of albums uh actually have a little batch job here to populate that table with some rows uh I'm actually super Overkill but I'm massively parallel inserting all four of those rows at once uh with this with this power map call also just showing off that um Services uh the storage layer supports transactions so you can update uh row you know multiple rows of multiple tables uh in a single transaction and yeah and then here's just the rest of the logic which looks pretty much the same as before so I'm going to go ahead and deploy this and yeah this is going to you know populate that table it's going to deploy those services and expose you know uh HTTP endpoint so if I go ahead and look up uh this uh song Shadow Journal we can see that that does indeed appear on the album the blue notebooks by Max RoR just one of my uh an album I quite like and yeah it's it's all working and um yeah we we've done it mission accomplished right okay so I think that was it for the demos so let's jump back to the slides um okay so in conclusion hash identifying code it it really does unlock all the things it's sort of like a superpower um it's you know we've been building Unison over a number of years and it's uh it feels almost like we're building on alien technology this idea it just makes so many other things a lot simpler and uh it's it's been a blast uh to to work on this um I want to come back to this question that I started with at the beginning which is why Building Systems the current way is so complicated so here's the reason it's that our programming languages are just way too narrow in what they can talk about well so what we think of today as a program is generally a description of what a single OS process on one machine is doing and that is a tiny little box and you know the experience when you're within that box is pretty nice but at the at the boundaries of that box you have all this CER of boilerplate codee to convert you know to and from different formats and then there's this larger question of how do you organize you know you have a system like this which is you know hundreds or thousands or hundreds of thousands of these different OS processes spread across lots of computers and they all need to be communicating and you know coordinating to do a thing and it's like our programming languages are that's there's a lot of information here that needs to be specified to for to build a system like this and our programming languages can't do it because they're trapped in this box and so that is that's what results in this huge mess where we have all this information that needs to be specified right and our programming languages can't do it so what have we come up with instead we've come up with you know the the the cluster autoscaler yaml file and just kind of this mess of just random ad hoc stuff that's been kind of thrown into the to the mix with no thought of compositionality or it being consistent or cohesive and it's just a huge mess and this is not the future it so the future is programming languages that can talk about entire systems like this and that's what we have been building with unison and uh yeah it's just been amazing seeing that it's actually working out and we can actually build systems in this way and it's just so much nicer than kind of the status quo okay so uh if you think that this stuff sounds cool uh okay a couple links here so one is uh uh just this first link we are so Unison cloud is currently in limited beta we're looking for folks to you know help us test it out and you know tell us what they think report bugs things like that before we make it widely available uh so if you'd like to join thatr program uh you can check out this first link also we're going to have a just a casual get together tomorrow night uh there's some details about that uh also at that first link uh so we love to chat chat with folks more um you know about future of computing and and making Computing simpler and that sort of thing and yeah Unison l.org is the homepage for the open source Unison programming language and you can learn more about the language itself there and lastly just really want to thank the contributors to Unison over the years uh this has been the work of many people and uh yeah thanks for thanks for being a part of this and that's it thank [Applause] you we have time for a couple of questions so I think the last time we talked you hadn't actually gotten the uh data stuff in yet and so I'm really excited to see that but where do you see yourself taking Unison next what are the what's on the road map over the next 6 months or a year uh that's a good question uh we have a lot of thoughts on that I think sort of the probably the most immediate thing is we want to just get this in people's hands uh playing with it and kind of see what what they say about what they would like to see um but yeah I guess we we do have a lot there's a lot of directions we can take it and uh so it's kind of just a question of like what are people most interested in so sorry kind of a non-answer but but definitely come talk to us after um so uh what does it look like to to to have your Unison code interact with code that's written in some other language yeah so if you are talking to code that's written in another language um I mean so it's really not that different than sort of other programming languages in the sense that you know like you could you could write a Unison service that uh is an HTTP service and can be talked to from any other programming language and you know same goes in the other direction where if you have some other service that's exposed over HTTP or or whatever else then you can certainly talk to that from Unison yeah I mean obviously you don't get as many of the benefits because it's you know you have that layer of annoying kind of stuff when you're talking you know across languages but uh yeah I mean you can do it and it basically works the same as as other as other languages uh one more question so in in the last demo for the uh the database table um there was no notion of database engine or so is is it that what's what's actually happening behind the scenes I guess how does unison interpret this command like how how does the basically how does the storage layer work right is is it a a Unison specific storage layer that you're employing here or yeah so that that stuff the the storage stuff that I showed was that was Unison Cloud's storage layer so that's like a sort of fully managed uh you know super scalable cloud storage that that we manage in our platform and and can Unis be made compatible I guess with uh uh other database enges yeah so if you if you want to talk to you know other data stores that you would do that pretty much the same way as you would in in other languages there's nothing nothing really Unison specific about that so okay so yeah but definitely come come chat with us afterwards uh so my my myself my colleague uh Rebecca and Fabio in the back there we are going to be around answering questions and love to talk more thank [Applause] you