Job candidates, tech-savvy users and others are often curious about the technology that powers FATpick.
And as engineering professionals we think there is value to an open, informed discussion of this topic.
And to be candid, we're a little proud of what our small team has accomplished in a short time, and engineers rarely have a platform to brag about this sort of thing.
So we're more than happy to talk about the design decisions we've made and some of the rationale behind them.
Maybe this should go without saying, but I feel compelled to mention that these decisions were made within a specific context. In particular, we are an early-stage start-up with a hiring philosophy that favors small teams of exceptional people. Those people bring certain skills and experience to the table. All of that is layered on top of the specific "problem domain" our product must address. Each of these dimensions (and more) is a factor that influences the design of our technology stack. We've been more than content with our platform so far, and are happy to recommend many of the same solutions to others. But our context may or may not match your context. As much as we love and recommend these tools, you'll want to evaluate for yourself whether or not they are a good fit for your team and your domain.
By the way, we weren't kidding about finding value in "open" (public) conversations of this topic among the engineering community. We're certainly not the first to publish a write-up like this, but I hope we're not the last either. If you have any questions, comments or reactions please contact us or hit us up as @FATpickApp on Twitter. Even better, if you have influence over your own platform decisions, I encourage you to share as much as you are comfortable with about your tech stack and how it came to be.
For the purpose of this write-up I've broken the description down into the following topics:
- Scope - a statement on the general scope of the FATpick technology stack
- Server Platform - an overview of the FATpick server architecture
- Client Platform - an overview of the FATpick client architecture
- Notable Components - an enumeration of major technologies used to create FATpick
FATpick is primarily experienced as a native app, installed and running on a user's computer or mobile device. But FATpick isn't really a stand-alone application. It's more like what used to be called (completely coincidentally) a "fat client". Non-trivial logic and processing is needed within both the client and server components of the system.
The FATpick client provides sophisticated audio processing functionality embedded in a media-rich, low-latency user interface. But the client apps also depend on a collection of services "in the cloud" that provide, among other functions, audio analysis and processing, data persistence, and content distribution services.
Hence FATpick requires a significant presence on both the client and server. Mobile and desktop clients, complex apps in their own right, are supported by a federation of microservices accessed over REST or similar web-service APIs.
FATpick's server platform is composed of a federation of microservices that support both the www.fatpick.com web site and the web-service APIs that the client applications rely on.
Each "tier" represents a cluster of identical (redundant) microservices, fronted by an instance of HAProxy which manages and load-balances between the other nodes in the cluster.
Each instance of a microservice is housed within an independent Docker container. You can see this more expressly in the component diagram, where the flat rectangles indicate logical groupings of components and the three-dimensional rectangles (UML "nodes") represent individual Docker containers.
The Web Tier
This tier acts as a reverse-proxy for the application servers and directly serves certain static assets.
The App Tier
The Media Tier
These applications are responsible for processing, analyzing and generating multimedia content.
Notably, the media service instances embed a headless instance of an Electron app — partially derived from and extending the same codebase as the FATpick client app itself — in order to make use of WebAudio-based processing. We think that's pretty neat.
The Data Tier
The data tier is simply composed of a cluster of PostgreSQL instances with one master (read/write) instance supported by several read-only clones. Streaming replication is used to synchronize the data from the master database to the read-only clones.
The FATpick desktop client is a native application for Windows, MacOS and (unofficially) Linux based on the Electron platform.
Internally the desktop client is structured like a single-page [web] application (SPA), in which the user-interface is often updated incrementally and asynchronously rather than through the click-link-and-refresh-full-page transactional workflow traditionally used on the "conventional" web.
But the most interesting and important component of the FATpick app is a single "view" within that SPA: the FATpick "tab player" that provides the game-play experience. As you might expect there are a lot of facets to a component of this complexity and import, but the Web Audio and Canvas standards are among the key technologies that support the FATpick game-play engine.
A simple alphabetical listing of some of the key components of the FATpick stack.
Docker is a containerization platform that helps one bundle, deploy and manage software components as isolated, self-contained lightweight virtual machines.
We find Docker useful across the entire software development lifecycle. It's a great way to deploy and manage microservices in the production environment and simplify configuration management, of course. But Docker really helps us manage and document dependencies (both internal and external), throughout the entire development process. With Docker (and a tiny bit infrastructure support and process discipline) it's easy to maintain consistency across developer sandboxes and production environments and to ensure that any changes to the operational infrastructural or configuration are documented, managed and reproducibly deployed.
Electron is a cross-platform framework for the development of native desktop applications, based on Chromium (the open-source foundation on which Google's Chrome browser is built).
UPDATE: Edison later elaborated on some of our reasons for using Electron in another blog post.
Its popularity is well deserved. Express is a mature, well-designed, lightweight, and flexible framework. To use a Ruby-specific comparison you might be familiar with, Express is closer in spirit and design to Sinatra than to Ruby-on-Rails. Express has an active developer and user community that provides a massive collection of extensions, add-ons, tools, documentation and overall support.
HAProxy is a load-balancing reverse-proxy service designed for performance and reliability. (The "HA" stands for high availability.) It supports both HTTP/HTTPS and raw TCP transactions.
At FATpick we use HAProxy to manage and load-balance across the nodes in a cluster. That is, each of the service "tiers" described above is a cluster of two or more instances of the same microservice (each running within its own Docker container), with an instance of HAProxy (another Docker container) acting as the entry point, service-discovery mechanism and load-balancer between the nodes of the cluster.
All traffic to the cluster flows through HAProxy, which allows us to seamlessly add nodes to or remove nodes from the cluster just using HAProxy's backend server pool. This supports dynamic scaling: if a service gets too busy we just start up another container (running the latest Docker image for the microservice) and HAProxy is the only component that needs to be notified about the change. This also supports zero-downtime service upgrades: we can stand-up a new container running the latest version of the service and validate it before adding it to the pool, swapping out old nodes for new nodes, one (or more, if necessary) at time.
While nginx is an extremely versatile and capable load-balancer, in the FATpick server stack nginx is primarily used as a high-performance web server for static assets (flat-files). We're not really bothered by the overlap in capabilities between HAProxy and nginx. While we could probably replace each of our HAProxy instances with instances of nginx, some of our HAProxy instances are operating in TCP-mode today, so nginx isn't quite a drop-in replacement anyway.
(I think you could also probably argue that HAProxy is a bit more purpose-built for the context in which we're using it than nginx is. And being more focused and minimalistic, HAProxy is probably ever so slightly more stable, performant and simple to manage. But I'd also guess that the difference is truly negligible, maybe close to unmeasurable. Both nginx and HAProxy are extremely fast and extremely reliable. I'm confident there is no meaningful difference in practice, at least in this context.)
We make extensive use of Node.js both on the server — as the platform on which most of FATpick's microservices are built — and in client — within the Electron framework that supports FATpick's desktop applications for MacOS and Windows (and, unofficially, Linux).
PostgreSQL is a leading, quite possibly the leading, relational database management system. PostgreSQL is an extremely robust database platform known for reliability, maturity and performance.
In addition to world-class RDMBS functionality, PostgreSQL is notable for its support of non-relational features more commonly associated with document-oriented databases and schema-less (NoSQL) key-values stores.
FATpick uses PostgreSQL in a pretty conventional manner. Our data storage needs are modest in both scale and complexity, but we certainly value the reliability, durability and performance that PostgreSQL dependably provides.