All the talk about cloud-native application design revolves around “microservices.” Rather than writing big, monolithic applications, the theory goes, we should make better use of the cloud and break these applications up into a set of small (“micro”) services. Ideally, these services would be reusable in a number of different contexts. Then, application design reduces to the problem of composing and orchestrating the right sequence of microservices to accomplish the task we want done. So, what’s a microservice?
To help you wrap your head around the idea of microservices, let’s take a look at a microservice you’ve been using for years. In fact, it’s been around since before Netscape made the Internet accessible to mere mortals back in the 1990s. It’s called the Domain Name System (DNS), and you undoubtedly used it a few minutes ago when you typed “leverhawk.com” into your browser. The DNS is most-often used to map a human-readable name like “leverhawk.com” into an IP address that can be used by your web browser to access a distant web site over the Internet.
Ideally, microservices conform to the following list of properties. These properties ensure that they are correctly sized, reusable, and are composable within larger applications.
- Perform a single function – Microservices should do a small set of things (ideally one thing) really well. If you find your microservice trying to do 10 things, you probably need to decompose it further, possibly into 10 different microservices.
- Exposed through a simple API – Applications need to access your microservice through its API. The API should be simple, mapping to the high level concepts embodied in the microservice. There are no rules for what technologies should be used, but modern cloud-native microservices tend to favor protocols like REST. Ideally, you should reject protocols that are heavily dependent on specific programming ecosystems (e.g., Microsoft Windows specific) or languages (e.g., Java only), as they will limit the flexibility of clients to consume your microservice.
- Upgraded and evolved independently – Ideally, your microservice is completely independent and decoupled from the clients that use it. If you find bugs or limitations in your microservice implementation, you should be able to fix or evolve it without rewriting or stopping the applications that use it.
- Architected for fault-tolerance – Because microservices perform small, atomic functions, they are often consumed by many different applications. If microservices aren’t fault tolerant, then the failure of a single microservice can result in the failures of multiple different applications. To prevent this from happening, most microservices are architected with fault-tolerance in mind.
- Accessed over the network – Microservices are designed to run in the cloud and be accessed over the network from other microservices (yes, microservices can consume other microservices) and applications. While a microservice might be placed and run in the same node as an application, particularly for unit testing where we’re doing all the work on a single node, that’s the exception not the rule. This can influence the API, forcing the microservice designer to deal with issues like network latency, for instance.
The DNS as a Microservice
Okay, so given this list of properties, how does the DNS stack up?
- Perform a single function – Generally, the DNS does a single thing: map human readable names into IP addresses. It does have a few different modes and options, however, allowing email servers to find the remote email server for a remote domain, for example. The email server for leverhawk.com might be located on a different node with a different IP address than the node servicing web requests. This is done using “MX” records in the DNS (see the DNS protocol specs for more information). If you wanted to generalize, the DNS is basically a simple distributed database that looks up domain names and returns the corresponding record, where the record could be an IP address, another domain name, a string of text, or whatnot. Typically, however, it’s used by programs to retrieve an IP address.
- Exposed through a simple API – The DNS has a fairly simple API, documented in the DNS specifications. For efficiency and low-latency reasons, DNS is typically accessed by clients using a simple UDP-based request-response protocol. The request is coded in a single packet and the response is coded in a single packet. Since DNS requests are very short-lived and the DNS might require contact with a server around the world, this eliminates the latency of setting up a TCP connection (notably, the multi-way handshake at the start of the TCP connection).
- Upgraded and evolved independently – The DNS was designed to operate in an almost completely distributed fashion. This was done for reasons of both fault tolerance and administration. In particular, DNS names are hierarchical and are administered at different levels. While top-level domains are administered in a more centralized fashion, enterprise administrators are able to create local domain names themselves (e.g., mymachine.leverhawk.com). The domain name system is run on hundreds of thousands of different servers, administered by hundreds of thousands (millions?) of organizations around the world. Each of these servers is installed and upgraded independently, allowing the DNS to evolve without disrupting clients.
- Architected for fault-tolerance – DNS servers are typically deployed in at least pairs and often in triplicate. The root servers use special fault-tolerant techniques such as anycast address space to clients quickly connect with redundant servers in the event of an outage. DNS servers also delegate portions of the DNS resolution to local servers to help shed load. Local servers also cache responses allowing them to respond to popular queries for a period of time without accessing the rest of the system, which helps hide faults in the rest of the system. While any given part of the DNS namespace might become inoperable, preventing the lookup of something like mymachine.leverhawk.com, the global DNS as a whole is extremely fault-tolerant and resilient, even in the face of determined DDoS attacks on the root servers.
- Accessed over the network – The DNS is certainly made to by accessed over the network, with its popular UDP-based protocol (the DNS also supports a TCP-based protocol, but that’s most often used for name-server-to-name-server zone transfers, not individual name lookups for clients).
So, there you have it. The DNS is probably one of the oldest microservices in existence, dating back to the 1980s, before the World-Wide Web and the rise of the popular Internet. Its architecture is a model for others to follow and it has stood the test of time, servicing uncountable requests daily for decades. If you want to wrap your head around microservices, you would do well to study the DNS architecture and copy some of its general principles (though perhaps not the details).