I am trying to work out an elegant way to collect information for audit/history purposes over multiple operations/method calls spanning multiple classes. Does anyone have any ideas/opinions on how this data should be collected?
For my specific example, I need to interact with a webservice to synchronise data between two systems. This involves several different operations that can potentially provide useful information that will need to be logged.
For downloading of data, I need to;
- Communicate with the webservice to obtain the data.
- Data to collect;
- Summary info on webservice query (record id, type, last update time etc)
- information on开发者_开发问答 any exceptions that are thrown during the webservice call.
- time in seconds taken to retrieve the data (or exception) from the webservice
- Data to collect;
- Validate the received data through local validation rules
- Data to collect;
- Validation errors.
- If valid, then XML representation of the received data and the local representation of the same object.
- Data to collect;
- Save the validated data to a local database
- Data to collect;
- Any exceptions during database calls
- Data to collect;
- Save audit/history record detailing all of the information collected above.
Similar type of data is stored during upload of information to the webservice where we collect Exceptions during webservice/DB communications, validation errors received from the webservice via a FaultException, time taken for webservice comms etc.
For my current solution, I have currently created an AuditHistory object with placeholders to store the various information, and am passing that around to the various classes methods and having populating it during each method call. At the end, I am simply saving this audit history object.
It works, but it feels ugly and clunky.
Any ideas are appreciated.
Not exactly a pattern (more a complete paradigm shift), but for what you're describing you might want to think about Aspect-Oriented Programming.
Alternatively, if you wish to stick with the OO paradigm, you might try incorporating Observer, making all the things you need to track observable and having the audit data collector observe them.
I think your approach works, but I'd use a different method; I'd associate every request with a GUID, and pass that GUID around. Then you can use that GUID in logging, and eventually you can run something that will "stitch" the various audit history entries together based upon the GUID to give an idea of the flow through the system. It's more work on the back end to "stitch" the various logs / reports back together, but it decouples each of the components from any dependencies on each other; there's just a string that they each must use in logging.
The other benefit is that you only take the hit of dealing with the construction of the audit trail based upon the GUID of the request when you actually need the data. So if the full audit trail is only necessary on, say, 2% of the calls, the other 98% are expending resources passing around an AuditHistory object that won't be necessary. Those resources may be relatively small, but there's all the associated issues of interdependencies when the AuditHistory object changes, and the required rebuids, etc.
One rather quick fix you could implement is to define the auditing interface for each of your components. For instance, in c#, you could create the following 3 interfaces:
public interface IWebServiceAuditInfo
{
int RecordId { set; }
string Type { set; }
ICollection<Exception> WebServiceErrors { set; }
Timespan TimeToComplete { set; }
}
public interface IValidationAuditInfo
{
string ValidXML{ set; }
ICollection<Exception> ValidationErrors { set; }
Timespan TimeToComplete { set; }
}
public interface IDatabaseAuditInfo
{
ICollection<Exception> DatabaseErrors { set; }
}
For now, you can have your AuditHistory object implement all 3 interfaces. However, in the future if you want to log the information for each one immediately, you can simply provide an implementation of each interface that is then stored (potentially with a GUID for cross reference, as mentioned by Paul Sonier) to the database immediately.
I would use an explicit implementation of the interfaces in c#, since this will force you to alias each of the fields, allowing the AuditHistory object to change independent of the components that use it. By only having your business components dependent on an interface containing the fields they need, you don't have to re-compile them if your AuditHistory object implementation changes.
精彩评论