Local microservices development with Edward

Lately I’ve started playing around with microservices concepts and Kubernetes, which are really intriguing to me. I love the ideas being those things, so I began getting my hands dirty with a personal project.

Wait a minute, the dude is making microservice alone? Duh. Yeah, I know, but I’m a very curious person passionate about learning.

Anyway, I built my first two services using Go and threw gRPC in the mix, ‘coz why only learn one thing at a time? Those services were:

  • A database interface
  • A front end for users

Nothing fancy at all. 👍🏼

Then I wrote the two Dockerfiles, built the respective Docker images, launched Minikube, wrote a Kubernetes config file and applied it: awesome! Nope, apparently something went wrong. No problem, right? It’s part of the beauty of experimenting with things. Investigating the cause of the issue I found a typo in one of the services, which was no big deal. I fixed the typo, rebuilt the Docker image, apply the changes to Minikube and everything was looking good, except for one thing: while I was enjoying the way this all works, I soon came to a point where I found it not to be very practical for development (not to mention that I was losing advantage of Go’s blazing fast compile time).

In the need for a better solution (again, by a development point of view), I stumbled across Edward, which claims to be “A tool for managing local microservice instances”. How couldn’t I give it a try?

Edward requires Go 1.6, once you have that you install the tool with

$ go get github.com/yext/edward

Cool, now all it needs to do its job is an edward.json configuration file, which is composed of three main sections: imports, groups and services. services are the building blocks through which, guess what, you define your services, providing a path, build and launch commands, environment variables for that particular piece of software and many other things (seriously, take a look at the docs, you can even warm up your services).

So I wrote the definition for both my dummy services, and it looked like this:

# edward.json
{
  "imports": [],
  "groups": [],
  "services": [
    {
      "name": "web",
      "path": "./web",
      "commands": {
        "build": "go build",
        "launch": "./web"
      }
    },
    {
      "name": "db_interface",
      "path": "./db_interface",
      "commands": {
        "build": "go build",
        "launch": "./db_interface"
      }
    }
  ]
}

Now we’re ready to start, let’s begin spinning up the web service with

$ edward start web

web > Build: [OK] (1.641s)
web > Start: [OK] (182.167ms)

Then on with the database interface

$ edward start db_interface

db_interface > Build:  [OK] (2.86s)
db_interface > Start:  [OK] (167.675ms)

Things are looking good so far, but how about checking the status or our services?

$ edward status

+--------------+---------+-------+-------+----------+---------+---------------------+
|     NAME     | STATUS  |  PID  | PORTS |  STDOUT  | STDERR  |     START TIME      |
+--------------+---------+-------+-------+----------+---------+---------------------+
| db_interface | RUNNING | 78429 | 50051 | 0 lines  | 2 lines | 2017-07-28 12:32:46 |
| web          | RUNNING | 78321 | 3000  | 11 lines | 1 lines | 2017-07-28 12:30:00 |
+--------------+---------+-------+-------+----------+---------+---------------------+

But what if we wanted to manage more than one service at once? That’s what groups are for, remember them? Let’s create one for this awesome app then. Let’s add a group to our configuration:

# edward.json
{
  "imports": [],
  "groups": [
    {
      "name": "front",
      "children": ["web", "db_interface"]
    }
  ],
  "services": [
    {
      "name": "web",
      "path": "./web",
      "commands": {
        "build": "go build",
        "launch": "./web"
      }
    },
    {
      "name": "db_interface",
      "path": "./db_interface",
      "commands": {
        "build": "go build",
        "launch": "./db_interface"
      }
    }
  ]
}

Now the two services are manageable together through the app group, and we can, for instance, restart them at once running

$ edward restart app

front > web > Stop:  [OK] (223.307ms)
front > web > Build:  [OK] (1.463s)
front > web > Start:  [OK] (170.654ms)
front > db_interface > Stop:  [OK] (206.495ms)
front > db_interface > Build:  [OK] (2.825s)
front > db_interface > Start:  [OK] (170.689ms)

This is way faster than the build the Docker image, apply to Minikube process, but there is still another Edward feature that came in very hand for me, and that is the possibility to watch on services folder and then automatically rebuild them if a change occurred. To use it, simply add the watch key in the configuration, specifying the folders the folder you want Edward to keep an eye on and those that need to be excluded. Let’s add this to the web service

# edward.json
{
  "imports": [],
  "groups": [
    {
      "name": "front",
      "children": ["web", "db_interface"]
    }
  ],
  "services": [
    {
      "name": "web",
      "path": "./web",
      "watch": {
        "include": ["."]
      },
      "commands": {
        "build": "go build",
        "launch": "./web"
      }
    },
    {
      "name": "db_interface",
      "path": "./db_interface",
      "commands": {
        "build": "go build",
        "launch": "./db_interface"
      }
    }
  ]
}

And that’s it, now this super simple microservices infrastructure is also simply manageable, and the web service will rebuild and relaunch if any changes occur within its folder.
Overall Edward was such a great discovery, if you want to see more of the things it can do for you (I advise you do) check out the speech from Tom Elliott at GopherCon 2017 and Edward documentation.

https://uracode.comMember since February 10, 2017

Former designer then turned to coder (old habits die hard), today I'm a software developer and solution architect at KIMon S.r.l., working everyday with Golang, Ruby, Docker, Kubernetes and AWS. The thing I enjoy most of my job is learning.

Comments

  • Dyson 3 months ago

    I’ve been looking for something like Edward so thanks!

    Something you might find useful instead of building and then calling the binary is to launch your go projects with the following:

    “launch”: “/bin/sh -c ‘go run *.go'” (you need to wrap the wildcard command in a shell because os.Exec doesn’t do globbing)

    This saves having to add the binary to .gitignore and keeps the project directory cleaner.

    • Francesco Renzi 3 months ago

      That is very good to know, thanks a lot for sharing Dyson! And glad you enjoyed the post

Your email address will not be published. Required fields are marked *