The complete source code for this article can be downloaded at http://www.udooz.net/file-drive/doc_download/16-determinedinteraction.html.
Read my determined interaction pattern for following this article.
Introduction
Handling and propagating errors in a SOA system is well known and few patterns are already available for this. An interaction (means that when a consumer invokes a service) may fail due to the incorrectness request data or problem in the service layer itself. This makes an interaction as faulted. There are some business scenarios where an interaction might not be required to fault, when the cause of failure is accepted by the consumer. This means that overriding that failure. This kind of failure is called as warning. Determined Interaction is a pattern to handle and propagate warnings in SOA systems. This article explains the implementation of exposing warning in WCF services based on my WCF pattern Determined Interaction. Read the pattern here. I've taken the same drug prescription for this article.
The following figure shows the responsibilities of each layer of a SOA system to handle warning.
Core Objects at Service Layer
The following class diagram shows the core objects required to convey warnings.
In a service with request and response message exchange pattern, a medium is required to convey if any warning occurred during an interaction. Acknowledgement is acted as a medium which conveys the following:
- Status - status of the interaction which could be either success or failure. I've defined this as integer and mimic HTTP status code 200 (Success) and 301 (Warning)
- TimeStamp - when the result happened
- Warnings - contains warnings if any
Warning contains Code
which uniquely represents a warning type, for example I've used 12345 for drug allergy with relevant Message
which contains description of the warning. CorrelationState
contains the hashed value of data which is required for interaction integrity.
Two interactions might be possible, one ends up with conveying warning details to the consumer followed by approval interaction with the same set of information in addition with correlation state, if consumer wants to override the warnings. Both interactions have to be handled by single code path in the domain layer. AddWarning()
method of Acknowledgement
class processes both interactions and gives the result as WarningElevation
. WarningElevation.Admonish
means that the interaction should be halted and convey the warning details to the consumer. Override means that this is second interaction (i.e. determined interaction) and consumer wants to override the warning. Let us see the implementation of AddWarning()
step by step.
Exploring AddWarning()
The signature of this method is:
public static WarningElevation AddWarning
(int code, string message, DomainBase[] admonishedObjects, object[] otherIntegrities)
The first two parameters are self-explanatory. The domain objects could be the place in the domain layer where correlation state on every item level can be conveyed. Hence, I've declared a base class (though I'm in favour of POCO) DomainBase
with correlation state array in the domain layer as shown in the below code:
[Serializable]
public class DomainBase
{
[NonSerialized]
public string[] CorrelationStates;
}
You may be surprised that CorrelationStates
is an array, because there would be more than one warning possible during an interaction. Let us see the first part of AddWarning()
method.
string generatedCorrelationState = null;
int eludeCount = 0;
int admonishedCount = 0;
if (admonishedObjects != null && admonishedObjects.Length > 0)
{
string[] cstates = admonishedObjects[0].CorrelationStates;
bool verifyMode = (cstates != null && cstates.Length > 0 &&
!string.IsNullOrEmpty(cstates[0])) ? true : false;
generatedCorrelationState = new ObjectBytifier(verifyMode, code,
admonishedObjects, otherIntegrities).Stringified;
admonishedCount = admonishedObjects.Length;
eludeCount = admonishedObjects.Count(admonishedObj =>
{
return admonishedObj != null &&
admonishedObj.CorrelationStates != null &&
admonishedObj.CorrelationStates.Length > 0 &&
admonishedObj.CorrelationStates.Contains
(generatedCorrelationState);
});
}
This part will be executed when at least an object is given in the admonishedObjects
. If any of the objects in admonishedObject
contains CorrelationState
, this interaction will be decided as determined interaction. Every time, a correlation state is being generated and kept in generatedCorrelationState
variable by using ObjectBytifier
. This will be conveyed to the consumer for the first interaction. During the determined interaction, this is used to verify the one coming from the consumer. eludeCount
stores how many admonishedObjects
contain the correlation state during the determined interaction. Let us see the remaining part of AddWarning()
.
if (eludeCount == 0 && admonishedCount != eludeCount)
{
string language = string.Empty;
Acknowledgement currentAck = Acknowledgement.Current;
if (currentAck.Warnings == null)
Acknowledgement.Current.Warnings = new List<Warning>();
Warning fault = new Warning
{
Code = code,
Message = message,
CorrelationState = generatedCorrelationState
};
Acknowledgement.Current.Warnings.Add(fault);
Acknowledgement.Current.Status = 301;
return WarningElevation.Admonish;
}else return WarningElevation.Admonish;
When no correlation state is available in the admonishedObjects
or no matched correlation state, warning has been generated and added to the Acknowledgement
. Based on this, appropriate WarningElevation
has been returned. Let us see the another important object which generates correlation state.
Exploring ObjectBytifier
The ObjectBytifier
contains properties to set all interaction integrity elements AdmonishedObjects
, OtherIntegrities
, UserId
and WarningCode
. The OtherIntegrities
is a place holder wherein if you want to place anything other than domain objects, for example, in my sample, I've used this to place patient ID which is nothing but a string
, but required for integration integrity.
Exploring BytifyBaseObject()
Bytifier
property converts all interaction integrity elements into byte array and Stringified
element converts that byte array into fixed size string. This is what we called as CorrelationState
. BytifyBaseObjects()
is the vital method here. Let us see the initial part of it.
string salt = string.Format("{0}{1}{2}", "SALT",
UserId, WarningCode);
_bytified = ASCIIEncoding.UTF8.GetBytes(salt);
if (AdmonishedObjects != null && AdmonishedObjects.Length > 0)
{
BinaryFormatter serializer = new BinaryFormatter();
MemoryStream memStream = null;
for(int i = 0; i < AdmonishedObjects.Length; i++)
{
if (AdmonishedObjects[i] != null)
{
using (memStream = new MemoryStream())
{
serializer.Serialize(memStream, AdmonishedObjects[i]);
_bytified = _bytified.Concat
(memStream.ToArray()).ToArray();
memStream.Close();
}
}
}
}
The initial string
has been formed with salt, user id and warning code followed by all the AdmonishedObjects
are serialized using BinaryFormatter
and stored into _bytified
field.
if (OtherIntegrities != null && OtherIntegrities.Length > 0)
{
BinaryFormatter serializer = new BinaryFormatter();
MemoryStream memStream = null;
for (int i = 0; i < OtherIntegrities.Length; i++)
{
if (OtherIntegrities[i] != null)
{
using (memStream = new MemoryStream())
{
serializer.Serialize(memStream, OtherIntegrities[i]);
_bytified = _bytified.Concat
(memStream.ToArray()).ToArray();
memStream.Close();
}
}
}
}
_bytified = new MD5CryptoServiceProvider().ComputeHash(_bytified)
Followed by admonishedObjects
, OtherIntegrities
has been bytified. Finally a fixed hash has been generated by using System.Security.Cryptography.MD5CryptoServiceProvider
and stored again into _bytified
field.
Let us see the remaining in part 2 of this article.