A few decades ago, in this very galaxy, the first computer languages and compilers were written, enabling developers to create programs faster and in comfort. Quickly, quite a lot of programs were created. The more observant programmers could not unsee that many problems repeated across those programs. The same solution to the very same problem was written again and again. The solution was simple -> write the repeating code once and only use references in the new code that was actually different and solved something new. For example, GUIs: Many programs spawn new windows with forms inside them. Therefore, the code to create GUI elements was separated and reused.
However, that is not the end to our problems. As every developer knows, for a program’s instructions to be executed, they must be loaded into memory from a persistent state. Then they go into a CPU’s memory and are then executed by the CPU. So, developers were no longer writing repeating parts again and again, but those parts still had to be shipped with the code, because the resulting instructions were, of course, required for the program to run correctly. If the same example with GUI libraries is used, the library had to be become part of the resulting program translated by the compiler into something executable by the target machine. So, at compile time — actually, in the last phases of compilation — those libraries were included in the resulting “executable program package.” Such programs could be distributed.
Of course, distributed, but with all the libraries. There was one computer, one operating system, and many programs — and each and every one of them had all the libraries packed within. When those programs (in fact, parts of them) were loaded into memory to be executed, the same libraries were loaded over and over again. Back then, and sometimes even now, memory was precious. Way back, even hard drive space was precious. Thus, one more effort was made:
- Not to distribute all the libraries with the programs, but rather have them “downloaded” once in the whole system.
- Not to load all the same libraries many times into memory, but rather load the shared code once for other programs to use it.
These are, very briefly, basics of how programs and their distribution evolved. It has a lot to do with operating systems and the process of compiling the programs. If you would like to know more, there are several great books to start: Operating Systems: Principles and Practice and Compilers: Principles, techniques and tools.
Nowadays, for common business logic, there is plenty of memory and even more space on persistent storage. Also, the problem is not as shallow as I just presented it. If many libraries are pre-loaded in the system, those libraries may end up unused. Also, shared code has versions. Each program may require a slightly different version of “the same” library. Anyway, things cannot be worse than bundling everything everywhere, but with the techniques mentioned above, chances are that memory and disk space is saved.
These are some very basic concepts that even moderate people are fully aware of, and I even guess some readers may be offended at this point. But fear not! This introduction serves as a stepping stone for further reading.
Java EE and Reusing Code
In the Java world, we have a variation of this problem. Every Java program requires a JVM (native code can still be created). So, there is one recent JVM on the machine and all the Java bytecode is run by means of this one single JVM. The JVM is not distributed with each program. The story is the same with Java EE. Java EE represents a common effort of many vendors to standardize commonly used functionality and separate it from the actual business logic. Everyone is, of course, free to create the implementation, but that freedom solves different kinds of problems that the software industry faces time and again. In the past, such common functionality, represented by separate JSRs, was bundled into a package called the application server. There are many good intentions behind an application server:
Run once per environment, therefore only one runtime is started.
Deploy many applications to it, therefore all the libraries are loaded into memory only once.
Services are loaded lazily, so unused parts reside on the HDD, but no attempt to occupy RAM is made.
Curated package: well-tested.
Used by many: most of the bugs are discovered and fixed quickly.
Professional support available.
Applications use databases, web services, REST services, dependency injection… for every JSR, there is a place. In addition, the standard specifications and the implementations were made by better people than the “average Joe developer,” which I also consider to be a plus.
Code reuse is something every developer faces. All frameworks, not only Java EE, provide ways to reuse both design and code at the same time. The motivation of reusal, as previsouly mentioned, may vary, whether it's saving space on persistent storage, saving space in RAM, or saving time and avoiding mistakes during development.
The next chapter is going to describe how modern applications in the enterprise Java environment are distributed and how it impacts the way of packaging them and reusing common parts in the code.