Having used FDO for quite some time now, a lot of its API design has influenced my design of the Maestro API. Those who have used FDO may notice many parallels in the design when working with the Maestro API.
The first similarity is the heavy use of interfaces. FDO models its connections and commands via interfaces. In the Maestro API, we model the following in the same manner using interfaces:
- MapGuide Server connections
- MapGuide Service APIs
- MapGuide Resource classes and message types
The MapGuide Server connection is represented by the IServerConnection interface. This is the main point of entry in the Maestro API.
This contains the ResourceService and FeatureService interfaces to provide resource manipulation and feature querying capabilities. Additional MapGuide services are available via GetService method and you can interrogate the Capabilities property to determine what you can and can't do with this particular connection.
Now the reason this is an interface is because in the Maestro API, there are currently 2 implementations of this connection:
Now the reason this is an interface is because in the Maestro API, there are currently 2 implementations of this connection:
- One that communicates with the mapagent via http. (Maestro.Http)
- One that wraps the official MapGuide API. (Maestro.LocalNative)
Now given that IServerConnection is an interface, how does one go about creating instances of it? Enter the ConnectionProviderRegistry
Simply call CreateConnection passing in the desired provider and the parameters needed to initialize it. The ConnectionProviderRegistry reads from a ConnectionProviders.xml file which contains all the registered connection providers. Each connection provider is an implementation of the IServerConnection interface.
So having acquired a connection, we can now interact with the services provided by MapGuide. Once again, these services are modeled as interfaces
Each service is defined in a corresponding interface. For reference, they are:
- IFeatureService
- IResourceService
- IDrawingService
- IMappingService (also contains rendering service APIs)
- IFusionService
- ITileService
- ISiteService
- For the http connection provider, these interfaces wrap http requests to the mapagent and automatically processes the XML responses from them.
- For the local connection provider, these interfaces wrap the offical MapGuide service classes (eg. The implementation of IFeatureService wraps the methods of the MgFeatureService class, and does automatic conversion of MgByteReader values)
Now there's a lot of XML Schemas for all the various forms of XML data that MapGuide returns. Actually building the classes that represent this data by hand would've taken an eternity, so I've taken a shortcut for this particular case and used the xsd2code code generator to generate all the required classes from their XML Schemas. I used xsd2code for generating the classes instead of using the default xsd.exe tool for reasons I have already explained. The set of generated classes produced by xsd2code a much cleaner (relative to xsd.exe anyway) and gave me a very useful foundation to work with.
In the case of resource classes, using the generated classes as-is was still not good enough. Additional tweaking of this generated code was required. The reason is that each particular version of a resource was its own separate class when generated. Each particular version of a resource in its generated class form had nothing in common. Once again, interfaces come to save the day.
Now you may notice that there is only one interface for Layer Definitions, but there's been 3 additional revisions to date in the Layer Definition XML schema (ref 1, 2, 3, 4 and 5). So how were 4 separeately generated classes able to be reconciled into a single interface?
One of the fortunate things thus far, that Autodesk did when they introduced a new version of a given resource was that the schema revisions have been incremental and additive. Nothing is actually removed or altered in every schema revision thus far.
Because of this, the design of these interfaces is such that they represent the 1.0.0 version of the respective resource type each interface is modeling. Elements introduced in newer versions of the schema are exposed through a new version of the interface that extends the original.
So with this design, we have a consistent baseline interface for all resource types and we can interrogate the resource version to determine if we can cast these interfaces (or its child properties, which are also interfaces) to the newer versions to tap into the extra functionality exposed in these newer interfaces.
As is the case with the Web Layout in the above diagram. IWebLayout2 extends IWebLayout and maps to the 1.1.0 version of the Web Layout schema that introduced a new "ping server" property. However both can be accessed via the IWebLayout interface. We can check if the resource version is 1.1.0 to determine if it can be cast into the IWebLayout2 interface.
It is through this design that the Resource Editors in MapGuide Maestro are able to edit most, if not all known versions of a given resource type, and have the necessary intelligence to enable/disable certain UI elements for elements not supported by the edited resource version. This design allows Maestro to keep up to date as newer resource types or schema versions are made available, providing that these schema revisions stay true to their additive form.
This incremental quality of the XML schema revisions is also reflected in the generated code. Thus with the magic of partial classes and careful use of the C# pre-processor, getting different versioned classes to implement the same baseline interface was actually an easy task. Then it was a case of extracting the newer properties and methods introduced in newer schema versions into their own separate interface that extends the original.
The end result, is a consistent object-oriented API that is (mostly :) ) free of boilerplate code that you would normally write to deal with raw XML content, MgByteReaders and HTTP requests/responses. Any developer who has worked with the official MapGuide API would appreciate
So that's the high-level overview of the design of the Maestro API. Stay tuned for part 3, where I actually show some concrete examples of how to use this library.
No comments:
Post a Comment