In the early days of computing each application interfaced directly with the underlying hardware as shown in the diagram to the right. This had the advantage of speed and control. Each application would reserve memory addresses and task the CPU or other hardware devices, like communications ports. As computers developed, the need to handle multiple instructions at the same time developed; first on mainframe and large computers and then on personal computers. On PCs, this was the era of Disk Operating Systems (DOS) like MS-DOS (Microsoft’s version), PC-DOS (IBM’s), AppleDOS (Apple’s), etc. When a user launched a program from the operating system, the operating system launched the application, who dealt with memory and hardware devices directly. One large disadvantage of this type of system lied int he control of memory and the stability of systems. In order to program efficiently, programmers had to “reserve” memory with the OS and handle memory transactions themselves. With interpreted languages, the interpreter generally handled that for the programmer, but in an inefficient manner. For compiled languages, the programmer had to issue these directives themselves. Sloppy programming or incorrect programming could lead one program to read (address) memory that another program reserved. This leads to changes in the computer’s memory unbeknownst to the owning program. As you can imagine, this leads to programs failing or behaving in an unexpected manner.

With the advent of multi-tasking on the PC platforms this paradigm was obviously flawed. Apple and Amiga were one of the first to really stress hardware-independence. Rather than directly addressing hardware devices, these companies stressed programs request access to the device from the operating system. In this manner, the programmer needed to know less about each device and the OS could ensure that the same device wasn’t in use at the same time by two programs. Whereas the Microsoft operating systems labored under a virtual wild, wild west of application developers and hardware platforms, the MacOS and AmigaOS were limited to the hardware developed by their parent companies. This allowed these platforms to virtually eliminate “raw” access to devices and force programmers to work through the OS. The clear disadvantage with this system is the speed and control of access. Where this showed up the most was in real-time applications, like machine control systems and health systems. For this reason, PCs became the system of choice for real-time applications. As Windows grew up, however, Microsoft began stressing the same concept and with the advent of the re-written Windows (called Windows NT for New Technology), they introduced something called the Hardware Abstraction Layer or HAL. This layer of code acted as an intermediary between applications and hardware devices. Though programmers can work around this layer (as they can on the Macintosh as well), it has become exceedingly difficult. Another large advantage of this change was first realized by Apple and to a much lesser extent by Microsoft in later generations (and still not really realized on Unix platforms). By forcing developers to embed calls to the OS for device access, the operating system vendor was forced to write standard routines for typical programmer actions: writing to a file, saving a file, opening a file, printing a file. Apple took advantage of this and not only included an Application Programming Interface (API) for opening a file, but it also created a way (called a “call”) to request what they called the “Standard Open File Dialog Box” or the “Standard Save File Dialog Box”. This allowed Macintosh programs to all work with the same look and feel creating a user experience that was the same regardless of the application used. It took Windows about a decade to reach this point (and some woudl argue they still haven’t reached the same level as Apple).

As computers developed more and more power and PCs began to dominate the workforce, the management of these devices began to become a problem (one that still has not totally been solved!). If 1,000 users needed access to a corporate database across 5 buildings, then the applications on all 1,000 machines had to communicate to the database server. This meant a heavy network load on a relatively immature networking technology. Stability and responsiveness became an issue, forcing many businesses to stay on older mainframes where this was less of an issue. Unix and VMS were invented as powerful workstations for the scientific community. One need of this community is an efficient manner to send data back and forth. Several methods were created to do this and these platforms had this networking ability built into them out of the box. The internet was built on the backs of Unix’s networking ability. The rise of Unix was built around the likes of Sun and DEC. It wasn’t long before people started to centralize processing on Unix machines and force them into mini-mainframes, but without the large buy-in price and even larger on-site labor support costs. However, the Unix OS is not very user friendly. (I call is the only actively user hostile OS invented!). Clearly there needed to be a way to connect users on PCs to mainframe databases (or other Unix databases).

The solution was something called a 3-tier application. Later came 4-tier applications, and finally, they all were called n-tier applications. With this model an application on the client machine connects to a server and requests data. This server then connects, in turn, to the data server; processes the data, and then returns it. The three tiers were called the presentation layer, logic layer, and the data layer. If you think about a program ins the abstract, you’ll be able to see why this is a compelling way to think about programming. On the presentation layer, the programmer doesn’t care how the data is stored or what the business rules to the data are. They are simply presenting the data to the user and requesting the user respond to the presentation; while the logic layer programmer could care less how the data is presented or responded to. In this layer, the programmer concerns themselves with the rules of the business. Who can access the data? If there is a change in one piece of data, what things must occur? Furthermore, at the data layer all the programmer is concerned with is what needs to be stored and to obtain requested data. This model serves as a way for projects to be split along fairly well understood lines of demarcation, while at the same time allowed for simpler network communications. In the example above, we still have 1,000 people connecting to the Logic Layer and using an application to have the data presented.

Typically, however, the presentation of the data won’t change drastically and, so, the presentation layer doesn’t need to be modified too often. The heart of the application lies in the logic layer, which can be changed with little impact to the other layers. The other large advantage of this model was derived form the old mainframe days. One reason mainframes continued for so long (and still continue) is that the data needs a “asymmetric”, meaning the data needs going one direction differ than the data needs going in the other direction. Users typically only need a small amount of data displayed. On a mainframe terminal this was a screen of 80 characters wide and 30-50 lines long. However, the user’s request may generate a large amount data being sent from the data layer to the logic layer. For example, I may request the names of all students at PSU. This is a list of nearly 85,000 people. This pales in comparison to the 40 or so that I can look at on one screen. The n-tier application thus reduced the networking needs form the end user where networking speeds where the smallest. (At the time, think 300, 600, and 2400 baud [bits per second, kind of] modems).

While this innovation has radically changed the modern programming world, it didn’t come without some issues. Though I won’t go into all of them right now (security becomes harder, for example), one important one for the beginning web developer is one of application scope. Going back to my original description of a programs execution, the program has access to the hardware devices, through the OS, of the platform on which it executes. This means that programs running in the Logic Layer cannot access the devices on the Presentation Layer. For example, the Logic Layer cannot print a document. Only the Presentation Layer can. If the Presentation Layer doesn’t have all the data, then it must repeatedly ask for all the data. This is a relatively easy problem to solve and won’t vex a web developer much. What will vex them, however, is the state and value of environment variables. One such variable that caused me some deal of pain was the date and time of execution. Assuming everyone is in the same time zone, this shouldn’t be a problem. However, many users (network time wasn’t common then) had their clocks set incorrectly. The client computer sent the request to the server and the date/time was set from the client computer’s clock. The server requested data and created a certificate (x.509 PKI certificate, if anyone cares!) based on the server’s time. When we tried to use the certificate on the client we found that it failed because it was not valid yet. After we looked through everything we realized the client’s date was sent incorrectly. The server dutifully did its job and the creation of the certificate was accomplished successfully. The fault lie in the date/time of the client. This could cause havoc to an enterprise if it was allowed to continue in the manner. We realized that we had to create certificates based on the client’s time. In order to do this, however, we needed to pass this value from the client to the server with the request. Though this was a simple issue, it will crop up repeatedly. Always fall back on the n-tier model and understanding the scope of the program you are writing.