Monolithic, Micro-services, and Megazord

GoFrendi Gunawan
10 min readJun 12, 2019

--

I believe, it is not your first time hearing about monolithic and micro-services. But if these terms are new for you, let me give a brief explanation in the next two paragraph.

Monolithic and Micro-services

Monolithic is a single monstrous application containing all your business logic, authorization, etc. Monolithic is easy to develop, easy to deploy, and relatively easy to debug. However, monolithic application is not very good for horizontal scaling. Odoo (https://www.odoo.com/) is a good example of monolithic application. You can create modules and custom logic in odoo, but your modules and odoo’s core base are binded into a single application. Thus, wrong memory allocation in your module might bring your entire system into abyss. Don’t get me wrong. I love monolithic. Monolithic is not bad at all. Especially if you only have a limited infrastructure and small user base.

On the other hand, we have micro-services. Micro-services is a software architecture containing a lot of independent applications that talk to each others using REST/RPC/pub-sub mechanism. Facebook is a good example of micro-services architecture. Several month ago, Facebook users around the world had to deal with something awkward. They couldn’t post any photos and comments, but they still able to see old photos/comments. The incident was quickly fixed, and no one remember about it. The incident was one of micro-services best showcases. When a single service down, the other ones are still running. Apparently, Facebook has different services for posting and fetching comments. The services might share the same database, but they are independent to each other. Another interesting feature of micro-services is you can scale up certain part of your application. A news portal for example, has a few authors and a lot of viewers. Micro-services architecture allows you to only scale up your news-fetching service.

Monolithic vs Microservice — courtesy of https://www.weave.works

To conclude, monolithic application is easy to develop, easy to maintain, and easy to debug. However, it is not good at horizontal scaling. On the other hand, micro-services are independent to each other, easy to scale up, but need a lot of effort to maintain and to develop. When you have to use micro-services, you need to invest on your infrastructure. Tooling and automation might help a lot, because it is silly to deploy your services into six different virtual machines by hand.

If you are building your own startup with a very limited resource, I think monolithic make more sense. In my opinion, you should always start with monolithic on your first day. If you want to build your MVP (Minimum Viable Product) fast, you have to make sure nothing will hold you up from breaking things and making quick fix.

However, once your business start to grow and millions of users depend on your platform, you might need to consider micro-services. After all, you don’t want a single wrong user input brings your entire system down.

Rewriting your monolithic monster into micro-services is not an easy task. Some code might be tightly coupled to each other in a sense that you can’t take a banana without fetching a gorilla and the entire forest.

FaaS

Well, everything I wrote in the previous paragraph might no longer relevant. Nowadays we have something named FaaS. You can build your functions and deploy it into production with a few keystroke as long as you have active internet connection. FaaS is indeed a promising solution.

However, FaaS has it’s own drawback: vendor lock-in. Imagine you have already deploy your functions in AWS Lambda. Unfortunately, for some political or economic reason you have to move to Google Cloud Function. The migration process might not be easy.

Megazord

Let’s put aside FaaS for now and go back to our monolithic vs micro-services thing.

I like to think monolithic application as a giant robot protecting the earth from monsters and aliens. This giant robot is very powerful, but once you (accidentally) break it, humanity will be pretty defenseless.

Aside from the giant robot, we can also have small robots fighting together as a team or acting independently. In some occasion, when something stronger is needed, the robots should join forces into a single powerful megazord.

Megazord — coutesy of https://ideas.lego.com/projects/6d9e4993-3506-4bcc-98d8-b4d1dbf53896

You might be familiar with this concept. Now, how about making it real? We will cover it later.

The Dark Side of Micro-Services.

Microservices, so little, so green, so mean, so weak, so many, so much work — Courtesy of warhammerfantasy.wikia.com

Some months ago, a senior engineer in my office proposing mono-repo. If you are new to this term, mono-repo has nothing to do with monolithic aside from the same mono prefix.

We want mono-repo because maintaining a lot of independent repositories and ensuring consistencies among them is kinda difficult. Apparently, having everything you need in a single repository might at least make you aware that you are working in a single service that probably affecting other services. In the world of micro-services, a simple action like swapping the signature of function getDiff(a, b) into function getDiff(b, a), might bring inconsistencies among your system.

The best way to deal with this kind of incident is by testing everything as often as possible. How to do that? Sure, we have development server. But how do you make sure that other developers don’t mess up with the system while you are doing your job?

Developer A: Sorry guys, I deploy a wrong version of gagagu, that’s why guguga give you internal server error.

Developer B: What? Sh*t, I thought the problem was related to bwakakak parser. So I rewrote function kakikukeko which is also used by karako, I also talk to Developer C, so that he provide another BSON response that bla bla bla…

Developer A: Ups, sorry… I thought it only affect guguga

Honestly, without any good tooling and automation, dealing with micro-services is like living in the hell (that’s why we need to take vacation once in a while). Now let’s see some close-to-real stories so that you hate micro-services as much as I do:

  • Service A has a parser to change our specially built DSL into AST and save it into the cache. After a long meeting, it is decided that the parser should also be implemented in Service B. The easiest solution is to let service B send request to service A whenever it need to. But the drawback is we will ends up with extra unnecessary caches, and service B will be dependent to service A. Another solution is to copy service A’s parser and put it into service B. But how do you know that the parser will never be changed? The best solution is probably by providing a third party library and let both service share the library. But again, how do you know that the library version in service A and service B will always be consistent? Ah, f*ck it, probably we should re-design our micro-services.
  • Boss: Hi, we have a demo day. Can you please set another environment so that we can show all the cool stuff without affecting our production?
    You: Oh, demo day, what day?
    Boss: Let me check,… oh no it’s today.
    You: WTF, now which services should be deployed in that environment, what is the last stable version of gagagu? Is it compatible with guguga 3.14?
  • So we, the seniors, have just implement a very cool logger, and all of you, mere mortal juniors have to rewrite your functions in order to enable our perfect logging solution…

Ayanami

It was a good evening when I’m talking to other engineers in my community. I don’t exactly remember what happened before one of our friend said something interesting:

A good code is a code that is not written by anyone

For me, it sounds like generator. And that’s how I plan to take down the green monsters in micro-services architecture.

In the last few months I keep thinking about it. And since we have a long week holidays, I finally able to finish the prototype of the generator. Let me introduce Ayanami: https://state-alchemists.github.io/ayanami/

Ayanami Rei, from Neon Genesis Evangelion

The idea is quite simple. First, you have to define your service compositions, then you need to write your source codes, and finally, you generator will do the job, building your micro-services by copying your existing source codes into any service that depend to it. Also, to make testing process easier, the generator should also build a single monolithic application based on your source code and compositions.

Ayanami Workflow

Using Ayanami, you can write your source code once, and getting as many deployable as you need. You don’t have to choose between micro-services and monolithic, because you get both. You don’t need to worry about consistency because it is generator’s job, not yours.

Let’s Rock

Initiating a Project

First of all, you need to init your project. You can do so by invoking ayanami init.

Ayanami init — creating a project

Your project contains three directories, generator, sourcecode, and deployable. You will spend most of your time defining service-compositions and writing source code. Occasionally you will deploy your services into a monolithic application and make sure everything works as intended.

Your Project Structure

Defining Compositions

Now, let’s deal with a simple use case. You want to serve three endpoints:

  • / should always give HTTP response 200 Hello there
  • /hello/:name/ should great you with HTTP response 200 Hello :name, assuming :name can be any valid string. So, /hello/shinji should give you HTTP response200 Hello shinji
  • /banner/?text=sometext should run figlet to make an ascii banner, wrap it with pre tag, and send it as HTTP response 200. If any of the process failed, HTTP response 500 internal server error should be given instead.

Your entire architecture might looks like this:

Your Architecture

Let’s start with the easiest one, the gateway. Using Ayanami, you can define your gateway and services like this:

Gateway will simply publish event to message broker whenever it receives HTTP request from the client.

ServiceCmd has a method named figlet which actually does a very simple job: executing figlet

ServiceHtml has a method name pre. This method take a single input parameter, wrap it into a <pre> tag, and return it. The function is not yet exist, you will have to scaffold and implement it later.

Once the gateway and services has already be defined, you can start defining some flows.

flowroot

Flowroot is the simplest flow in our case. Whenever it receive request from gateway, it will set code into 200 and content into Hello there. Then it will send response to the gateway. You can see flowroot definition here: https://github.com/goFrendiAsgard/myproject/blob/master/generator/flowroot.go

flowhello

Aside from flowroot, we also have flowhello. This one is a bit more complicated since it needs to do a function call. The function will be generated once you scaffold the project. Here is flowhello’s definition: https://github.com/goFrendiAsgard/myproject/blob/master/generator/flowhello.go

Finally, we have flowbanner. It is slightly more complex than flowroot and flowhello since it needs to deal with servicehtml and servicecmd.

flowbanner

You can see the definition of flowbanner here: https://github.com/goFrendiAsgard/myproject/blob/master/generator/flowbanner.go

Scaffolding

Ok, so you have done with defining composition. Now, let’s scaffold the project. You can invoke make scaffold to scaffold your project.

Scaffolding process

Once the process done, your sourcecode directory will be populated with function skeletons.

Let’s modify our skeletons to meet our need:

Even though I’m not showing it here, your services and flows might share the same function. You don’t need third party repositories or put extra effort to maintain consistency.

If you notice, the way you write your functions is almost similar to those FaaS platform. You write functions, you don’t need to care about REST, AJAX, etc. Just write functions (and compositions), and let everything else handled by the generator.

Building

Finally, you can build your application and run it. To build your application, simply invoke make build.

You can see how pre.go and html.go has been copied to their corresponding flow and service. Also, there is an extra directory named megazord. Yes, that’s it. Please welcome our lovely monstrous monolithic !!!

Run Your Micro-services

To run your application as micro-services, you should open 6 terminals, and invokemake run in every service/flow/gateway. Let’s ignore the megazord for now.

To test whether it works or not, you can send the following request using curl or web browser:

You can see how the micro-services talking to each other by seeing their logs.

Run Your Monolithic Megazord

You might love micro-services, but you should already notice, running micro-services in your local computer without any automation script is a very bad idea. So, move into megazord directory, invokemake run, welcome our megazord, and play this video:

Mighty Morphin Power Rangers Opening Theme

As in the case with our micro-services, this monolithic megazord should also play nicely with your HTTP request. Here is how it looks like in my computer:

Megazord in action

Conclusion

it is fun, yet tiresome to build your own generator. But to tame the nature of micro-services, automation and tooling is a must.

Right now, Ayanami is still lacking some things. But I hope it can soon be implemented in real world.

--

--

GoFrendi Gunawan

ML Engineer at Kata.ai. Ex-lecturer. Occasional Fire Bender, Life-long Chunibyou