The Source for Java Technology Collaboration
User: Password:



   

Developing Clients with Simulated Servers Developing Clients with Simulated Servers

by Jonathan Simon
10/19/2004


Contents
Motivations
Our Sample Application
Encapsulate Server Communication
Introducing Endpoint Interfaces
Write the Stubs
Make Initialization Logic Configurable
Controlled Environment
Comparison
Wrap-Up
Resources

One of the difficulties of developing clients for multi-tiered systems is coordinating client and server development. Client and server codebases need to be in sync for shared objects and all servers have to up and running. On one system I built, we counted a total of 15 separate servers that all had to be up and running to completely test the client. To make matters worse, not all of those servers were under our control. Sometimes servers would be down for an upgrade that was expected to take an hour, but lasted a day or more. And we didn't have separate environments for integration and development. I spent entire days waiting for environments to be up and running, wasting precious time on an already late project.

I could go on all day about how environments could be set up to avoid some of these issues, but that's not the point. We as client developers have to be able to insulate ourselves from server development and maintenance. In this article, we'll explore a method for simulating servers, often called stubs, to run a minimal implementation of the server locally. This will allow us to develop and maintain clients without relying on the state of the servers.

Motivations

Before we get started, let's take a minute to list out the motivations for writing server stubs.

  • Develop and maintain client code when servers are unavailable: This allows us to work when servers are down for upgrades or environment changes.
  • Develop and maintain client code before servers are written: This allows us to develop clients as the servers are being written or changed. As long as an interface is agreed upon, the client should be able to be written exactly as it will run with the servers. This also covers the case where client and server codebases are incompatible. As long as we know what the changes are, we shouldn't have to wait for the new server to be up to develop.
  • Client code should have no knowledge of the stubs, or whether it's connected to stub servers or remotely: This allows us to completely remove the test code from the release. Just as we don't want any special code to run unit tests going into production, we don't want special stub code going into production.

Our Sample Application

We will use a sample application throughout the article for context. See the Resources section below to download the complete source code. Much like my previous article on GUI simulators, the example involves a secure application to view contact information. Here are the main model components (we're not concerned with the view here):

  • AuthenticationRemote: Communicates with the authentication service to verify login information.
  • ContactInfoRemote: Communicates with the contact information service to load data.
  • ApplicationModel: Used to store shared references to the remotes.
  • InitializationManager: Responsible for all client initialization.
  • Main: The launch class.

Now let's start to take a look at moving to a stubbed server approach.

Encapsulate Server Communication

Before we begin developing stubs, we have to prepare the client. The technique that we'll use for writing server stubs is based on using a service-oriented architecture (SOA). In service-oriented architecture, each business area has its own service or server on the server side. The client has a local counterpart for each of the services, which we'll call a remote. The rest of the client (screens, models, and any other client components) speak only with the remotes. There should be no direct communication with a server anywhere else in the client. In addition to helping stub the services, this type of encapsulation is just a plain good idea. It isolates the rest of the client from remote transport technologies such as RMI or JMS. Figure 1 shows how our example application is a service-oriented system.

Figure 1. Service-oriented architecture
Figure 1. Service-oriented architecture

Notice how the separate screens can call any of the remotes, but never call the servers directly. Also, notice the one-to-one correspondence between the services and remotes.

Introducing Endpoint Interfaces

So far, we have remote classes encapsulating server communication logic and acting as a central, controlled location for server access. This combination or responsibilities makes it an ideal location for introducing a layer of abstraction. If we introduce an interface for each of the remotes, we can have one implementation communicating with the server and one implementation executing locally as a server stub (shown in Figure 2).

Figure 2. The UML template showing the endpoint / remote / stub relationship
Figure 2. The UML template showing the endpoint/remote/stub relationship

Let's use the authentication remote as an example of how to migrate to the indirect remotes. Here is the code for the AuthenticationRemote:

  public class AuthenticationRemote {
     public boolean isLoginValid(String userName,
                                 char[] password) {
        //Make server call
        //return result
    }
  }

It's a pretty simple class, so introducing an interface should be fairly easy. But even if it were more complicated, refactoring IDEs like IDEA and Eclipse can do this for you automatically. I used the "extract interface" command in IDEA and ended up with a new interface called AuthenticationEndpoint, which is automatically implemented by AuthenticationRemote. The interface just has the one isValidLogin method. Here is the code for the new AuthenticationEndpoint interface and the updated AuthenticationRemote code.

  public interface AuthenticationEndpoint {
     boolean isLoginValid(String userName, char[] password);
  }
  
  public class AuthenticationRemote implements AuthenticationEndpoint{
     public boolean isLoginValid(String userName, char[] password) {
        //Make server call
        //return result
    }
  }

Pages: 1, 2

Next Page » 

Related Articles

Developing Swing Components Using Simulators
It's difficult to expose GUI components to testing, and in the worst case, tightly coupled components aren't seen or tested until their surrounding application is ready. Jonathan Simon says there's a better way, and it's called the "simulator."

View all java.net Articles.

 Feed java.net RSS Feeds