Fat server, Thin client

Naveen Muguda
5 min readAug 2, 2021

In this post, I expound on the concept of “Fat server, Thin client” an idea that manifests itself in multiple established design practices, but still isn’t well-known and under-utilized in mainstream software development.

Duplication is bad, if not evil

Data duplication

An update anomaly is a data inconsistency that results from data redundancy and a partial update. The inconsistency if overlooked can manifest itself in problems in seemingly unrelated and unforeseen circumstances.

The solution to this problem is normalization, which is an act of de-duplication.

Behavior duplication

Consider a banking application. Transfer between two accounts is a supported use case. The bank periodically collects charges and deducts them from accounts. It is expected that the bank informs the account holder if the balance goes below a threshold.

This check can be performed in a) both of the use case controllers or b)within the account object. Drawing parallels with the normalization process, handling the check concern within the account object avoids/minimize introducing bugs into the accounting system.

The above diagram depicts the movement of behavior from two different clients to a server. This movement makes the client thinner, and the server fatter.

Don't Repeat Yourself

“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system”. The DRY principle is an important design principle and is the bedrock of many important principles of software development.

The recommendations/observations in this post are all applications of the DRY principle.

Client-Server

The client-server model is an interaction model that partitions tasks or workloads between the providers of a resource or service, called servers, and service requesters called clients. It is a generic one and hence widely applicable.

The servers can be objects, classes, components, microservices, libraries, or platforms, They can be co-located with clients or distributed. The interactions can be stateless or stateful, synchronous or asynchronous. Communication can be uni-directional or multi-directional. The interactions can span across multiple interaction points(methods and APIs).

Parametricity

Let’s consider an elementary software problem, that of sorting.

void sort(Integer []data)

The server(the method implementing the sort, has the option of sorting in ascending order or descending order, never both). The server can service any client which requires sorting, but the order, the sort method has been chosen.

void sort(Integer [] data, boolean ascendingOrder)

here, the server takes a flag, where allows the client to choose the choice of order.

<E extends Comparable<E>> void sort(E []data, boolean ascendingOrder)

with the above signature, the server can support sorting of a data type, which has a natural ordering mechanism.

<E> void sort (E [] data, Comparator<E> comparator)

with this signature, the server can support sorting on a subfield, secondary sorting, etc.

We saw how progressively more powerful servers were able to service more clients.

Note that client-specific choices were handled in a client agnostic manner, by employing parametricity. Also, note that invariants of the order were not compromised which embracing parametricity.

Parametricity manifests itself with classes in multiple flavors.

Dependency Injection

public class A{ B b;
public A (B b){
this.b = b;
}
}

Parametric polymorphism

class List<E>

Ad-hoc Polymorphism

via type-classes

Subtyping

Different clients might require a different kinds of customization. This can result in conditional code on the server. While this doesn’t hamper the DRY principle, an elegant way of accomplishing this is replacing conditionals with polymorphism.

Note that subtyping in this instance is a client concern, unlike the other variants which are server-side.

Note that while de-duplicating, servers become fatter than the original incarnations.

Parametricity permits a degree of control on the server to the client.

Parametricity => Orchestrability

Parametricity of a server allows serviceability to multiple clients, additionally facilitates the ability of clients to orchestrate the server.

This style of thin-ing the clients, while fattening the server is inherent to many of the recommended design practices listed below.

Callbacks

callbacks are parameters that the servers need to invoke upon completion of services.

Plugins/Microkernel Architecture

A plug-in is a bundle that adds functionality to an application, called the host application, through some well-defined architecture for extensibility. This allows clients to add functionality to an application without having access to the source code.

The application (server) provides services which the plugin developer would need, but hasn’t developed by the virtue of working with the host application.

Policy vs Mechanism

policy vs. mechanism is about keeping the part of the system that is responsible for making decisions separate from the part that enforces them.

The policy vs mechanism style is an application of the {client, parameterized server} pattern, where the policy is a parameter supplied to the server which provides mechanisms.

Transaction Script vs Domain Model

when building applications in an object-oriented style, there are two candidate styles 1) Transaction scripts and 2)Domain Models. Transaction scripts can be thought utilization of fat clients, while domain modeling is equivalent to fat servers. For non-trivial applications, domain modeling is the recommended practice.

Features vs Capabilities

Features vs capabilities can be thought of as coarser, procedural counterparts of Transaction scripts and domain models. By adopting a capability-driven mindset one can attain the twin benefits of correctness and agility.

HATEOAS

Hypermedia as the engine of application state is a powerful idea that provides an evolution of server logic with little/no change to client code. We can think of a state machine for resources, and the server informs the client of possible state transitions via markup. This significantly increases the scope of the servers, while reducing the responsibilities of the clients.

Orchestration

Use cases/features tend to become thin orchestrators of domain objects/capabilities. This allows rapid evolution and agility in application development. Note that the orchestration can be imperative or declarative. When the number of servers that clients interact with increases, orchestration abstractions emerge, they can be encapsulated further thin-ing the clients.

Gotchas

while there are significant benefits of fat server, thin client philosophy one needs to be wary of premature fattening of the server, without the request needs from the client(s). The horse needs to come before the cart.

--

--