I rebuilt the backend in Go. Go forces me to handle errors at every corner. This is a good thing.
try/catch is not your friend. I now know almost exactly every error that can possibly happen to a user. This not only helps me sleep at night knowing that I've done my due diligence, but user-facing errors are almost non-existent in Slimvoice. And when one does pop up, it won't involve sorting through a deep, cryptic stack trace from a complex library I didn't write.
I've done microservices in the past. I've done Docker, Mesos, Kubernetes, Rancher, you name it. The costs of managing microservices and the overhead you incur to make it happen do not pay off until your scale is unbelievably large and thus, Slimvoice is compiled into a single static binary. It's so dang easy to deploy. At the bottom of the article I have some real-world graphs.
Go is pretty fast, both in execution and compilation. If I could have written the whole thing in C I would have (I prefer absolute control over the machine), but Go's standard library is fantastic and makes it so much easier to productively write a web server (without worrying about buffer overflows and a host of other vulnerabilities from C). It's a calculated trade off.
I'm using no backend web framework like Django; it's little more than a bunch of route handlers.
GET /something → return something.
POST /something → do something and 303 redirect to a GET route. It's so simple there isn't even any code to share. I will say that instead of Go's standard
net/http package, I ended up using fasthttp from Aliaksandr Valialkin (the same guy who wrote quicktemplate, which is what the first post in this series is about).
For those that are afraid of writing a web server without a web framework and ORM, there are really only a few things you need to keep in mind:
- Make sure that you validate which user can do what on every route.
- Remember to use positional arguments instead of concatenating SQL strings to protect yourself from SQL injection attacks.
- Make sure your templating language sanitizes user data rendered to HTML to prevent XSS attacks.
- Know the cost of a database query.
- Know what memory you're allocating and when it's freed.
For example, I decided that the app needs to have the constraint that you can't have duplicate invoice numbers per user.
CREATE UNIQUE INDEX ON invoices (user_id, number);
Why leave that up to the app, which could have a bug, or I could introduce one later? This way, I know that my data is always in a good state.
Here's another example where I make sure that every invoice must have the same number of expense descriptions, expense quantities, and expense values. If the data is ever in a bad state, the database won't save it. This has been built into SQL for a long time. By using NoSQL, you are throwing away most of these benefits.
ALTER TABLE invoices ADD CONSTRAINT invoices_check CHECK (cardinality(exp_descriptions) = cardinality(exp_quantities) AND cardinality(exp_quantities) = cardinality(exp_values));
Now that Postgres does all of heavy lifting of managing constraints and running calculations (like totaling invoices), my Go server is little more than a template-rendering HTTP wrapper around my database.
Slimvoice builds everything with a Makefile. If you set up a Makefile properly, it's fast! One of the most valuable things I've done in my programming career is to read the Make documentation. It ubiquitous and agnostic of your programming language. It's quite powerful, encourages simplicity, and I use it for all kinds of projects. Here's the beginning of my Makefile for compiling the server.
.PHONY: all all: test bin/slimvoice css js images GO_SOURCES = $(shell find . -type f -name "*.go") TEMPLATE_SOURCES = $(shell find templates -type f -name "*.qtpl") # The backend binary depends on all of the source code and templates bin/slimvoice: $(GO_SOURCES) $(TEMPLATE_SOURCES) mkdir -p $(@D) go generate go build -o $@ # Tests can be run with `make test` .PHONY: test test: go test .
CSS is similar, but compiled from SASS.
STYLESHEET_SOURCES = $(shell find frontend/stylesheets -maxdepth 1 -type f -name "*.scss") STYLESHEET_INCLUDES = $(shell find frontend/stylesheets/include -type f -name "*.scss") .PHONY: css css: $(patsubst frontend/stylesheets/%.scss,public/css/%.css,$(STYLESHEET_SOURCES)) public/css/%.css: frontend/stylesheets/%.scss $(STYLESHEET_INCLUDES) mkdir -p $(@D) sassc -I frontend/stylesheets/include -t compressed $< $@
Here's the solution I came up with for creating @2x images from @4x, and @1x images from @2x for all the retina-ready
srcset attributes I used on Slimvoice. Now, when I update the high-res image, all of the smaller versions are created automatically.
4X_IMAGE_SOURCES = $(shell find frontend/images/high-dpi -type f -name "*@4x.png") 2X_IMAGE_SOURCES = $(shell find frontend/images/high-dpi -type f -name "*@2x.png") .PHONY: images images: $(patsubst firstname.lastname@example.org,email@example.com,$(4X_IMAGE_SOURCES)) \ $(patsubst firstname.lastname@example.org,email@example.com,$(4X_IMAGE_SOURCES)) \ $(patsubst firstname.lastname@example.org,email@example.com,$(4X_IMAGE_SOURCES)) \ $(patsubst firstname.lastname@example.org,email@example.com,$(2X_IMAGE_SOURCES)) \ $(patsubst firstname.lastname@example.org,email@example.com,$(2X_IMAGE_SOURCES)) firstname.lastname@example.org: email@example.com @ mkdir -p $(@D) cat $< | pngquant - > $@ firstname.lastname@example.org: email@example.com @ mkdir -p $(@D) cat $< | pngquant - > $@ firstname.lastname@example.org: email@example.com @ mkdir -p $(@D) convert $< -resize 50% - | pngquant - > $@ firstname.lastname@example.org: email@example.com @ mkdir -p $(@D) convert $< -resize 25% - | pngquant - > $@ firstname.lastname@example.org: email@example.com @ mkdir -p $(@D) convert $< -resize 50% - | pngquant - > $@
The beauty of all this is that I don't need any complicated build systems or a suite of additional software installed on my machine. GNU Make is pretty easy to come by.
At this point, things are looking pretty good. I can run
The first is live reload on the server. When I make a change to the server code, I'd like to be automatically recompiled and restarted so that I don't have to tab over to my terminal, type
./bin/slimvoice, and then tab back to the browser to start testing my changes. You don't need
nodemon or any of that baloney to restart things when you make changes. You've got bash right? You've probably got
inotify; if not it's not hard to get on your system. Here's a script I saved as
dev.sh that will restart any binary when files change. Start it with
#!/bin/bash make while true; do $@ & PID=$! inotifywait -e close_write -e create -e delete -r . make kill $PID done
nodemon is currently at 1,102 commits on GitHub at the time of this writing, which is a little overkill. (What could they be fixing with all of those commits?)
Second, is live reload in the browser. This is extremely handy when making CSS changes. It would be ideal to pipe a signal from Make to the browser indicating that something has changed, but that doesn't seem to be an available method to communicate with the browser. At the moment, I'm just using the Live Reload Firefox extension.
I run my very simple, single compiled binary at Digital Ocean on a $5 box, along with Postgres and nginx on the same machine. The machine is their cheapest offering, with 1GB RAM, 1 vCPU, and 25GB SSD.
Slimvoice has been fun and easy to develop relative to other projects I've worked on who dug themselves into a huge, miserable hole. I'm very happy with both the dev experience and UX of the final product.
Make critical decisions about why one approach is better than another, despite what the marketing page says or what everyone else is doing. The people selling the cool new stuff are typically sweeping the downsides under the rug.
Looking for someone who can help you write minimal, efficient code? Please get in touch, I'm looking for work.