Distribution never looked so sexy17/Mar 2015
I love web application architecture. It’s my creative outlet in the sea of code and I treat it as a form of communication and expression. In my mind I even visualize it, because my mind never wants the right side useless while I find myself buried in technical code.
I think the most common web app architecture we see today is MVC (model, view, controller) or even refined as ADR (action, domain, responder) as Paul Jones notes. MVC is certainly where I started with design patterns many moons ago with an alpha version of CakePHP and then later on with Li3. However, I’m a lover of many languages and I’ve moved more to taking an RESTful API approach to my applications with Node.js and Go.
I’ve also had the need to architecture much more scalable applications that are more fault tolerant and that maximize the resources on the server. The problem with PHP and even Node.js is that you don’t really have that much control of resource utilization. Node.js is a little better since it’s asynchronous, but you don’t get the same type of parallel processing there as you do with Go.
So I’m going to write about a (flexible) Service Oriented Architecture (SoA) that I’ve been working on using Koding’s Kite package. The really nice thing about my approach is that you can build the application as a single binary so you don’t have any microservices (and therefore not a SoA design) or you can keep it fully or partially broken out into microservices. This gives you the most control over your application and resources. It gives you plenty of options for scaling your application and considers your budget.
First, each service is its own Go package. Each of these builds a binary and runs a server listening on a port (a Kite). Each one registers itself with Kontrol (a registry) and you can then have communication between the services by passing JSON messages over HTTP.
To put a use case to it, let’s say that Service A is actually a public facing API. Of course there can be multiple and they are all behind a load balancer so you can reach each one (round robin or weithed or however you configure that). Let’s say that service handles user input and asset upload. So videos are being uploaded and stored to S3 with this service. It handles all this in a goroutine so multiple uploads can be handled at once, etc.
In fact, right here is where I use a package like tunny (a handy helper) so that I can maintain a worker pool based on how many CPU cores the server has. This means I can run the service on a variety of servers and the program will understand how many workers it should have.
Next you have Service B which is passed a JSON message (from Service A) about the incoming video and where it is on S3 (this message fires off after the upload is done). Then Service B processes the video gathering all sorts of meta data and making a request to Amazon’s transcode service. Again, we maximize on the number of cores each server has here too. At the end of this job, it passes a message off to Service C.
Service C now stores the data into a database. Again, keep in mind that there can be multiple Kites (servers running the service) and each one can be multi-threaded to maximize on the server’s resoruces.
Separation of Concerns
This application was off the top of my head here, so you can elaborate or imagine something different if you like. The important part to understand is that we’ve separated concerns into microservices. This is an important part of any scalable and, more importantly, maintainable software application. The more you can keep things isolated, the less headaches you will have and the more options you will have for scaling. You might also hear the term loose coupling used in these cases.
As your application grows and does far more complex things, the other advantage here is that you can now have multiple teams of developers each responsible for different services. The division of labor is always something on my mind with software architecture because revision control systems aren’t always enough to keep developers from stepping on each other’s toes.
The Package Setup
I’ve also used this pattern with a single Go repository that had a directory for each service. Each service directory was named for the service (and package) contained a “service” directory (and package) from there. Why? If you want your project all under one repo. You can use a more traditional and idomatic approach if you like.
My Go application looked like this:
This isn’t idomatic Go. Typically you’d have each serivce off into its own package under its own repo. Again, you can certainly do this, but why did I do this? I did it primarily because I didn’t want to fill up my private repo slots on GitHub so I could save money. It was also convenient to have my entire app in one directory. Second, I did it because I wanted to be able to build a binary from the project root that imported each service (without making yet another package). I wanted to run tests and do builds all from one place.
main.go file has import paths like this:
You might be wondering why I have a
service directory (and package) under the
service_a package. It’s already separated, why nest yet another level?
The microservice (Kite) is also setup and registered under each
main.go file as well. This leaves that
service sub-package containing functions related purely to the
concern at hand and leaves the Kite out of things unless you were to compile a binary from the root service package directory. This is important.
One drawback here is that it forces you to make many functions public that you might otherwise keep private. It’s going to depend on how you like to work. I always suggest going with the idomatic approach, but I am also an advocate of rule breaking when it makes sense and betters your workflow and eases headaches.
You might also wonder why there’s a
main.go in the root directory. It’s so I can build
main.go from the root and have a stand alone application. In other words, I can
easily change from a distributed architecture to a solitary one. This means
main.go is importing each service package itself and that is why I nested a
under each service. Otherwise, I would have some conflicts with Kites running from each package in a single binary.
Yes, I could also run multiple binaries (Kites) on the same server because different ports are being used. However, I wanted the option of a single binary for special distribution, development, and testing needs. When you’re developing locally, you don’t want to build and then run a bunch of microservices each time you want to test something (outside of your normal test cases). It’s a bit annoying to open up a bunch of terminal windows and run each Kite to be frank. Especially when you have many of them.
So you want to limit the number of times you test like that. In order to do that, you want a single application to run through. Of course this may not be how your application works in production…Or maybe it is for a while. You might want to run a single server and have just one application to manage until you have more demand from users or something.
In this setup, you would have the
main.go file in the root orchestrate things. It might import multiple services and use them together in a certain way. It might even define
HTTP handlers and become your API where services A through C are doing something very specific.
As you can see, you have an extremely flexible setup here. You can easily separate concerns, test, and deploy from a single package. You build both a single binary application that does it all or build multiple binaries as distributed microservices.
At the end of the day…
This really helps your development process because one of the drawbacks of a SoA is coordinating and testing each of the services. This is one of the reasons SoA is less common outside of the enterprise world and why other pattterns became more popular. Plus, let’s be real here: who’s personal blog requires a bunch of services? You lump everything together in an MVC pattern and call it a day. Users and posts are just fine to be put into the same build and server.
I’m not trying to say this is a replacement for MVC or ADR, but maybe it makes sense for your blog to use a microservices architecture as well. It’s going to depend on many factors. At least now you get to decide which services to combine. You’re also separating concerns and thinking about different parts of your application in a completely different way. In a distributed way.