Top-down Design Using the System Designer
This article describes a technique for top-down system design supported by the Visual Studio System Designer, available in the Visual Studio Team Edition for Software Architects.
In Visual Studio 2005, the System Designer experience is optimized for bottom-up ‘composition’ of systems from pre-existing applications or systems. However, top-down design of systems is also a valuable architectural design technique. Using top-down design a system architect can first describe the overall behavior of a system and then progressively drill in to the design and identify further sub-systems or applications, and then assign aspects of the system behavior to these system members. Eventually all the system's behavior will be assigned to applications that are to be or have already been implemented. Any number of levels of sub-systems may be defined using this process.
While top-down design was not a key scenario for the first release of the Distributed System Designers, they do support a top-down or system ‘decomposition’ approach. Ideally, you would be able to add endpoints to a system directly and then later delegate the behavior top-down to endpoints on member systems or applications. However, in Visual Studio 2005 you can only create system endpoints by adding them bottom-up as 'proxy endpoints' for existing endpoints of member systems or applications. However, with a little care and using a little-known feature it is easy to overcome this limitation and design systems top-down. You will also see how the approach allows an iterative and agile process to be used - you should not confuse top-down design with a waterfall process.
Summary of the Approach
The top-down design approach is reasonably straightforward - you first create a system and a 'shadow' application whose sole purpose is to define the behavior of the system. You include this application in the system and expose all its endpoints from the system with proxy endpoints. The system can now be included and connected within other higher-level systems if required. Over time as you determine how the system should be implemented, you define and include lower-level sub-systems or applications and progressively delegate the behavior of the system endpoints to the endpoints on these members instead of the shadow application. As you can this technique recursively to create and include new systems, you are able to progressively refine a design from the top down using any number of levels of nested systems.
Re-assigning a Delegation
A system is simply a configuration of its member. The endpoints on a system only exist to selectively expose the behavior of the members, which is represented by the delegation of the system endpoint to a member endpoint. For this reason system endpoints are referred to as proxy endpoints. The top-down design technique pivots on a feature which allows you to associate a different member endpoint with a proxy endpoint - effectively moving the target of the delegation of the proxy endpoint from one member endpoint to another in a single action. Previously, you could not do this - instead you had to delete the proxy and re-add a new one from the alternate endpoint. But doing that deletes (legitimately) all connections or delegations to the proxy in any open higher level system diagrams. With no support for moving delegations it made top-down design extremely hazardous!
An Example Walkthrough
The rest of the article walks through an example and describes how to use the move delegation feature. In this example we start by creating a high-level Auction system and describe some of its key behaviors exposed as system endpoints. We then add a Bidding system as a subsystem of the auction system and then drill into the Bidding system and define a Bidding application which offers a Web service and a bid history database to implement the bidding system's behavior.
So first let's define the Auction system. This can be created by adding a new Distributed System Diagram directly from the Solution Explorer, but as we know we need to create an application inside the system we'll create that first and then add the system via the Application Designer - it's a little easier that way around. So in the Application Designer we drag an ASP.NETWebService prototype onto the diagram to add an ASP.NETApplication and name it AuctionSystem_shadow and rename the default Web service endpoint, Bidding. Then we add additional Web service endpoints named, Buyers, Sellers and, Administration, to support the other behaviors the Auction system is to expose. At this point we can start to define the behavior of each endpoint. At this point the application looks like this.
On the Bidding endpoint we can go ahead and add the operation, SubmitBid, and define that it takes a parameter NewBid of type Bid, and returns a Type Bid Acknowledgement. Note that you don't define the types, you only name them. Detailed type definitions are required only once you generate code. We can add other operations to the other endpoints but with that one operation we have enough for our example. It is not required that you define the operations before you drill-down into the system design - in fact endpoint behavior can be defined at any point in the process - it's enough that we have the endpoint defined. Depending on the types of endpoints you wish to expose you may need to create more than one shadow application . For example, if you wish to expose a database endpoint you will need to create a shadow external database. As a rule you should create as few applications as possible - they only exist as placeholders so that you can describe the endpoints. Once the behavior defined on the shadow application has been migrated to lower level subsystems and applications the shadow application will be deleted. The shadow application can be updated as often as required, adding, removing or updating endpoint definitions to keep the system specification current.
Now we select our shadow application and choose Design Application System. In the dialog we name the new system, AuctionSystem. In the resulting system diagram we visit each endpoint on AuctionSystem_shadow and add a proxy endpoint on the system. You do this by dragging a delegation line from each endpoint to the system border or with a right click action on each endpoint. The AuctionSystem looks like this at this point.
At this point we have defined our high-level system and described (some of) its behavior and can now include this system in other still higher level system diagrams. In the diagram below you can see how we can include this in a higher level system and connect consumers to the endpoints offered by the system even though we have not even remotely started to define how the Auction system will be implemented.
The next stage - which might happen some time later - is to drill down into the auction system and describe how it is to be implemented. This can be done progressively and iteratively. There is no need to proceed in a waterfall fashion. In fact we have done enough in our example to progress to a complete implementation of just the SubmitBid behavior if we so choose.
When you are ready to refine the system and define the next level of decomposition, we will want to ‘migrate’ the behavior from the system's shadow application to applications we either intend to implement or onto the shadow applications of one or more lower level systems. The behavior is migrated as follows. On the application diagram, create the application to which you wish to migrate the behavior. Use copy and paste to add copies of the appropriate endpoint(s) to the new application. Note that you can migrate endpoints progressively over a period of time – there is no requirement that they are all migrated at once. This allows you to drill into some parts of a system but leave others aspects defined only at a high-level.
So in our example we will add a BiddingSystem_shadow application as above and copy the Bidding endpoint to it. On the application diagram you should now have the following two applications.
Now we select the application and create a BiddingSystem as before. On the resulting system diagram we expose the Bidding endpoint as a proxy as below.
Next we return to the AuctionSystem diagram and open the System View window if it's not already open. This window provides us with a list of all the applications and systems in the solution that are available for inclusion in the auction system. We locate the BiddingSystem under the Systems node and drag this on to the system diagram as a peer to the AuctionSystem_shadow application. At this point the AuctionSystem looks like the image below with all its behavior delegated to the AuctionSystem_shadow application and an unconnected member, BiddingSystem, with one endpoint, Bidding. If we select the Bidding endpoint on the BiddingSystem and choose to View Operations we see that it defines the NewBid operation as does the Bidding endpoint on the AuctionSystem_shadow.
Now we use the 'move delegation' feature to reassign the delegation so that a consumer of the bidding service is delegated to the new BiddingSystem. Locate the Bidding endpoint on the AuctionSystem, press the alt key and drag a new delegation line to the Bidding endpoint on the BiddingSystem. In one step the delegation to the shadow application is deleted and replaced with a delegation to the Bidding endpoint on the lower-level system.
At this point we can tidy up the shadow application and delete the Bidding endpoint.
When we are ready to describe the implementation of the BiddingSystem we go through the same process again. In this case, we will implement the bidding system using an ASP.NETWebApplication with a Web service plus a database that can only be accessed via the Web service. In this example, the shadow application we already have can be used as the basis of the implementation, so it's enough to rename the shadow application to BiddingManager and add the external BidHistory database and connect it to the application. Note that the application will need to be renamed on both the application diagram and the system diagram. We then add the database to the Bidding system and connect it there to the BiddingService application. Note that by not exposing the database connection endpoint with a proxy endpoint we have made the database private to the BiddingSystem. At this point we would continue to flesh out as much detail as we want to on the application using the Application Designer before implementing it as normal. The final form of the BiddingSystem now looks like this...
So with this example we've seen how to progressively design the implementation of a system, using both sub-systems and applications and have used the ability to re-assign a delegation to allow the behavior of the system to be migrated downwards to the applications we finally intend to implement.
Implementing the Shadow Applications
One interesting extension to this approach is to implement the shadow application with simple stubbed-out behavior. This will allow you to do some limited testing of consumers of the system before you're ready to commit to the details of the system's implementation design. This allows you to adopt an agile programming approach even while you're doing top-down design.
Tips and Tricks
So while this whole exercise arguably fits into the 'tips and tricks' category, there are a couple of useful points to remember if you're not familiar with these design tools. When looking at a system diagram with a 'nested' system on it, you can double click on the system to open that system in another system diagram. You can then use the Visual Studio back and forward buttons (not very obvious) on the tool bar to retrace your steps and move back and forth. And to create a connection between endpoints you can press the Alt key before dragging from the endpoint. You can create connections from either end with no impact on the direction of the line which is determined by the orientation of the provider and consumer endpoints involved.