Challenges of Distributed Application Development
Developing distributed applications is challenging—there’s no doubt about it. Although this section focuses on the challenges that a distributed application technology must meet, it also presents some of the challenges that a distributed application developer must overcome.
A number of factors can affect the performance of a distributed application. Some examples are factors outside the software system, such as network speed, network traffic, and other hardware issues local to specific machines, such as CPUs, I/O subsystems, and memory size and speed.
Given the current state of distributed application technologies, performance and interoperability are mutually exclusive goals. If your distributed application absolutely needs to perform as fast as possible, you usually have to constrain the application to run inside the firewall, and you have to use the same platform for both client and server. This way, the distributed application can use an efficient network protocol such as TCP and send data by using a proprietary binary format. These formats are far more efficient that the text-based formats usually required for open-standard support.
Assuming a distributed system’s hardware (including the network) is optimally configured, proper coding techniques are essential to a scalable high-performance application. The best optimization is to avoid making distributed calls wherever possible. This optimization is usually referred to as the chatty vs. chunky trade-off. Most traditional object-oriented programming techniques and texts focus on designing elegant solutions to common programming problems. These solutions are usually most appropriate when collaborating objects are very close to each other (probably in the same process, or with .NET, in the same application domain).
For example, if you’re working with a partner who sits beside you in the office, you two can chat as often as you want to solve problems. You can bounce ideas off each other, change your minds, and generally talk throughout the workday. On the other hand, if you’re working with a buddy who lives on another continent, your work style needs to change dramatically. In this scenario, you do as much work as possible on your own, check it carefully, and try to make the most of your infrequent communication with your partner. Working this way isn’t as elegant as working with a partner who sits beside you, and it requires you to learn a new approach to stay productive. For efficiency, you wind up sending back and forth bigger chunks of work more infrequently when distance becomes a factor.
Thus, if you’re using local objects, you can perform tasks such as the following:
- Use properties at will
You can set the state of an object by setting many properties on it, each of which requires a round-trip to that object. This way, the client has the flexibility to change as few or as many properties as a scenario requires.
- Use callbacks at will
Because the communication time of local objects is negligible, an object can walk a list of client objects and call them even to update a trivial piece of status information. These client objects can call each other to update and retrieve any information they want, without too much worry about a performance hit.
If you’re using remote objects, you need to adhere to these stipulations:
- Avoid heavy use of properties
Instead of using many properties, you should set remote object state by combining a number of these properties as parameters to one or a few methods. In fact, some very scalable distributed applications have methods with large parameter lists that seem ridiculous for local calls.
- Think carefully about callbacks
In the interest of avoiding expensive remote calls, many callbacks could be aggregated into a single or a few method calls with many parameters.
The bottom line is that good remote application design can frequently seem like poor object-oriented design. You simply can’t apply every local object metaphor to distributed object scenarios without considering performance.
Of course, you should always avoid writing sloppy, wasteful code. Thus, it’s good object-oriented practice for objects to limit their communications where appropriate. Because of the chunky vs. chatty trade-off, scalable remote object design frequently means avoiding many patterns you might have grown accustomed to when dealing with local objects.
No aspect of distributed systems has gotten more attention lately than security. With the increasing exposure of company networks and data to the Internet, the focus of security will only grow. To be considered secure, a distributed application needs to address three main security areas:
Servers need a way to make sure the client is who it says it is.
After the server authenticates the client, it must be able to secure the communications.
- Access control
After the server authenticates the client, it must be able to determine what the client can do. For example, what operations can the client perform, and what files can it read or write?
DCOM provides strong support for authentication, cryptography, and access control by integrating closely with the Microsoft Windows NT security system. Although DCOM offers a robust and comprehensive security model, in practice, implementing DCOM security effectively is far from straightforward. When the complexity and scope of DCOM solutions encompass solving real-world problems, configuring security can become quite difficult. Because security is so critical to distributed applications, implementing it needs to be foolproof and as simple as possible.
Interoperability and Wire Formats
Most distributed application technologies including DCOM, CORBA, and Java RMI have their own proprietary wire format that’s usually designed with performance in mind. A few years ago, interoperability wasn’t nearly as important as staking out territory and possibly achieving vendor or technology lock-in. Some third-party “bridge” implementations have attempted to help the locked-in organizations talk to the other side. But none of these solutions are as seamless and easy to use as having the interoperability support baked into the various distributed application technologies.
The Internet and Firewalls
Most of the popular distributed application technologies were originally designed to operate over private networks. Even though the public Internet has been around for years, until recently, its use was mainly confined to file transfer, e-mail, and Web servers delivering HTML pages for viewing. Most people didn’t use the Internet as a network for running distributed applications. Over time, companies started protecting their internal networks from all traffic other than HTTP, usually only over port 80. It’s probably safe to say that, at this point in history, the majority of all client connections are over HTTP. It’s not that HTTP is an efficient protocol. It’s simply a convention that evolved because of the popularity of the Internet.
Legacy wire formats and protocols usually require exposing unsafe ports through firewalls. In addition, these formats and protocols tend not to restrict their communications to a single port but to several ports or to ranges of ports. The general feeling in the security community is that configuring firewalls to allow this kind of traffic essentially defeats the purpose of the firewall.
Thus, the next step for companies was to bridge private networks with HTTP. This task was and still is accomplished by tunneling a proprietary wire format over HTTP, writing a custom client and server to pass traffic, or relying on traditional Web servers to handle that hop. None of these alternatives is too attractive. They are labor-intensive, error-prone patches for wire protocol limitations.
This situation has made the use of such proprietary formats unsuitable for the Internet. Like it or not, the industry standard for getting through a firewall is to write distributed applications that communicate by using HTTP.
Real-world distributed applications are usually quite complex. You have to control a number of factors just to enable remote communication, much less get any real work done. These variables include endpoint management, activation policies, security settings, and protocols. A number of configuration techniques have appeared in various distributed technologies, but it is now widely accepted that these systems should allow configuration both programmatically and administratively.
DCOM, for example, supports programmatic configuration access through the COM API. Unfortunately, DCOM configuration information is dependent on the registry for storage. Because editing the registry is error prone and dangerous, Microsoft supplies the Dcomcnfg tool to enable easier editing of DCOM configuration information. Even with this tool, using the registry to store distributed application configuration information makes deployment difficult because configuration requires human intervention or installation scripts.
All modern distributed object technologies have facilities to make a remote object appear as though the object is local. This is an important goal of distributed systems because it allows server objects to be moved or replicated without making expensive changes to the calling objects.
Object Lifetime Management
Networks are inherently unreliable. Client applications crash, users give up, and the network can have periods of unavailability. Precious server resources can’t be held any longer than necessary, or scalability will suffer and the hardware requirements to support a given load will be unnecessarily large. Distributed application technologies need to provide ways to control object lifetimes and detect client failures so that server objects can be removed from memory as soon as possible.
DCOM’s solution to object lifetime is based on a combination of pinging and client-managed reference counting. Unfortunately, placing responsibility for server lifetimes on the client usually forces programmers (at least C++ programmers) to pepper their code with calls to AddRef and Release. As with managing security, keeping track of AddRef and Release pairs gets increasingly difficult as interfaces are passed between objects and the number of objects and interfaces grows.
Dealing with multiple interfaces from different objects, passing their references to other objects both local and remote, vigilantly calling Release in error scenarios, and avoiding calling Release at the wrong time are common problems with complex COM applications
Although reference counting takes care of keeping the server alive, you need a way to detect clients that have failed before releasing the server references they were holding. DCOM’s solution for detecting client failures is to use a pinging mechanism. Although pinging (or polling) the client periodically increases network traffic, DCOM’s pinging mechanism is heavily optimized to piggyback these pings onto other requests destined for the machine.