DA Tutor

Overview

DA Tutor (tentative name) allows learners (users) to graphically create a distributed application (DA).

Using drag-and-drop, the learner builds a network of elements (components and interfaces) connected by arrows (depends and implements arrows).

The learner can view his distributed application at varying degrees of detail. Technology views might show the supporting classes needed by the underlying transport protocol. For example, if RMI is used, the technology view might show stubs, skeletons, and registries. Implementation views might show the actual source code, suitable for deployment.

The user can also simulate the exchange of messages between components.

Requirements Model

Use Case Elaborations

Run Simulation

This needs to be spelled out.

Demonstration: ATM

Using drag-and-drop, pre-defined components, and dialog boxes, the user creates a UML component diagram representing some distributed application:

Here is an alternate UML notation using sockets and lollipops:

Internally, DA Tutor represents this DA as follows:

public class BankingDemo {
   public static void main(String[] args) {

      // create interface Transaction:
      Operation getBalance =
         new Operation("getBalance", Double.TYPE);
      getBalance.add(new Parameter("acctID", Integer.TYPE));
      System.out.println(getBalance);
      Interface transaction = new Interface("Transaction");
      transaction.add(getBalance);
      System.out.println(transaction);

      //Create a component that implements transaction interface:
      Component bankServer = new Component("BankServer");
      bankServer.provides(transaction);
      System.out.println(bankServer);

      // create a component that requires transaction interface
      Component atm = new Component("ATM");
      System.out.println(atm);

      // create & display a distribute application:
      DistributedApp banking = new DistributedApp();
      try {
         banking.connect(atm, bankServer, transaction);
      } catch(DAException dae) {
         System.err.println(dae);
      }
      banking.display();
   }
}

Program Output

Here's the output produced. Of course ultimately DA will have GUI views of the output.

getBalance( acctID: int ): double;
interface Transaction {
   getBalance( acctID: int ): double;
}
component BankServer {
   required interfaces:
   provided interfaces:
      Transaction;
}
component ATM {
   required interfaces:
   provided interfaces:
}
Clients:                Services:
ATM             [<Transaction in BankServer>]

What's missing

DA Tutor also needs to generate Java code for the components that uses real RMI.

Design Model

DA Tutor uses a standard 3-layered architecture. Note one-way dependencies:

For now the persistence layer uses Java serialization to save the DA and its components to a file.

Domain Model

A DA manages a table of services indexed by the components that use them. A service is simply an interface together with an implementing component. An interface is a collection of operations (methods without implementations). An operation has a name, return type, and a list of parameters:

Implementation Model

class Interface

An interface is a named collection of operations:

class Interface implements Serializable {
   private String name;
   private List<Operation> operations;
   public Interface(String name) {
      this.name = name;
      operations = new ArrayList<Operation>();
   }
   public Interface() { this("IUnknown"); }
   public void add(Operation op) { operations.add(op); }
   public String getName() { return name; }
   public Iterator<Operation> iterator() {
      return operations.iterator();
   }
   public String toString() {
      String result = "interface " + name + " {\n";
      for(Operation op: operations) {
         result = result + "   " + op + "\n";
      }
      result += "}";
      return result;
   }
}

An operation is a method without an implementation. It consists of the name of the method, the return type, the parameter list, and a list of exceptions that might be thrown:

class Operation implements Serializable {
   private String name;
   private Type returnType;
   private List<Parameter> parameters;
   //private List<Type> exceptions;
   public Operation(String name, Type type) {
      this.name = name;
      returnType = type;
      parameters = new ArrayList<Parameter>();
   }
   public Operation(String name) { this(name, Void.TYPE); }
   public void add(Parameter p) { parameters.add(p); }
   public Iterator<Parameter> iterator() {
      return parameters.iterator();
   }
   public String getName() { return name; }
   public Type getReturnType() { return returnType; }
   public String toString() {
      String result = name + "( ";
      for(Parameter param: parameters) {
         result = result + param + " ";
      }
      result = result + "): " + returnType + ";";
      return result;
   }
}

A parameter consists of the name of an operation input plus its type:

class Parameter implements Serializable {
   private String name;
   private Type type;
   public Parameter(String name, Type type) {
      this.name = name;
      this.type = type;
   }
   public String toString() {
      return name + ": " + type;
   }
}

class Component

A component is a self-contained piece of a distributed application. A component has an external view, an internal view, a manifestation, and a host. Web services, EJBs, Java beans, and servlets are examples of components.

The external view of a component consists of its required and provided interfaces.

The internal view of a component is a collaboration: the set of classes that work together to implement the provided interfaces.

A manifestation of a component, called an artifact, is the file that contains the component. In Java this is usually a .jar file.

A manifestation is deployed on a host which is a software or hardware platform. Examples include J2SE, J2EE, .Net, etc.

class Component implements Serializable {
   private String name;
   private Set<Interface> providedInterfaces;
   private Set<Interface> requiredInterfaces;
   // private Collaboration implementation;
   public Component(String name) {
      this.name = name;
      providedInterfaces = new HashSet<Interface>();
      requiredInterfaces = new HashSet<Interface>();
   }
   public void provides(Interface intf) {
      providedInterfaces.add(intf);
   }
   public void requires(Interface intf) {
      requiredInterfaces.add(intf);
   }
   public boolean isProvided(Interface intf) {
      return providedInterfaces.contains(intf);
   }
   public boolean isRequired(Interface intf) {
      return requiredInterfaces.contains(intf);
   }
   public Iterator<Interface> requiredIterator() {
      return requiredInterfaces.iterator();
   }
   public Iterator<Interface> providedIterator() {
      return providedInterfaces.iterator();
   }
   public String getName() { return name; }
   public String toString() {
      String result = "component " + name + " {\n";
      result = result + "   required interfaces: \n";
      for(Interface intf: requiredInterfaces) {
         result = result + "      " + intf.getName() + ";\n";
      }
      result = result + "   provided interfaces: \n";
      for(Interface intf: providedInterfaces) {
         result = result + "      " + intf.getName() + ";\n";
      }
      result += "}";
      return result;
   }
}

class Service

A service is my own invention, I think. It is an interface bound to a component that implements the interface.

class Service implements Serializable {
   private Component server;
   private Interface intf;
   public Service(Component server, Interface intf)
   throws DAException {
      if (!server.isProvided(intf)) {
         throw new DAException("Interface mismatch");
      }
      this.server = server;
      this.intf = intf;
   }
   public Component getServer() { return server; }
   public Interface getInterface() { return intf; }
   public String toString() {
      return "<" + intf.getName() + " in " + server.getName() + ">";
   }
}

class DistributedApp

A distributed application (DA) is a network of client components connected to services. Of course a server component that implements a service interface may be the client of some other service.

The links in the network represent communication links. These communication links may be implemented by a variety of communication protocols including RMI, CORBA, HTTP, UDP sockets, TCP sockets, or SOAP.

Since a distributed application is a network, I have elected to represent a DA using the standard table representation of a directed graph. In this representation a table key is a client component, and the corresponding table value is the set of services that the client uses.

class DistributedApp implements Serializable {
   private Map<Component, List<Service>> connections
   = new Hashtable<Component, List<Service>>();

   public void connect(
      Component client, Component server, Interface intf)
      throws DAException {
      List<Service> services = connections.get(client);
      if (services == null) {
         services = new ArrayList<Service>();
         connections.put(client, services);
      }
      services.add(new Service(server, intf));
      client.requires(intf);
   }

   public void display() {
      System.out.println("Clients:\t\tServices:");
      for(Component client: connections.keySet()) {
         System.out.print(client.getName() + "\t\t");
         System.out.println(connections.get(client));
      }
   }
}