| <?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 |
| "bundles" (in the language of Eclipse, these |
| components have been traditionally known as |
| "plug-ins"). 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 "get it |
| right", and even then you sometimes have to settle for |
| "good enough" 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 "Plug-in" Projects (that is, each |
| "Plug-in" 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 "subset of |
| functionality" 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 |
| "loans" 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 "popupMenus". 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> |