I keep encountering the term "monolithic software" or "monolithic design". It appears to be a pejorative about legacy code based on context, but I have yet to find a more specific meaning. Does anybody know its source?
I have often wondered that also. The definition below (taken from http://en.wikipedia.org/wiki/Monolithic) seems to match the meaning that I had imagined:
monolithic: Something that is monolithic is something created in one piece, resembling a monolith such as an obelisk. It mostly signifies artifacts without any subcomponents, i.e. a non-modularized, non-componentized, non-dismantleable building block.
But how does that apply to software? Does it mean one giant file that contains all 300 subroutines for an app with no "modules"? Based on context, I don't think that is what they meant.
Some call the use of an RDBMS "monolithic" because it is not distributed (DistributedComputing). In distributed designs, each "site" apparently has a responsibility or responsibilities assigned to it (ResponsibilityDrivenDesign). It seems to me that it would be tough to integrate information under such a setup. Things such as joins and dealing with many-to-many relationships may be tough to make easy or transparent. There was a debate on this around here somewhere, but I don't remember where right now. It is possible for a RDBMS to hide the physical location of information, which seems like a better abstraction (hide location and implementation). BigIron DB's can apparently do this. I used one briefly in one shop and did not otherwise see the difference (it seemed pretty transparent from app developer viewpoint). Some of this is talked about under WebStoresDiscussion. --top
Huh? RDBMSs can and often are distributed. There're a bunch of ways to do so, from simple replication to horizontal partitioning to fancier systems. It's often not completely transparent, but close enough that you don't really care. Take a look at the LiveJournal codebase for an open-source database app that's distributed over 20+ servers. -- JonathanTang
I think they mean the software design more than they mean machine or network architecture. Good DB distribution would not require significant changes to the app. For example, it should not matter from the app code if you joined a table hosted in Japan to one hosted in Australia. --top
A monolithic system is what you get when a system grows in functionality and interdependence at the same time. MonolithicDesign is characterized by such tight coupling among modules that the modules really have no independent existence. For example, right now I'm working on a numerical system whose I/O tends to break when you change the processing control flow.
MonolithicDesign is self-perpetuating because the tight coupling makes it impossible to reuse parts of the system. The system (it's always called "the system") provides the only available implementations of vital functionality that would take months or years to reimplement, and the only way to reuse part of the system is to reuse the whole thing.
Characteristics of MonolithicDesign:
- Functionality implemented by part of the system cannot be reused without using the entire system.
- To make one part of the system work, other parts must be "tricked" by using them, even if they aren't logically needed. (For example, you might need to "pump" a file reader, even if your data is coming from another source.)
- Initialization of the system may be tricky or laborious.
- Change to the control flow is impossible.
- The only escape from MonolithicDesign is to spend months refactoring and rewriting the system into independent modules.
Factors leading to
MonolithicDesign:
- A bad sense of aesthetics. (This above all.)
- Procrastination of refactoring.
- Premature optimization, especially a tendency to performance perfectionism or Puritanism.
- Not writing for reuse.
- Tunnel vision or attachment that limits your vision to one architecture, one flow paradigm, one memory management technique, etc.
How to prevent
MonolithicDesign:
- Code for survivability, not optimal fit. The more perfectly something is adapted to its environment, the less it can tolerate change in that environment. When you find yourself expending insane effort to maintain a perfectly static environment for your perfectly adapted code, you are probably dealing with MonolithicDesign. When you write modules that can be used independently from each other in varying architectural contexts, you are protected from MonolithicDesign.
- Refactor often, and focus on eliminating dependencies. Examine the relevance of every module that you are forced to use.
- Take advantage of opportunities to work with a variety of paradigms and techniques, so that you learn to recognize and eliminate unnecessary limitations in module functionality.
- Practice proactive laziness; i.e., expand your vocabulary, not just your repertoire. Developer 1 writes a program that must perform task X. Developer 1 writes the program and says, "Now I know how to write programs that do X;" he has expanded his repertoire. Developer 2 writes a program that must perform task X. Developer 2 writes a module to do task X, uses it in his program, and says, "Now I have a module that does X." Developer 2 has expanded his vocabulary, because now he can accomplish X by invoking the name of his module. When developer 1 needs to write a new program that does X, he will be tempted to tack the functionality onto his first program, bloating and complicating that program and starting the trend toward MonolithicDesign.
- ReduceCoupling
I don't see how this differs from "badly-written software" or "poorly-factored software" in general.
There's poor factoring, and there's insufficient factoring. MonolithicDesign is about failing to separate software into modules, or allowing software modules to grow together and become inseparable. There are many mistakes you can make when you factor software; MonolithicDesign is the mistake you make when you don't factor software.
I am still not getting the difference. Perhaps an example that distinquishes between poorly-factored or "bad" code and monolithic code would help. Why use a word like "monolithic" when "bad" will do?
Monolithic is a special type of bad. Let me give some examples of code that is bad, but not monolithic. Suppose you are writing a module for extracting data from a complex data source based on various sorting and filtering criteria. Here are some bad, but not monolithic, design choices you could make:
- Functional limitation:
- You could make it impossible to express some searches that your library is capable of performing, for instance, by not providing a way to recursively compose sorting criteria.
- Clumsy interface:
- You could provide only a single function with a string parameter, forcing the user to encode all queries as strings. Alternatively, you could force the user to create half a dozen heap objects to express a simple query, creating performance problems and ugly code.
- Unreadable interface:
- You could give your functions names like "DSSSHT" and "DSSREC".
- Unhelpful error handling:
- You could return arbitrary or empty results when you receive invalidly structured queries, instead of reporting an error.
- Obfuscatory logging logging:
- You could log tons of detail that is difficult to distinguish from important information.
- Bad performance:
- You could use inefficient search algorithms.
- Overspecialization:
- You could use heavily tuned search algorithms that only perform extremely poorly on 10% of your users' datasets.
- No portability:
- Your module could malfunction when compiled on 64 bit systems, because you assumed that int and void* are the same size.
- Unreliability:
- Your module could crash or corrupt memory when given incorrectly parameters.
- Unnecessary physical dependencies:
- (C/C++) You could make user code sensitive to your data structure sizes, forcing frequent recompilation of user modules.
- Inappropriate architecture:
- You could write your module as a web service or text filter, even though that provides no advantage over a dynamically linked library.
None of these flaws make your design monolithic, because none of them prevent your module from being used (however ineffectively) in other programs. If, on the other hand, you attempt to use your data access module in a different program, only to find that it depends heavily on the presence of a second module, which depends on a third module, which depends on two other modules, in a dependency graph that eventually encompasses all of the original program, then you have been stung by
MonolithicDesign.
Well, that is almost impossible to achieve because interfaces tend to make assumptions also. Trying to make something truly generic can be daunting, is not a free lunch, and gets into FutureDiscounting issues. See CategoryReuse.
I agree to a certain extent. You have to find a middle way between the Scylla of trying to turn every piece of code into an independentally valuable module and the Charybdis of creating a duplicate implementation of a complex function for every new project that needs it (and doing redundant maintenance on all those projects). Both are time sinks.
See also BigBallOfMud, PlayDohPrinciple, AllOnePiece
CategoryDesign