Our custom development here at Whyte Hirschboeck Dudek regularly requires integration with our enterprise systems, from Active Directory to Time & Billing, our custom Marketing Dashboard, Document Management System, Customer Relations Management, etc...
A few examples of data we may regularly need include:
- Contact information
- Member of a security or distribution group in Active Directory
- Current secretaries assigned to lawyers, or vice-versa
- Who is associated with what clients and projects
A small disclaimer: we are lucky to be in a Microsoft house, with Visual Studio 2008, Active Directory, Exchange, (mostly) SQL 2000 & 2005, and some APIs to play with. Our systems are positioned to play nice with each other.
There are so many different infrastructure frameworks, paradigms, and architectures that it is overwhelming to try to keep up (just learning about them, never mind putting them into production!). Sure, service-enabling your enterprise with SOA seems to fit the bill through total abstraction, but the up-front costs are huge, and the ROI is uncertain at best.
As you probably know, POJO is a formalized ('Fowlerized") term for a 'plain old java object', and POCO is the CLR representation (PORO is Ruby, etc). It is a domain entity that carries no special baggage. It doesn't extend anything or carry any dependencies on other frameworks or methodologies, only the CLR.
When writing the application itself (a webpart or user control, clickonce app/utility, extranet, report, etc) 100% focus should be on the application's purpose and logic. I should never have to tell the application what a Client is, just that I need one. I shouldn't have to say that a client has matters, my client object should expose that for me! Also, I don't want to get my objects by writing up services, proxies, ORM metadata, and various providers unless it is absolutely necessary. I know these things are what's cool, and I need to grow up, but I like simplicity: I want my app to be as simple as possible to develop, and I want anything written outside the bounds of my app to be available as a single .dll file that describes my business entities so they can be reused later by other projects.
.Net provides some amazing capabilities for wiring an app together: Lambdas, LINQ, Extension methods, Generics, System.Converter<T1, T2>. It is much more capable than it used to be to work with objects quickly and easily on its own. (With this in mind, I am anxious to see what the Entity Framework does for us as far as value:complexity!)
The following explains our approach, starting with the fundamental bits in our framework, the core library...
Enterprise Core Library
The foundation of our enterprise integration efforts. The classes contain regularly needed functionality like:
- Logging (to a single EnterpriseLog table)
- Connection strings to various systems (SQL, Ldap, Service URLs)
- Email, Notifications
- Active Directory querying
- ClickOnce helpers (add to startup, autoupdate methods, etc)
- SQL access methods for reading and executing SqlCommand objects.
- List to RSS mapping
Our custom apps need a few keys in their .config, to access Active Directory, and to access our WhdEnterprise.ConnectionStrings table.
ConnectionStrings (here is my post on this)
Connecting to a system from our apps are trivial: ConnectionString.Retrieve("TimeBilling") grabs the appropriate string from the WhdEnterprise.ConnectionStrings table. When (not if) the Time & Billing system moves servers, update the row in the table instead of mass-updating config files for all broken apps that use it. A dictionary cache exists to keep performance from being an issue.
ConnectionStrings may be used for more than just databases... consider LDAP string for Active Directory, and URLs for web services.
One of our most fundamental objects is in our Core library: WhdUser.
WhdUser.Current uses Environment.UserName to identify the current user when needed, and optionally (lazily) grab email, phone extension, and computername from Active Directory.
Once the current user is identified, a domain-specific user can be instantiated (ex: List<Client> clients = TimeBillingUser(WhdUser.Current).Clients). Each system has its POCO user object, and controller to retrieve the relevant domain-specific info. Our time & billing system for instance, has a TimeBillingUser, which may have properties and methods like Secretaries, Clients, Projects, HoursWorked(), HoursBilled(), HoursCollected(), etc. This data is lazy-loaded when appropriate.
Organic, Evolutionary library growth
"We stress the importance of creating objects not to meet mythical future needs, but only under the demands of the moment. This ensures that a design contains only as much information as the designer has directly experienced, and avoids premature complexity." - Kent Beck & Ward Cunningham, A Laboratory For Teaching Object-Oriented Thinking
We extend our libraries only as needed, to meet current requirements. The caveat is that we must keep our libraries backwards-compatible. This can be accomplished easily by writing tests as the library is developed, and making sure those tests stay with the library project, and are run every time the project is extended.
For example, to get a list of our most active lawyers, we may write a library that contains objects for Timekeeper, and Timecard, then map these into our Time & Billing system that contains our timekeeper and financial (hours worked) data. At a later date, we may need a list of clients a lawyer does work for, so we would create a Client object, and map it into the appropriate table(s).
At this point, we have a single DLL (Whd.Enterprise.TimeBilling.dll) that is deployed with the new app. Other deployments of this dll used by other apps are older, but they were deployed with the requirements that app needed. With tests in place, those older apps should not have to worry about the new TimeBilling library being incompatible.
This is a controversial idea, and my naming could be better (not to be confused with MVC controller), but it works very nicely to develop against (especially with Intellisense). Each enterprise object has 2 classes, the POCO (plain old CLR object), and its controller class (Client, and ClientController respectively). Controller classes are full of static methods to do the work.
One of the key methods in the controller is Read(), which does the object-relational mapping. The Read() method is delegated to the Core's Sql class to handle, and returns the appropriate POCO it's responsible for. Other methods are added as needed, and if the controller class ever gets too big or busy to work with, it can then be refactored out to partial class files.
Pragmatic App Development
From the app-developer's perspective, integrating with the enterprise should be as simple as possible:
- Add projects to my solution for each library I will be using, extend it as needed to fulfill my needs (or just reference the dll!)
- Reference the enterprise projects in my app's solution.
- Simplest possible namespace structure as possible. Ideally, "WHD.Enterprise." opens up all of the domain objects and controllers I would ever need.
- [Object] & [Object]Controller is the only enterprise-related pattern I need to understand.
- Centralized logging (Logging usage has proved its value already)
Hopefully this is a good start to explain my thought and dev processes. Don't hesitate to post any questions and/or criticism, I am sure that in future posts I will delve deeper into some of the bits, maybe even post some code.