Microservices Gone Wild – Tech Dive Part 2

Tech Dive - Microservices

In this post, I’ll outline the program I’ll be using to demonstrate how microservices work. It’s written in go but it’s pretty straightforward. At the end of the series of posts I will upload all of these examples to github as well, in case anybody wants to poke at them.

The Program – Squariply

For demonstration purposes, I’ll be discussing a very simple program that is currently implemented in a monolithic fashion. I’ve called it squariply for reasons that will momentarily become obvious.

Purpose

Squariply accepts two integers on the command line, calculates the product (i.e. multiplies the two numbers), then squares the resulting number before printing the final result out. Mathematically speaking, if the integers provided on the command line are a and b, the output will be equivalent to (a * b) ^ 2.

Monolithic Code

My extremely amateur go code looks like this:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    str_a := os.Args[1]
    str_b := os.Args[2]

    int_a, _ := strconv.Atoi(str_a)
    int_b, _ := strconv.Atoi(str_b)

    multiplyResult := int_a * int_b
    squareResult := multiplyResult * multiplyResult

    fmt.Printf("Result is %d\n", squareResult)
}

For the purposes of clarity, I have skipped the usual error checking and validations that should/would take place. Simple enough:

$ squariply> go run squariply.go 2 4
Result is 64

Handily, the answer is correct. Since we’re going to overbake this solution, perhaps the mathematical operations should each be in a function?

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    str_a := os.Args[1]
    str_b := os.Args[2]

    int_a, _ := strconv.Atoi(str_a)
    int_b, _ := strconv.Atoi(str_b)

    multiplyResult := int_a * int_b
    squareResult := multiplyResult * multiplyResult

    fmt.Printf("Result is %d\n", squareResult)
}

func multiply(a, b int) int {
    return a * b
}

func square(a int) int {
    return a * a
}

I suppose I could import math and use the math.Pow() function rather than calculating the square manually, but where’s the fun in that? So there it is, some simple code ready to be compiled. Let’s build it then time the execution just for reference:

$ squariply> go build squariply
$ squariply>
$ squariply> time ./squariply 2 4
Result is 64
./squariply 2 4  0.00s user 0.00s system 61% cpu 0.007 total
$ squariply> time ./squariply 2 4
Result is 64
./squariply 2 4  0.00s user 0.00s system 57% cpu 0.006 total
$ squariply> time ./squariply 2 4
Result is 64
./squariply 2 4  0.00s user 0.00s system 58% cpu 0.006 total

Creating Microservices

For this demonstration, I’m going to take the two mathematical functions and turn each of them into a microservice. Naturally this is a very bad example of a good function to convert into a microservice, but for the sake of keeping the demonstration simple, it seems like a reasonable compromise.

The microservices will be each be served by a Docker container running apache/PHP and a REST API. The main program will make a REST call to one container to get the result of a multiplication function, then will use that result to pass via REST to the other container to get the result of the square function.

Microservice Flow

Again for simplicity, the mathematical function will be performed directly in PHP rather than calling another program to do it, so you’ll have to trust your imagination with the idea either that the function code in the container would directly accept the REST API call, or that the the received data would be passed to the code for processing. For the demonstration it’s much simpler to use apache as the HTTP daemon, a .htaccess file to redirect API calls to a PHP script, and PHP to process the call and perform the function.

Microservices Container Flow

Docker Configuration

My docker containers are pretty boring. Here’s the Dockerfile I used to build the basic container image:

# Debian image supporting apache / mod rewrite
#

# Base image
FROM eboraas/apache-php

# Maintainer
MAINTAINER John Herbert

# Set stuff up
RUN apt-get update
RUN apt-get install -y vim bash

EXPOSE 80

# Insert apache conf file with htaccess enabled
COPY apache2.conf /etc/apache2/apache2.conf

# Enable mod_rewrite
RUN a2enmod rewrite

# Restart apache
RUN service apache2 restart

Simple enough: take a base image and tweak it slightly. The baseĀ image did not enable mod rewrite and did not have the AllowOverride All option set in apache2.conf, so I created a local copy of the config file with the options I wanted, and Docker will copy that into the image at build time. I also had to enable mod_rewrite and reload the service for it to take effect.

Building the new image from there is very simple:

$ docker/apache1> docker build -t apache1 .
$ docker/apache1>

Finally, I created an empty directory for each container and ran the image, mapping the local directory to the /var/www/html/ directory within the container (so I could easily edit the web files). For the demo I’m running both containers on my Mac, so obviously I can’t have two containers trying to bind to port 80. Thus when I invoke the container, I map the first container’s port 80 to port 5001 on my Mac, and the second container to port 5002. Finally, I add the command to run apache, and launch the containers:

$ docker/apache1> docker run -d -p 5001:80 -v /Users/john/docker/apache1/html-multiply:/var/www/html apache1 /usr/sbin/apache2ctl -D FOREGROUND
0ffc8c171e85690409d084253613603b0da2f08ebe3f623997e0612ce717105d

$ docker/apache1> docker run -d -p 5002:80 -v /Users/john/docker/apache1/html-square:/var/www/html apache1 /usr/sbin/apache2ctl -D FOREGROUND
2c3f81a88b629a7fdb18832a45fe5e19dbd1f1d75358f4267c79554689774267
$ docker/apache1>

.htaccess Configuration

In each of the mapped local directories, I created a simple .htaccess file looking like this:

    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule api/(.*)$ /api.php?request=$1 [QSA,NC,L]

Roughly translated, if the URL begins with api/ and doesn’t point to a file or directory that actually exists, apache will call a script called /api.php and put everything after the initial api/ as a request variable passed to the script. So if I request http://localhost:5001/api/multiply/?a=2&b=4, the regular expression in the RewriteRule will match multiply/?a=2&b=4 and, as the first match in parentheses, it will populate $1. The api.php script will believe it was called as http://localhost:5001/api.php?request=multiply/&a=2&b=4. Now I can use api.php to process the querystring passed to it and figure out if the request is for a function that’s supported, and handle it accordingly.

api.php

First, a disclaimer: while I will tell you that I am not a programmer, when it comes to PHP I am really not a programmer. However, I can hack my way by enough to make this work ok. All I need is a script that checks the function passed in the querystring and grabs the variables passed in (a and b in the example above), does some simply math, then returns a result. My code, such as it is, looks something like this for the ‘multiply’ server:

 0) { 
        $json_response_array = array( "result" => array( "status" => "failed", "error_count" => $errors, "error_messages" => $error_messages ), "answer" => "" );
    } else { 
        $json_response_array = array( "result" => array( "status" => "OK", "error_count" => $errors, ), "answer" => $answer ); 
    } 

    // Convert to json and return response 
    echo ( json_encode($json_response_array) ); 
?>

As you can see above, I opted to send back the response in JSON format. This is overcomplicated when to be honest I could have got away with just sending back the result, but isn’t it just the cutest?

Quick REST Test

Does my mini-API work? Of course it does:

$ ~> curl "http://multiply.userv.myapp:5001/api/multiply/?a=2&b=4"
{"result":{"status":"OK","error_count":0},"answer":8}

$ ~> curl "http://square.userv.myapp:5001/api/square/?a=8"
{"result":{"status":"OK","error_count":0},"answer":64}

Next Steps

So what’s missing? I need a program to accept the arguments, make the REST calls and process the results. Once that’s running we’ll have some kind of microservices thing going on, and that’s something for the next post in this series.

Be the first to comment

Leave a Reply

Your email address will not be published.


*


 

This site uses Akismet to reduce spam. Learn how your comment data is processed.