This site mentions such thing: http://software-pattern.org/Reflection. It says that it is possible to replace part of the application behaviour dynamically. Does it mean that we can achieve partial deployments in monolyths? This of course is possible with careful use of microservices nowadays. In principle we can ensure high availability, by slowly replacing a deployment unit in loosely coupled environment. Are there real examples of this technique in monolyths using reflection? There is of course a thing called hot deployment, but i am not sure, if it supports routing to two simultaneously running codebases.
-
see Why do 'some examples' and 'list of things' questions get closed?– gnatApr 6, 2022 at 8:57
-
1Sample pluggable architecture using Reflection– John WuApr 6, 2022 at 12:06
-
They are most likely talking about plugins rather than hot deployment.– Karl BielefeldtApr 6, 2022 at 13:13
-
I think you should take the time to read the chapter of the book where you cited this Reflection pattern from. Buschmann et al had most probably something different in mind than what you think.– Doc BrownApr 11, 2022 at 21:21
1 Answer
A monolithic architecture does not preclude things such as rolling updates for zero-downtime deployment, or splitting the software into multiple libraries that can be updated independently (the software as a whole would still have to restart to load the new library).
Inserting new code at runtime is a really tricky problem though. There are a lot of live-reload and hot-patching solutions, but most of them are intended for development use only and will possibly reload the entire software if an in-place change is not possible.
The issue is that we can't just replace a part of the running software with a new version, but we must modify the running software to safely redirect function calls etc to the new version. In some cases, this is impossible, for example when a data structure layout changes between versions. So this is far from a silver bullet. In the general case, it's not sufficient to only develop a new version of a component, but it's also necessary to write the migration code to update the running application. This is difficult to test and error-prone.
Reflection capabilities can help with migrating a live system to a new version, but it's not strictly necessary that the programming language itself provides these capabilities.
An interesting case-study in such issues is the Linux Kernel Livepatching technique. To update the kernel of an operating system, it is generally necessary to reboot which means downtime for that machine. There are techniques to speed this up, for example kexec or hypervisor tricks. Livepatching takes a different approach. The Linux kernel internally annotates most relevant functions to make it possible for kernel modules to add hooks, essentially a kind of reflection infrastructure (despite being written in C!).
A kernel module can use this infrastructure to do livepatching, that is to replace individual functions in the running kernel. But this is quite non-trivial – a bit like doing open heart surgery while on a parachute jump. The livepatching happens concurrently while the system remains to run and changes its state. It is not possible to completely halt the kernel and perform the migration atomically. Instead, livepatching can require a complicated migration, possibly involving multiple phases, and always respecting the kernel's consistency requirements so that different versions of a function don't interact during a migration.
Since developing a live-patch takes a lot of effort, this is not suitable as an everyday upgrade strategy, but rather for inserting high-importance security patches in systems where downtime from a reboot would be unacceptable. For example, Ubuntu/Canonical sells livepatches as part of its enterprise offerings.
This circles back to my previous point that such in-place updates are not necessary to do zero-downtime deployment, especially in a SaaS context. Rolling updates and blue–green deployments work with microservices and monoliths alike, all we need is the ability to have more than one running instance at the same time. In most cases, running a second instance to do such rolling deployments or failover is much cheaper than going through the development effort of migrating a running application, but of course this depends very much on the specific context.