The design features provided by Visual Studio 2005 Team Edition for Software Architects have been influenced not only by Microsoft's vision for model-driven development, but also by a technological evolution from object-based architectures through (distributed) component-based architectures to the service-oriented architectures (SOA) that represent the current best practice in distributed system design.
This section summarizes that evolution—in part to demonstrate that service orientation is a good idea, and in part as a natural lead-in to the first of the new visual designers, the Application Designer, which provides a DSL specifically for modeling interconnected service-based applications.
When object-oriented programming became popular in the mid-1990s, it was perceived as a panacea. In theory, by combining state (data) and behavior (functions) in a single code unit, we would have a perfectly reusable element: a cog to be used in a variety of machines.
The benefit was clear. No more searching through thousands of lines of code to find every snippet that manipulated a date—remember the Y2K problem? By encapsulating all date manipulation functionality in a single Date class, we would be able to solve such problems at a stroke.
Object orientation turned out not to be a panacea after all, for many reasons, including—but not limited to—bad project management (too-high expectations), poor programming (writing procedural code dressed up with objects), and inherent weaknesses in the approach (such as tight coupling between objects).
For the purposes of this discussion, we'll concentrate on one problem in particular, which is the style of reuse that objects encouraged—what you might call copy-and-paste reuse.
Consider the following copy-and-paste reuse scenario: You discover that your colleague has coded an object—call it Book—that supports exactly the functionality you need in your application. You copy the entire source code for that object and paste it into your application.
Yes, it has saved you some time in the short term, but now look a little further into the future.
Suppose the Book class holds fields for Title and ISBN, but in your application you now need to record the author. You add a new field into your copy of the Book source code, and name that field Author.
In the meantime, your colleague has established the same need in his application, so he too modifies the Book source code (his copy) and has the foresight to record the author's name using two fields: AuthorSurname and AuthorFirstname.
Now the single, reusable Book object exists in two variants, both of which are available for a third colleague to reuse. To make matters worse, those two variants are actually incompatible and cannot easily be merged, thanks to the differing representations of the author name.
Once you've compiled your application, you end up with a single executable file (.exe) from which the Book class is indivisible, so you can't change the behavior of the Book class—or substitute it for your colleague's variant—without recompiling the entire application (if you still have the source code, that is!).
As another example (which we'll continue through the next sections), imagine you're writing a technical report within your company. You see one of the key topics written up in someone else's report, which has been sent to you by e-mail. You copy their text into your document, change it a little, and now your company has two—slightly different—descriptions of the same topic in two separate reports.
At this point you might be shouting that individual classes could be compiled separately and then linked together into an application. Without the complete source code for the application, you could recode and replace an individual class without a full recompilation, just link in the new version.
Even better, how about compiling closely related (tightly coupled) classes into a single unit with only a few of those classes exposed to the outside world through well-defined interfaces? Now the entire sub-unit—let's call it a component—may be replaced with a newer version with which the application may be relinked and redeployed.
Better still, imagine that the individual components need not be linked together prior to deployment but may be linked on-the-fly when the application is run. No need to redeploy the entire application then; just apply the component updates. In technological terms, we're talking here about DLLs (for those with a Microsoft background) or JAR files (for the Java folks). And in .NET terms, we're talking about assemblies.
Continuing our nonprogramming analogy, consider hyperlinking your technical report to the appropriate section of your colleague's report and then distributing the two documents together, rather than copying his text into your document.
Continuing with this line of thought, imagine that the components need not be redeployed on client devices at all. They are somehow just available on servers, to be invoked remotely when needed at runtime.
In our nonprogramming example, consider not having to distribute your colleague's report along with your own. In your own report, you would simply hyperlink to the relevant section in your colleague's document, which would be stored—and would remain—on an intranet server accessible to all recipients.
One benefit of this kind of remote linking is that the remote component may be adapted without having to be redeployed to numerous clients. Clients would automatically see the new improved version, and clients constrained by memory, processing power, or bandwidth need not host the components locally at all.
This leads us to a distributed component architecture, which in technology terms means DCOM, CORBA, or EJB. All of these technologies support the idea of a component—or object—bus via which remote operations may be discovered and invoked. In Figure 1-1, the component bus is indicated by the grayed-out vertical bar.
In case you're wondering, the notation used in Figure 1-1 is UML. In fact, it's a UML Component Diagram drawn using Visio for Enterprise Architects. We used UML here because we haven't yet introduced you to the new notations.
Of course, as remote components are modified, we must ensure that none of the modifications affect clients' abilities to use those components, which is why we must make a distinction between interfaces and implementations:
Interface: A component interface defines the contract between that component and the clients that use it. This contract must never be broken, which in practice means that existing operations may not have parameters added or taken away, though it may sometimes be permissible to add new operations.
Implementation: A component implementation may be changed at will in terms of the use of an underlying database, the algorithms used (e.g., for sorting), and maybe even the programming language in which the component is written, as long as the behavior is unaffected as far as the client is concerned.
This distinction between interface and implementation raises some very interesting possibilities. For example, a single component implementation (e.g., Bank) may support several interfaces (e.g., AccountManager and MoneyChanger) whereas the MoneyChanger interface may also be supported by another implementation (e.g., PostOffice).
Moreover, the underlying implementations may be provided by various competing organizations. For example, you could choose your bank account to be managed by the Bank of BigCity or the National Enterprise Bank so long as both supported the AccountManager interface. This idea is revisited in Chapter 2.
What's wrong with the distributed components approach?
To start with, the same underlying concepts have been implemented using at least three different technologies: the OMG's Common Object Request Broker Architecture (CORBA), Microsoft's Distributed Component Object Model (DCOM), and Sun Microsystems' Enterprise Java Beans (EJB). Though comparable in theory, these approaches require different programming skills in practice and do not easily inter-operate without additional bridging software such as DCOM/CORBA bridges and RMI-over-IIOP.
Furthermore, distributed component technologies encourage stateful intercourse between components by attempting to extend the full object-oriented paradigm across process and machine boundaries, thereby triggering a new set of challenges such as how to manage distributed transactions across objects, requiring yet more complex technology in the form of the CORBA Transaction Service or the Microsoft Transaction Server (MTS).
To a certain extent, a service-oriented architecture alleviates these problems by keeping it simple: by making the services stateless if possible, and by allowing services to be invoked using the widely adopted, standard over-the-wire protocol Simple Object Access Protocol (SOAP).
Logically, a representation of Figure 1-1 (distributed component bus) redrawn as Web services would be virtually identical, as shown in Figure 1-2 (distributed Web services). Admittedly, this diagram has been drawn specifically to look as much like the other one as possible; nevertheless, you should note the close correspondence between the various elements.
Not only does Figure 1-2 show the close correspondence—and logical progression—from distributed components to Web services, it also offers a first glimpse at the kinds of diagrams you'll be drawing in this part of the book. It is a Visual Studio 2005 application diagram drawn using Application Designer.
Before we move on, it's important to understand a key point here: Just because the original object-oriented paradigm has evolved toward the service-oriented architecture, and just because Application Designer (described in the next section) is biased toward service-oriented architectures, that doesn't mean that the earlier object-oriented and component-based approaches have been replaced—they are merely complemented. In fact, in Chapter 5, you'll do some traditional object-oriented design using the Visual Studio 2005 Class Designer.