Programming patterns are concepts that make code development more of a science, and less than a guessing game.
At times, it is nice to invent how the internals of a program behave. It feels fresh. Other times, though, it feels like a guessing game. A game that in most cases the developer is doomed to lose, because it’s simply too difficult to foresee the infinite ways a program will evolve.
That is why it is better to rely on programming patterns that are best-practices, tested and trusted over a long period of time. Patterns are one of the few things that make software development an engineering specialty. Stop the guessing game. We can all be artists, just not all of the time.
I have found patterns useful throughout my fifteen years (in the making) of work. In this article, and in a few more that I have planned, I am going to discuss things I learned using these concepts, as well as things I am learning, and I will use one pattern in each article to lead the discussion. That will make it easier to describe examples taken from my own daily practice, hoping that you will find them useful too. I believe that examples, to be useful, have to be real—not just realistic.
To start things off, in this article I am going to talk about the pattern that I have used the most. It’s called the observer.
In certain types of engineering, professionals cannot escape from patterns. In construction work, for example, buildings’, bridges’, and houses’ development must adhere to certain patterns. In many countries, it is forbidden to develop buildings that don’t fulfill the technical specifications prescribed by the government. That is because construction work is seen as closely related to safety. Software isn’t.
Thus it often happens that programs are written without clear guidelines. The closest that software engineering comes to construction engineering—to my knowledge—is with software infrastructure development. In that specialty, the big vendors who dominate the market (AWS, GCP, etc.) set the best-practice patterns and invest heavily in their customers’ education, because they make money out of it. There’s no exact equivalent of that in software development, where it’s all about writing code.
Perhaps the reason is that patterns in software development are theoretical concepts. They are ideas around how to organize the program’s code, so that it is more maintainable and, most importantly, standard. That is the most important feature, in my opinion, and the biggest benefit of employing well-known patterns: The fact that I can talk with a colleague and without looking at the tiny details, and ten thousand lines of code, I can simply tell him What I am doing there is an observer pattern. Or factory pattern. Or decorator pattern, and so on. He will understand how the code works without needing to look at it. Fast, clear communication in the workplace—the holy grail.
These concepts are well studied, of course, and taught. For me, I took a class that dealt almost entirely with software patterns during the penultimate year of a five-year software engineering degree. Not to mention books. Bookshelves are full of books on this subject, the most famous of which is, I suppose, the Gang of Four’s book. That is also what I studied during the course in college. It’s a nice little book, with examples in Smalltalk language. That in itself would make today’s developers turn up their noses.
Speaking of today’s developers, in fifteen years of practice I’ve had just a handful work-related conversations about software engineering patterns. Most of the excellent developers who build all of the stuff we see on the internet these days are self-taught—no kidding—and the ones who arent’s have too many deadlines to think about best-practices, and, in the end, who cares about patterns if the program works?
⋄ ⋄ ⋄
I do.
The time I remember more vividly talking about software patterns in the office was during my PhD. Both my coworker and I were working on the development of formal algorithms to improve road traffic, and, although I don’t remember the conversation exactly, I said something like This traffic light needs to know when the one downstream turns green. You can do an observer pattern. We were writing code to run numerical simulations of our algorithms. They turned out to be not so formal, after all.
I believe the observer pattern is the most useful of the ones I know. It’s a celebrity with its own Wikipedia page. I have used it countless times, before and since that conversation.
Just a few days ago I used it again, in a flavor that I don’t think I ever had before.
The basic idea
A rare case of Wikipedia’s clarity: The observer pattern is a software design pattern in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes.
In other words: There’s a living thing in the memory of the running program—be it a class instance, a struct, a function state—and there are other things that would like to know when some events occur in that first thing. Because they really, really want to know, they express their interest in becoming “observers” of it. So that it (the first thing) will add them in its contacts book, and will notify them in due course. And, because we like to give names to things, the first thing is called subject, and the other things are called…well, observers.
The implementation is conceptually simple:
- The subject keeps an array with pointers to all of the observers. This is the contacts book.
- The subject makes available a function (or method) that can be called by observers, to express their interest. I usually call this in my code
register(...)
, orsubscribe(...)
. - The observers make available a function that can be called by the subject, as a means of notification. I usually call this in my code
notifyme(...)
.
Thus, when something important happens in the program to the subject, all it has to do is:
1. Loop over the contact books
1a. For each contact, call its notifyme(...) method.
There’s no step 2 in the algorithm.
The beautiful thing about this pattern is that it is at the same time simple (fact), elegant (my opinion), and powerful (fact). It quite literally is the basis for millions of lines of codes that power giant distributed systems. It’s intrinsic in the nature of the register/notify, or subscribe/receive, or pub/sub to make it for distributed computations.
My advice at this point would be to pause reading, and review in your mind the things you have worked on recently: programs you wrote, programs you are writing, programs you didn’t write and just looked at. Ask yourself, Where would the Observer pattern fit here? The answer might be Nowhere!, and that’s okay, but then again, if you work on any type of distributed systems, chances are this pattern’s application makes sense. Maybe it’s already there, and now you can spot it because you know it’s got a name and a wiki page.
The following are some examples of programs in which I used it: simulation of distributed control algorithms (each controller wanted to observe some of the others), SLA/SLO monitoring of web-services (some SLO test needed to know when some other is finished), parallel compilation of a domain-specific language (tokenization and semantic analysis ran concurrently and the latter needed to observe the former). These are old, but struck with me and are the ones I remember vividly.
Implementation: threads
In my mind, the most natural implementation of this pattern is a multi-threaded program. The subject as well as the observers run in distinct threads of execution.
As a quick reminder, “threads” means procedures that share the same memory space and are scheduled by the same CPU so that it seems like they run at the same time, while in fact each gets a tiny slot of time. “Processes”, instead, are procedures that do not share memory, and really run at the same time (assuming there are multiple CPUs), using as many CPUs as there are available to the program.
To have the subject and the observers to run in threads makes it easy to handle the communication’s complexity. Especially since all one has to do is to instantiate a ThreadPoolExecutor, or whatever the name in the language of choice these days, and then throw in the pool the execution of a function applied on the subject/observers. Usually, that function is named run(...)
.
object A { // type: thread, subject
my_observers = [ ] (field, array)
registerme(self, observer):
add(my_observers, observer)
run(self, ...):
do_work(...)
// then, after work is done
foreach elem in my_observers:
elem.notifyme(..., self)
}
object B { // type: thread, observer
notifyme(..., subject):
do_work(..., subject)
register(self, subject):
subject.registerme(self)
}
main { // program entrypoint
subj = create_subjects(1)
obs = create_observers(5)
foreach elem in observers:
elem.register(subj)
pool = create_thread_pool()
pool.add(subj, ...obs)
pool.exec()
}
The snippet above can almost be run as-is, except that the functions do_work(...)
should do something specific to your program. Ah, also, it’s pseudo code.
There’s one caveat though. Notice that the subject calls the observers’ function notifyme(...)
in the last line of its run(...)
method. Therefore, if any of the observers do not respond immediately to that call, the subject will hang and be blocked.
In a multi-threaded implementation, that may not be too bad. If the subject is just one, and if the language implementation makes threads lightweight (e.g., Golang, Rust), then having one thread hanging around without much to do is not too bad.
But it’s suboptimal, and can leak resources, and who likes that? Especially since the easiest fix is not hard to come up with. In the run(...)
function, the observers should first check if they can do their work, for instance if a flag has been set. I know that I am over-simplifying here, but please bear with me: I have a point! Who sets this flag? The observers themselves, but only when they are notified.
object B { // type: thread, observer
is_ready = false (instance field, bool)
notifyme(..., subject):
set(is_ready, true)
// so it returns immediately and the caller is not blocked
run(...):
while (is_ready == false):
wait(delta_seconds)
do_work(...)
}
In the pseudo code above there’s a pattern called busy-waiting. It may very well be the oldest pattern on earth, and you can find countless criticisms of it on the internet. However, when the waiting part is implemented correctly, which means without holding the CPU hostage, then busy-waiting is not too bad. All of the languages have primitives to wait. For example: time.sleep(...)
in Python, time.Sleep(...)
in Go.
Implementation: unthreaded
To run threads means to give up the control about what part of your program runs at what instant to the operating system. The operating system selects at every CPU clock what thread must run for the next clock cycle, and the choice is based on a lot of factors. Most of these factors are outside the programmer’s control. Sometimes that is fine. You can trust that the OS will do its job, and if the threads are correctly implemented, which in practice means there are no deadlocks, things will run smoothly.
Other times, though, you just don’t want to give up control. It must be possible to pause functions executions based on conditions you know, right? It sure is, and it’s not a new thing either. It’s been around a while, and its name is asynchronous routines. All of the routines run in the same thread of execution, and they can be paused—literally: Their state is saved on the stack, and the control is given to another function even if the former hasn’t “returned” yet. Typically, programming languages offer primitives to do that. In Go, a function can just wait on a channel (syntax: <- channelName) until somebody unblock that channel. In JS, some flavor of the keywords async/await. The most explicit of all is, in my opinion, Python, because it’s got a keyword yield
, which means exactly to yield control (and data) to somebody else.
Here is how I would rewrite the previous snippet. Notice that only two lines have changed. One change is to use yield instead of waiting/sleeping. The other is because the object is not a thread anymore.
object B { // type: observer
is_ready = false (instance field, bool)
notifyme(..., subject):
set(is_ready, true)
// so it returns immediately and the caller is not blocked
run(...):
while (is_ready == false):
yield // This will yield until somebody says
// "you: start again"
do_work(...)
}
On the other hand, if you don’t want to give up the control to the OS, then you will have to manage the routines. In practice, that means to restart routines that stopped because of a yield. In Python the restart operation is done with next(...)
. All considered there is a bit more work to do in the program entrypoint, a bit more code to write.
This pattern does seem powerful now, isn’t it? The priorities between routines are fully in the program’s control—not the operating system’s—and, therefore, the programmer can choose what routine goes next at any given point.
main { // program entrypoint
subj = create_subjects(1)
obs = create_observers(5)
foreach elem in observers:
elem.register(subj)
// Instead of a thread pool,
// all objects go in an array and they are scheduled by next()
everybody = make_list(subj, obs)
for elem in everybody:
elem() // Start all of them, some will wait on a yield
for elem = select_next_routine(everybody):
next(elem) // Restart this "elem"
}
As always, there are details that the pseudo code doesn’t show. Here are two of them: the next(...)
function in Python can throw a StopIteration
error, which signals that the routine is really finished, and needs to be handled; In this example the subject doesn’t actually ever yield, so it will finish immediately. I hope that the missing details do not obfuscate the main points of this discussion.
I have worked on systems where the additional control over the execution order of each object was important. In a recent one, I needed to measure the execution time of certain processes. If these processes were threads, their execution could be stopped by the operating system, and transparently to the program’s code. This would happen especially if the number of CPUs was less than the number of threads, which, well, is almost always the case in a system that does actual work. Instead, normal procedures that are not threads only pause when they yield, never to make room for another thread of the same program (of course, they could be stopped to run a thread of another program, but that just means that you have to know what else is running in your servers).
That made the recorded execution times more accurate, especially under heavy load. It was, however, a niche application, and in the vast majority of cases it may not matter what concurrency paradigm you use. Fine-tuning the execution flow is additional work, and needs additional care. If your program yields one time too many, it may hang forever. If your program doesn’t call next
the correct amount of times, it may hang forever, or it may crash. With threads, instead, your program just needs to throw the threads in a pool, and the operating system will take care of the rest. To be extra safe, if you don’t want to handle semaphores and locks, just avoid shared memory between the threads (I am over-simplifying again).
I know the following one may be the most frequent sentence on this site, but here it comes: it depends. Concurrency and parallelism is a vast subject, and what’s best to do depends very much on the details of the problem at hand.
Here’s good news: the point of this article isn’t about concurrency, nor about parallelism. It’s about software implementation patterns, and, in particular, the observer pattern.
Who are you calling stateless?
Many of you are coming from a web development background, where the big words today are CRUD and stateless. And AI. And API, of course.
Can you implement an observer pattern in a stateless system? At first look, it appears you cannot. Isn’t the list of observers part of the state of the subject?
Yes, it is. In some cases, though, it would be nice to use it.
Let’s talk about API, one of today’s favorite words. Many of us develop and maintain web services API that are CRUD and stateless. When a user of the system logs in, your program needs to find some details about the user in the local database. It also needs to find some details about their payments in Stripe. After the program has gotten both the user details and the payment details, it needs to merge these details to be able to do something else in the same database. For example, the database contains the address, but the user changed their address in Stripe’s Customers Portal, so once it finds the new address it should compare the two and update the table with the new one.
The first database query will be very fast. Your program can do that in a few microseconds (assuming the database is in the same local network), then do the Stripe query in a few hundreds milliseconds, and then do another database query.
Or, it could do the first two steps concurrently. The database query is so fast, that the part of the program that takes care of it will need to hang around while waiting for the Stripe query to be finished. Hanging around doing what? Eating CPU, most likely, and nobody wants that. It should, instead, pause. Pause for the other to be finished.
If you find yourself thinking “to pause for something to happen”, now you know it smells like an observer pattern.
I would make two objects, within the same endpoint’s route process, each taking care of one of the two first steps. Let’s call them ProcDB and ProcStripe. Then I would register the ProcDB object as an observer of ProcStripe, and I would implement ProcStripe so that it sends a notification to its observers after it receives the data from Stripe. ProcDB, instead, would first do the database query and then…pause. Until it’s notified by the subject (ProcStripe).
How to pause? If ProcDB and ProcStripe run in two different threads, you could make a busy waiting loop inside ProcDB:
while true:
if received_notification:
break
sleep(0.1)
This will work, and, like I said, it’s not as terrible as people on the internet would say. The sleep function is a wrapper of a system call, and let’s assume it is very well implemented.
But, as we have all learned the hard way, APIs are their own weird category. Spawning threads just like that can be dangerous. The whole thing is certainly running within a web server system, like nginx, or gunicorn, or net/http, or, likely, a combination of those. Unless you are super experienced with these systems, it can be hard to guess what they do. Some spawn a thread for each incoming request. Some don’t. What’s their memory configuration? How many workers are being run, and on how many nodes?
Those are good questions, but are pointless in this case, because you don’t need threads. ProcDB doesn’t need to go to sleep to wait, it can just yield. Instead of running two threads apart from the main one, run just the main thread of execution, and execute ProcDB until it yields, and have ProcStripe send the notification to it (exactly as before), with or without yielding anything (it should not matter, unless ProcStripe does other things).
while true:
if received_notification:
break
yield
The idea in the last pseudo code snippet works because the main thread of execution, which in this example is the endpoint route, will call next()
on both ProcDB and ProcStripe, while ProcStripe will notify ProcDB—whenever it’s finished—and hence set received_notification
.
I hope that, despite the ten-thousand feet point of view, the discussion in this article was helpful. I believe that now you are itching to write some code. I am. Maybe to rewrite some too.