11/29/99
This abstraction emerged during Thanksgiving when we were trying to clean up weaknesses in the PartUser interface, which depended on Cell.findPart(String partName). The interface is:
public interface PartUser {
public String[] getPartNamesNeeded();
public void setPart(String partName, Object part);
}
The problem is the PartUser doesn't publish the full dependency. What interface is it expecting? How can we determine what the part needed should do, if many parts implement the desired interface? How can whoever is providing the part make sound decisions automatically and perfectly in a system of thousands of parts?
The problem starts with PartUser assuming knowledge of system structure, namely the part names in its container and above. We want to let system designers use any appropriate part names they want when assembling systems. This is crippled if they must satisfy zillions of subparts expecting certain part names to exist, be certain interfaces and do certain things.
One option is a "slot based" architecture where slots are named. It's the slot that PartUser needs refer to. The slots are a published dependency, available for all to take advantage of.
Another option is service style lookup, such as Jini uses. To summarize, a service holder part publishes it's service interfaces and "attributes" such as "Color=red", "MaxSize=10" and "Role=Cell". A service user part publishes what services it needs by specifying the needed interface(s) and attributes for each sercice. A "ServiceProvider" part then does service lookups by matching needs with available services. This is all very clean, flexible, and doesn't use part names at all. This is the tried and true Trader Pattern.
Part names should be only for human understanding of a system, and as internal IDs for identification use only. This is the same as the trap many fell into when using social security numbers for primary keys, or putting other data in keys. Better is global record IDs. In our case we use part names as a global id within a container.
Now let's examine a simple way to use service style lookups. After much examination of alternatives, we could use:
public interface ServiceUser {
public ServiceItems getServiceNeeds();
public void setServiceNeed(String id, Object service);
}
public interface ServiceHolder {
public ServiceItems getServiceOfferings();
public void getServiceOffering(String id);
}
public interface ServiceItems {
public void addType(String id, String typeName);
public void addAttribute(String id, String name, Object value);
public Enumeration enumerateIDs();
public Element[] getElementsForID(String id);
}
public interface Element { // Immutable
public String getKey();
public Object getValue();
}
ServiceItems is bolded to highlight the emerging pattern. The IDs are arbitrary but unique within a ServiceItems set. At least one type (an interface name) is required.
An Element value can be a String, Class object representing primitives, custom object, or another Element array. We expect most to be Strings, where wildcards are supported.
A ServiceItems can be built completely from DK, configuring a part to use the appropriate service for a reuse case. We have rejected an SQL style where clause as too confining.
The ServiceProvider, given a part inplementing ServiceUser:
1. Searches the container for parts inplementing ServiceHolder.
2. For each userID in the ServiceUsers ServiceItems, it checks to see if a holderID in the ServiceHolders ServiceItems satisfies (matches) the need.
3. The match is true if a needed set of ID Elements is a subset of an offered set of Elements. This explains why we sinplify descriptions down to Element arrays.
4. If true, getServiceOffering(holderID) is called on the ServiceHolder to get the actual service object. Then setServiceNeed(userID, service) is called in the ServiceUser.
5. If false, the remaining parts inplementing ServiceHolder in the container are examined for a match.
6. If still false, the search goes to the parent container.
7. If the root container is reached and no matches are made, the ServiceProvider can throw an exception if the need was required or call setServiceNeed(userID, null) to indicate a failed automatic hookup if the need was optional. (see below)
Note the symmetry of the interfaces. The needs and offerings are expressed with the same interface. (The addition of setOptional(String id, boolean isOptional) to ServiceItems would be for ServiceUsers only, and would destroy perfect symmetry. It could possibly be used for ServiceHolders to indicate a service may be dynamically unavailable, but this is weak.)
One weakness is the ServiceUser Elements are assumed to be all required. There's no "or" feature. Another weakness is no dynamic service lookup, when needed, using criteria built on the fly. A further weakness is service replacement, unavailability, change of attributes, etc is not handled. A future design will fix these.
As you can see, this is simple, clean, flexible, and elegant. But what's really happening here? Why does this low level design solve the problem so well?
The answer is that ServiceItems represents attactors present in the system. If an attraction is strong enough (a match is true) the two parts "snap" together. The system becomes self assembling in a small way. It's as if we threw a bunch of car parts into a box, and they built themselves into a Ford....
The ServiceItems interface is better named ServiceAttractors.
Attractors are much like the forces in molecular bonds. Attractors represent the way people think, when they wonder, "What's the best hookup here?" Attractors allow extreme richness of Visual Tool design.
Attractors as an abstraction can appear many places in UHR. For example:
1. Automatic part service hookup as described.
2. Automatic message hookup using the same mechanism. However some may need to be manual, especially multicasts.
3. The system assembler is working on a container. They're not sure if a certain part is a good choice. They drag the part over the container. It glows brightly if a good fit. The assembler could then drop it into the container, and hear a satisfying "snap" as the automatic hookups occured. It glows only a little if a poor fit, or not at all if a bad fit.
4. The system assembler is not sure how to best organize a system tree that's in progress. They click on a part. Lines emerge from it to other parts showing the attrractors involved, or to special icons for unsatisfied attractions. Line width or color shows attraction strength and resolution. They drag the part to various containers to see where the attractors are the best, and leave the part in the container showing the optimum attractors.
5. A container(s) needs a part. The assembler has no idea which of 250,000 parts in inventory are good candidates. They have the inventory split into 10 PartShops. They drag a PartShop over the container. It senses the attractors present, and pops out one or more parts to satisfy those attractors. Those new parts have a special color.. The assembler "sprinkles" parts out of the PartShop on a few containers, and then examines each of the new parts for a good fit as described in (4).
6. The upper containers of a system has been created. The system now wants to grow itself. Using downward attractors it negotiates with PartShops to populate lower containers. These new containers in turn do the same. The system grows organically from a minimal start, much like animals and plants grow from a very small beginning.
7. A part is ill. It may have errored out, become corrupt on disk, not have been used for a long time and discovered to be obsolete, etc. The system needs a replacement quick. Using the attractors for the sick part, it walks the available PartShops and attracts a dozen replacement candidates. It measures the total strength of attraction for all, and uses the best. Surprise! That one becomes ill in use immediately. It's replaced with the second strongest attraction part, which does just fine. The PartShop is notified about the two sick parts.
This points to the need for an "Attractor" layer. Its responsibility is automatic hookups. I wonder if it's possible to design parts and systems to have 100% automatic hookups and still make the parts highly reusable?
Well, that's the idea. How exciting!!! :-)