When do you define interfaces for a distributed system?
David, I left your initial comments in-tact and added a pattern stub up top. Great job on hatching this one! --PhilipEskelin
When do you define interfaces for a distributed system?
In some recent work, I put a service together as follows:
- Core abstractions (rule-based routing/pricing of trades). Developed very quickly using (solo :-( ) XP, this is still the core code. The tests mean I'm sure that it works, whatever changes: it's easy to extend the rule classes
- Persistence. A separate package (yep, it's Java) with minimal support for persisting these things (which is actually pretty transparent as we've used an OODB). All the unit tests run from here work against persistent versions of the rules
- Finally, turning this into a service involved defining a service information model (objects are not distributed at the grain of individual rules) and writing a (thin) translation layer between a CORBA interface for this model and the core abstractions. Transactional support lives here too.
Looking at code for which the distribution interface came first, I see lots of confusion: distribution, persistence and core code all mixed up, because the developer has worked in
from the interface (which is an artefact of
delivery) rather than out
from a clean model.
seems (to me) to be an obvious way of approaching distribution, and a good candidate pattern. It's one way of trying to keep distribution interfaces amenable to change (I'm confident in the above system that I could change the interfaces - in clients and servers - almost as quickly as I could change the code). Defining interfaces first needn't lead to the sort of tangle I've described, but it's harder - you have to forget the interface for a while.
Not only have we seen this to be true, but we found it in the same domain and technologies (options trading, CORBA and Java). We did things in the following order:
(1) Design a clean model (we happened to use UML and Rose with CRC and Use Cases)
(2) Prototype the model (rapid prototyping similar to XP) -- the idea was to get to a level of granularity that most things were extremely well defined.
(3) Design the interfaces (They were all instances of the FacadesAsDistributedComponents
pattern) for distribution.
(4) Refactor the model so that it fits cleanly into the new services interfaces designed in Step 3.
I'd like to nominate this one for inclusion in ComponentDesignPatterns
I concur! Interfaces always change along with requirements during the project lifecycle. Can't wait to start filling this one in...see changes above...
I find it the other way round, at least for single-address-space frameworks. That is, I define AbstractInteractions
, encode them as interfaces and then define components that play roles in those interactions.
Or are we in agreement? Your steps (1) and (2) seem to be about discovering and refining the AbstractInteractions
. You then encode them in step (3) and define components that use them in step (4).
Perhaps this pattern is a form of, or follows on from, or leads into, AbstractInteractions
Also, I'd say that the problem could be generalised to "when do you define interfaces between components?"
Nat, I find that the two differ. The way you've defined it for single-address space systems is basically what KenAuer
and I were talking about in BuildInterfaceImplementationPairs
. That really is about defining the abstract interactions. However, the problem that DavidHarvey
pointed out occurs when you try to do this in multi-address space systems. In that case, the "big" objects that you WANT for distribution become monolithic, huge "God" objects. The idea of "design once without distribution in mind, using AbstractInteractions
as necessary, then refactor that first pass with distribution in mind" seems to cover both parts.
Nat and Kyle, your viewpoint contrast tells me that defining ProcessBoundary
early in your project lifecycle -- determining which components are in domestic space and which ones are in foreign space -- allows you to know what interactions deal with foreign space components.
Hence, a resulting context aspect of ProcessBoundary
would be utilizing AbstractInteractions
with domestic component interaction, and GoingThroughCustoms
and developing InterfacesLast
with foreign component interaction.
If this pattern is about distribution issues then I think it needs a different name. AbstractInteractions
are not limited to a single address space, and the term "interface" does not necessarily mean an IDL interface providing TransparentDistribution
. One can codify AbstractInteractions
in terms of Java interfaces or Java Remote interfaces, C++ abstract classes, CORBA IDL, MIDL or whatever.
This pattern seems to be about delaying the design of component interactions between address spaces until as late as possible, while ProcessBoundary
seems to be advocating making those decisions as early as possible. I think that fleshing out the forces of the two patterns is necessary before we can compare the two.
Nat: Fine, I'll buy your thinking that we should flesh out forces more. But ProcessBoundary
is focused on the problem of "where do my components go?" and making that decision early. You're thinking about space. And in making that decision, you are not coupled to immediate or delayed definition of interfaces or explicitly addressing interactions. But that is
a solid player in resulting context.
I think you're right that AbstractInteractions
spans both domestic and foreign spaces. I think GoingThroughCustoms
(whether it's a force or pattern) and InterfacesLast
are more polar to the foreign space.
I still don't "have" this pattern...
If this pattern is to be used within the context of ComponentBasedDevelopment
, then surely the components are being used within a framework (we've bashed this one out in ComponentDesignPatternsContext
). Frameworks are defined in terms of AbstractInteractions
between components, so the interfaces have to be defined early on, before
components are implemented. As well as logically matching what we have discussed previously, this matches my experience of developing frameworks for both single-address-space systems and distributed systems.
It seems that this pattern is being used where there is no framework for distributed components. If that is the case, then why?
- Is it that there are no suitable distributed component models? Is InterfacesLast the first round of an iterative development and refinement of a distributed framework? If so, then that does match my own experience of developing frameworks -- such development is iterative and component interactions do change per iteration.
- On the other hand, is a distributed component framework not really required? That is, is InterfacesLast being used to define ComponentGlue that is "GoingThroughCustoms" to connect component frameworks in different address spaces?
Kyle said that the interfaces were facades. Is that true for David and Philip,
as well? If so, then it is pretty obvious that interfaces come after the domain
objects because facades usually come after the objects that they hide.
Although once you have a facade, the subsystem behind it can be refactored
and you can end up with new objects. Anyway, is this just a special case of
, or is it a different pattern?
I get the impression this is a ProcessPattern
more than a DesignPattern
. I'm trying to understand it. It seems similar in some respects to EndGame
, written up in . The key sentences in EndGame
"We created these four zones of design.
- The end-client's interface. During Move One, we concentrate on the dialog between the workstation class (the Account) and its clients, ensuring they have an appropriate interface and a dialog absolutely independent of the server technology. After Move One, the workstation object's clients no longer care about the details of distribution, the nature or the implementation of the server database. The design of that interface is sound and stable, subject only to necessary changes that come from the bare fact of distributing computation.
- The workstation object's internal design, as though processing were all local. During Move Two, we pretend there is no database to clutter up the discussion, and derive a clean and sound local design - even though we know this is not a true assumption and some parts of the design will have to change. After Move Two, the object has a sound OO design and be relatively stable. It is untouched by questions of the server database, or by the current relational tables.
- Distribution. During Move Three, we add the facts of distribution, accounting for performance loss through delays and multiple, concurrent users. After Move Three, the design of the workstation object is stable, and sound with respect to performance delays and distribution. The workstation object no longer cares about the implementation of the server. This is the end of the story from the workstation team's perspective.
- The server's internal design. During Move Four, the server team designs the server portion of the object, taking into account their own special requirements. After Move Four, the design of the server is complete. Note that the implementation technology on the server may be changed without damaging the workstation object or its clients.
counts as a ProcessPattern
because it says, "do this first, then this, then this". Which is InterfacesLast
intended for? --AlistairCockburn
[Comment from AnonymousDonor that COM requires interfaces to be defined first, retracted by author.]
If you go back to the beginning you'll see that this is referring to something different than what you're thinking.
This pseudo-pattern is about the emergence
of components from objects. It shows that you can't always do top-down component design because you're not going to be sure what
the components are. As the disagreement between Nat and I showed this tends to arouse strong feelings.
For instance, think about your COM and C++ example. Suppose that you started with a COM interface. It had a big honking C++ class that implemented it. Over time someone read "Refactoring" and decided to cut the big C++ class apart into multiple smaller classes. People found that that made the individual parts more easily digestible, and started using the individual classes in places where they didn't use the original COM component. Someone else came around and said "that's silly" so they defined a set of COM interfaces on the new classes.
I don't agree: you are working with interfaces all the time. In your example above, you start off with one interface and later refactor it into many interfaces. However, at all times you are working in terms of protocols between components defined in terms of interfaces -- AbstractInteractions
. In most cases, it is uncommon to start of with a single interface because one's design effort is concentrating on the protocols between components and these can only be defined by multiple interfaces in current languages.
I think InterfacesLast
is a misnomer. This is a refactoring pattern for ComponentBasedDevelopment
COM and CORBA books tell you to define your interface first, with IDL, and then do the implementation.
This can make a lot of sense for large multi-team projects with BigDesignUpFront
-- you really want to keep inter-team interfaces relatively stable.
However there's a trade-off:
- Too much focus on interfaces will pervert your OO design.
- Ignoring the inter-system interface chasm for too long can cause severe performance problems. (Typical of MTS components with too many atomic properties to set.)
It's probably a good thing to ignore interfaces at first, to come up with a good "ideal" OO design.
Then change it, if needed, to achieve measurable performance and scalability goals.
(Don't be surprised if you have to compromise "OO-ness" to improve performance -- procedural RemoteProcedureCall
s are very efficient. ;-)
COM and CORBA interfaces >can<
But you have to be more careful if people outside your team are using the interface.
(...unless you like it when your application crashes.)
But you can gracefully provide backward compatibility in COM during the transition period -- by supporting both old and new interfaces at the same time.
(I'm told there are CORBA design patterns that support this too, but I haven't been able to find them.)
CategoryInterface ComponentDesignPatterns CategoryPattern