blob: 6005421c010ba2b1f8ac877008c3c1c5f10b297b [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<article>
<title>Eclipse RCP Architecture Application Architecture</title>
<copyright year="2006">The Eclipse Foundation</copyright>
<revision>
<date>September 8, 2006</date>
<author>Wayne Beaton</author>
<email>wayne@eclipse.org</email>
<company>The Eclipse Foundation</company>
<link>http://www.eclipse.org</link>
</revision>
<abstract>
The Equinox implementation of OSGi R4 is one of the more
compelling features of the Eclipse Rich Client Platform (RCP).
At its heart, Equinox is a powerful component management system
that provides incredible value to your application: but only if
you architect your application to leverage that power. In this
article, we discuss how you can go about factoring your
application into multiple components.
</abstract>
<section>
<para>
The Equinox component model is one of the more compelling features
of the Eclipse Rich Client Platform (RCP). At first glance, the
component model appears to be a mechanism for grouping
collections of code and resources together. Equinox provides more than
this. Eclipse components
components provide management: the ability to dynamically
install, update, load, and unload components is included in
this. Also included is the management of dependencies among
components; one component needs to be able to declare what
other components (and versions of these components) it needs
to function. These dependencies are managed by the component
infrastructure at runtime. Standard Java doesn't have
anything like this.
</para>
<para>
One of the greater strengths of the Eclipse Platform is the
underlying component model. It is this component model that
makes Eclipse relatively easy to extend with
highly-integrated yet loosely-coupled features. The Eclipse
Platform makes extensive use of the component model: it is
itself a composition of components as is every other
Eclipse-based product. This powerful component model is part
of the Eclipse Rich Client Platform.
</para>
<para>
The Eclipse component model is provided by the
<xref-link href="http://eclipse.org/equinox">
Equinox
</xref-link>
project. Equinox, which implements the OSGi R4
specification, supports a notion of components called
&quot;bundles&quot; (in the language of Eclipse, these
components have been traditionally known as
&quot;plug-ins&quot;). While Equinox provides the component
infrastructure, the composition of components is left to
application architects and developers. Components can be as
small or as large as desired. An application can be built as
a single component, as huge collection of extremely small
components (in the extreme, each component would contain a
single class), or somewhere in between (the ideal is
generally somewhere in between).
</para>
<para>
Architecting an application as a large collection of small
scale components can make it relatively easy to update your
application and potentially reduce startup time (Equinox
loads components as they are required). However, a large
collection of plug-ins requires complex dependency
management and ultimately increases the runtime overhead of
the application. Managing that complexity can be an
significant burden. As such, deciding on exactly how you
should divide your code into separate components takes some
effort. Determining how to factor the components is not
particularly difficult, but nor is it a trivial matter. It
generally takes several iterations to &quot;get it
right&quot;, and even then you sometimes have to settle for
&quot;good enough&quot; as you balance the various forces
that come into play while designing your archtecture.
</para>
</section>
<section title="Multiple Components are Good">
<para>
Eclipse is itself a collection of many components. Having
multiple components is a good thing; this is what makes
Eclipse so customizable.
</para>
<para>
By building your application as a collection of components,
you have potential to significantly reduce its memory
footprint. When an Eclipse RCP application starts up, the
entire collection of components is scanned and a registry of
those components is constructed. However, only the subset of
components required at startup are actually loaded and
initialized. Henceforth, components are loaded as they are
needed. This can result in a significant reduction in start
up time as well.
</para>
Update individual components rather than the whole.
Built-in extensibility. The application architecture that makes
</section>
<section title="Layered Architecture">
<para>
Use components to separate layers. Component model
encourages decoupling of components by enforcing a one-way
visibility restriction.
</para>
Loose coupling is good. Opens opportunities for reuse without
modifying or branching the code. Some
subset of the components in your application developed for the
desktop can be reused in devices such as cell phones.
</section>
use components to separate
<section>
<para>
In the development environment, components are represented
as individual &quot;Plug-in&quot; Projects (that is, each
&quot;Plug-in&quot; Project becomes a separate component in
the runtime environment). An example is shown in
<xref-figure id="plug-in-project" />
. At runtime, a component manifests as either a single
compressed archive file, or as a directory; in either case,
the component is actually a composition of files. Components
can contain Java code, other resources (such as image files,
for example) and a manifest. The manifest is a text file
that describes the component and how it interacts with the
environment.
</para>
<para>
Components are declaratively specified using a manifest. In
the manifest, the developer must specify what other
components the component has access to. This visibility
between components is strictly enforced at both build and
runtime. Component versions can be specified as part of the
declaration; this makes it possible for different components
to make use of different version of the same shared
component (contrast this with the support provided by
classpath ordering in standard Java).
</para>
</section>
<section title="Forces">
<para>
When developing an architecture it is important to keep the
various forces in mind. Getting it right is a process of
balancing many forces; not all are technical. These forces
include:
</para>
<itemized-list>
<segue>
These forces are discussed in greater detail below.
</segue>
<item title="Extensibility">
<para>
The application should be extensible. That is, after
the initial delivery of the application, it should
be possible to extend it to provide functionality
that was either not part of the initial
requirements, or could not be delivered in time for
the release.
</para>
<para>
In a similar vein, the application should be
customizable. Ideally, the application can be tuned
for different clients with slightly different needs.
In many cases, tuning can be done through property
files and parameters; in many cases, custom code is
required. Ideally, the application code should not
have to be branched to accommodate these
customizations. This is of particular concern to
independent software vendors (ISVs).
</para>
<para>
An application is extended and customized by adding
components. The application needs to be designed to
accept new components and allow added components to
participate and contribute.
</para>
</item>
<item title="Updateablility">
<para>
The application should be updatable. More
specifically, it should be easy to update parts of
the application without having to update the entire
application. Ideally, the application should not be
a single monolithic part that can only be updated in
it's entirety but rather a composition of multiple
parts that can be individually updated.
</para>
</item>
<item title="Start up time">
<para>
The application should start up quickly. Ideally, as
functionality is required, the behaviour
(application logic) for that functionality is
dynamically loaded.
</para>
</item>
<item title="Loose Coupling">
<para>
The various parts of the application should be
loosely coupled with other parts. Coupling is a
measure (more qualitative than quantitative) of how
much the various parts of the application know about
the other parts. Loosely coupled applications are
generally easier to extend than more tightly coupled
applications. Loosely coupled parts are also more
easily reused than their tightly coupled
counterparts.
</para>
</item>
<item title="Seemless Integration">
<para>
The parts of the application should seemlessly
integrate. That is, from the user's perspective, the
parts of the application, though developed
separately, should manifest as a single coherent
application.
</para>
</item>
<item title="Multiple Platform Support">
<para>
Multiple platform support. Realistically, it may not
be possible to use all parts of your application on
all platforms. A user interface designed for a
desktop system may not be appropriate for a
hand-held device. However, the underlying object
model, business logic, and some aspects of the user
interface should be easily reused across the
different platform types.
</para>
<para>
Ideally as much of your application as possible
should be developed as components that are reusable
across all the supported platforms (desktop, server,
and devices).
</para>
</item>
<item title="Multiple Version Support">
<para>
Support for multiple versions of some parts. That
is, it must be possible for some parts of the
application to use one version of of a library
(either third party, or custom built) while other
parts of the application use another version. This
is particularly true as the size of the application
and development team grows.
</para>
</item>
</itemized-list>
</section>
<section title="Bringing Balance to the Force">
<para>
The Equinox component management system provides the
infrastructure that you need to balance all of these
concerns. While Equinox can run your architecture, it
doesn't design it for you. You have to do that.
</para>
<para>
Traditional application architecture wisdom encourages
separating an application into into multiple layers. The
model-view-controller pattern is an example of a layered
architecture that provides a good starting point: one
component for your application's object model (including
business logic), one component for your application's view
(user interface components), and one component for your
application's control (the glue that binds the model and
view layers). Eclipse provides most of the view components
through the
<xref-link href="http://eclipse.org/swt">
Standard Widget Toolkit
</xref-link>
(SWT), leaving us with two components representing. the
model and control layers.
</para>
<para>
Two components is a good starting point. However, as an
application grows in size and complexity, so will these two
components. It may make sense to divide your application
along functional lines. That is, one set of components
(model and control) for each different subset of
functionality. A bank, for example, might consider building
one set of components for handling personal loans, one that
handles business loans, another for investments, and so
forth. Determining what constitutes a &quot;subset of
functionality&quot; is as much an art as it is science. One
starting point might be to divide the functionality along
departmental lines.
</para>
<para>
Overlaps between components will occur. These overlaps can
be factored out into separate components. In the
aforementioned banking example, commonalities between
personal and business loans might be factored into a
&quot;loans&quot; component that is shared.
</para>
<para>
Ultimately, determining how big your components should be
and how many of them you have will be accomplished by
balancing the various forces (technical, team, policies,
politics). Whatever the case, Equinox will support you.
</para>
</section>
<section title="Extension Points and Extensions">
<para>
One way that Eclipse facilitates extensibility is through
the extension point mechanism. Using the extension point
mechanism, component builders can provide hooks that other
components can use to contribute functionality.
</para>
<para>
Perhaps the most obvious use of extension points is found in
the Eclipse user interface. Eclipse provides, for example,
an extension point called &quot;popupMenus&quot;. By hooking
into this extension point, other components can add their
own entries to the pop-up menu for many views and editors.
There are similar extension points that allow components to
contribute to the toolbars and other aspects of the user
interface.
</para>
<para>
The use of extension points is not specific to the user
interface, nor is it limited to it. Nor is the use of
extension points the exclusive domain of the base Eclipse
code. Your application can (and likely should) define it's
own set of extension points. An application might, for
example, include an extension point that provides a hook for
adding backends to your application.
</para>
<para>
Consider an application that can store data in a database,
or on a server via (for example) a web service. You could
define a single component that provides the required code
for both (as shown in
<xref-figure id="single-component" />
); or you could define a component that includes an
extension point for backends, along with a separate
component for each kind of backend supported. By doing so,
you provide customizability in that it is possible to tune
the set of backends supported simply by including only the
backends you require. Additionally, you provide for
extensibility by allowing yourself or others to create their
own backends without requiring that they change your base
component's code.
</para>
</section>
<section>
<para>
An application, factored into multiple layers, is generally
easier to enhance and maintain. Ideally, each layer contains
code that is specifically focused on that layer (no view
code in the model layer) which makes the layer potentially
reusable. A well-designed model layer can be used in both
your Eclipse RCP application and your web application, for
example. By putting your data management logic into a
separate layer, it becomes easier to change or completely
replace that layer without change rippling across the entire
code base. At a minimum, each layer of your application can
be built as a separate component.
</para>
<para>
When Eclipse starts up, it loads the minimum set of
components required by the environment to function.
Additional components are then loaded as they are required.
This significantly reduces the start up time required by the
application.
</para>
<para>
An application composed of multiple components is generally
easier to update than one built as a single monolithic
component. Individual components, each containing some part
of the functionality can potentially be updated
individually; this reduces the amount of time required to
download and install updates. Of course this comes at the
expense of potentially increased complexity in managing
relationships and dependencies among components.
</para>
<para>
Version management makes updating components actually work.
</para>
<para>
An application composed of multiple components is generally
easy to extend. In fact, it is possible to--when
necessary--completely replace components to radically change
the behaviour of the application while minimizing the impact
of the rest of the code. Some ISVs use components to
customize the behaviour of their application for individual
customers without forking the code base.
</para>
<para>
A large number of small components make updating your
application easier. Rather than updating one huge monolithic
application runtime, you can update individual components.
The Eclipse Update Manager--in conjunction with an update
site--even does most of the heavy lifting for you. In
addition to the update benefit, a collection of components
is better able to take advantage of the lazy-loading feature
provided by Equinox. Equinox loads components only when they
are needed; this reduces the time and effort required when
the application initial starts up which improves the user's
experience.
</para>
<para>
Perhaps one of the more significant advantages of the
Equinox component model is the management of dependencies
between components. All components must explicitly declare
their dependencies on other components. In the Equinox
environment, when a component is required, all of the
components that it is dependent on are loaded before the
component itself (naturally). More importantly, Equinox
insulates components from other unrelated components. That
is, a component can only directly access the elements
contained within the components it is dependent upon. All
other components cannot be referenced and will not
interfere.
</para>
<para>
It is possible that two completely independent (i.e. no
direct dependencies) components can define classes with the
same full name and there is no conflict (contrast this with
standard Java in which the class that appears earliest on
the classpath wins). This extends to multiple versions of a
single component: it is possible to have individual plug-ins
reference different versions of the same component. Multiple
versions of the same component can run concurrently within
the environment without conflicting with one-another (this
works so long as the plug-in does not contribute anything to
the user interface; that just gets weird).
</para>
</section>
<section title="Balancing Act"></section>
<section title="Observer Pattern">
<para>
Very often there is a huge divide between the ideological
theory and the practical reality. That is, it is easy to say
that multiple components are good and that they should be
used, but how do you actually make it work?
</para>
<para>
The observer pattern is an important mechanism for making a
proper separation of components work. This pattern is not
specific to Eclipse or even to Java (it is used extensively
Smalltalk and in other languages). It is one of the key
features of the JavaBeans specification.
</para>
<para>
Essentially, the observer pattern is used by one object to
watch changes in another without building a formal
relationship. Rather than the observed object knowing
anything specific about the observing objects, the observed
object instead fires the moral equivalent of an event. The
event comes in the form of a message being sent to a
listener object that is registered by the observer. The
windowing system that underlies Eclipse, the Standard Widget
Toolkit (SWT), does exactly this.
</para>
<para>
<xref-listing id="listener" />
shows a fragment of application code that creates a new SWT
Canvas and adds a paint listener to it.
</para>
<listing id="listener">
<caption>Registering a listener on an SWT Canvas</caption>
<code>
<![CDATA[
canvas = new Canvas(parent, SWT.NONE);
canvas.addPaintListener(new PaintListener() {
public void paint(PaintEvent event) {
event.gc.drawRectangle(10,10,50,50);
}
});]]>
</code>
</listing>
<para>
When it is time for this canvas to be redrawn (either when
the canvas is exposed or it is explicitly asked to redraw),
it will send the
<code type="method">addPaintListener()</code>
message to all of the registered listeners.
</para>
<para>
This pattern is leveraged throughout the Eclipse code. Some
components register with the workbench selection service;
doing so invokes the listener whenever a selection occurs in
a view or editor. The Eclipse Corner Article
<a
href="http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html">
Eclipse Workbench: Using the Selection Service
</a>
discusses the selection service in detail.
</para>
<para>This pattern can be leveraged in your code.</para>
<para>
One of the things that I implemented with the Sudoku game
was a disconnect between the model and the view. I provided
a representation of a Sudoku game that knows nothing of user
interface. It knows about cells and boxes and numbers; it
also knows if any given cell is considered valid. It knows
nothing about what colour should be used to draw a cell, how
thick the lines should be, or anything else that has
anything to do with drawing.
</para>
<para>
To help with the drawing part, and to avoid having to
continually recompute position information for individual
cells, I introduced the notion of a CellDrawer that does
know about colours, and line thickness, and such. It also
knows--in absolute coordinates--where to draw the cell. This
class is part of the user interface code.
</para>
<para>
I set up the Sudoku board so that it can be observed.
Interested parties, like a user interface, can register
event listeners that are triggered when a change occurs on
the board. I use the listeners consistently. When the user
clicks on the board or types a key, the user interface
translates that operation into a command that's passed to
the Sudoko board. The command is along the lines of "set the
value of the cell at position (5,4) to 7" (position is the
relative position of a cell, not a screen co-ordinate). The
board makes the necessary modifications and fires an event
to inform interested parties of the change. The user
interface is one of the interested parties, and when it
receives the event it redraws the board. I could have done
this a lot simpler: when the user clicks or types a key,
change the board and then get the user interface to redraw.
Why not just do this? Why all the extra event stuff?
</para>
<para>
Well... Chris answered this question pretty well. In a few
minutes on a plane, Chris added an ECF component to the
game: he made the Sudoku game multi-player. He did it
without interacting with the user interface, he just worked
directly with the model. When the remote user makes a
change, a message is sent. When that message is received,
the model is modified which causes an event to be triggered
resulting in the user interface updating. Pretty cool. And,
a fine example of extending a system in a way that the
original author had never considered.
</para>
<para>
I use the event in other places as well. The solver included
with the implementation modify the model which then triggers
an update of the user interface. The solvers know nothing
about the user interface.
</para>
<para>
At some point, I'll probably add a couple more views to the
game. An obvious view might show some statistics: how many
cells are left, how long as the game been going, how many
times has a particular cell been changed, etc. This view
will also just register itself to receive changed events
from the board and update itself whenever those events are
triggered. My main user interface will not have to know
anything about this new view and so will be totally
decoupled; but it will appear to the user to be tightly
integrated. Very cool.
</para>
<para>
This relationship is described by the Observer Pattern. It's
used extensively throughout the Eclipse code (including SWT)
and all over the place in Java. Heck, we even used it in
(wait for it, Denis...) Smalltalk! It's a pretty powerful
mechanism that may require a little bit of work up front,
but sure makes building applications a lot easier.
</para>
</section>
<section title="Applying the Theory">
<para>
As a starting point, divide your application into separate
plug-ins for the model and view. Ideally, a separate plug-in
to manage data concerns (i.e. storing and retrieving your
objects from a data source) should be considered as well.
</para>
<para>
After that, it may be desireable to build separate plug-ins
along functionality lines.
</para>
</section>
</article>