Differences

This shows you the differences between two versions of the page.

Link to this comparison view

it:ad:patterns:dependency_wrapping_strategy [2019/03/24 12:02] (current)
Line 1: Line 1:
 +# IT:​AD:​Patterns:​Dependency Wrapping Strategy #
  
 +
 +
 +<callout type="​Navigation"​ class="​small">​
 +* [[../​|(UP)]]
 +{{indexmenu>​.#​2|nsort tsort}}
 +
 +
 +</​callout>​
 +
 +
 +<panel title="​Summary">​
 +
 +The worst thing you can do for the portability and longevity of your code is to tie to a vendor of some kind ([[IT/​AD/​Patterns/​Vendors Suck Strategy/​]]). ​
 +
 +The reason is that everything gets stale at point. DbConnections have had a long lifespan, and lasted a good 15 years, but even they are looking stale in an era of NoSql, etc.
 +UI classes get staler much faster (WPF, we barely knew thee...).
 +
 +
 +</​panel>​
 +
 +
 +## Process ##
 +
 +The way around it is to ensure that as few assemblies as possible have References to the various vendor assemblies that the application needs (as expressed in [[IT/​AD/​Patterns/​Vendors Suck Strategy/​]],​ that's the purpose of the [[IT/​AD/​Patterns/​DDD/​Components/​App.Infrastructure|Infrastructure]] Component within a [[IT/​AD/​Patterns/​DDD/​]] application.
 +
 +But the problem isn't completely resolved just by this. Without further work, the References Bleed through to rest of the application.
 +
 +What I mean by that is the [[IT/​AD/​Patterns/​Service Pattern/]] created within those assemblies will be working with 3rd party vendor classes contain information that you want to pass back to the rest of the application:​
 +
 +<sxh csharp>
 +    namespace SuperWeatherVendor.Weather;​
 +    ​
 +    public class SomeWeatherService :​ISomeService {
 +      ​
 +      private SuperWeatherService _superWeatherService;​
 +    ​
 +      public SomeWeatherService(){
 +          _superWeatherService = new SuperWeatherService();​
 +      }
 +      //returns a vendor object:  ​
 +      public WeatherInfo GetByCity(int id) {
 +         ​return _superWeatherService.GetByCityId(id);​
 +      }
 +    }
 +</​sxh>​
 +
 +The problem with the above code is that any class that references this service from another assembly in order to use the returned WeatherInfo,​ is going to have to have a Reference to the SuperWeatherVendor assembly before it will compile. Not good at all, as you're dragging a long term dependency on a 3rd part product all through your code. [[IT/​AD/​Patterns/​Vendors Suck Strategy|Vendors Suck]], but now so does the code.
 +
 +You have several strategies to consider.
 +
 +#### Project the WeatherInfo to another object ####
 +
 +Maybe the returned WeatherInfo comes back with tonnes of stuff that your app does not need. In other words, maybe it comes back with a collection of temperatures for that city taken every 5 minutes since the beginning of the day. Or the directions of the wind every hour. Or the time the tide is coming in...and all you really wanted was the current temp and current/​projected cover.
 +
 +In such a case, get rid of the extra load and project the fields you need into a new class.
 +
 +<sxh csharp>
 +    namespace SuperWeatherVendor.Weather;​
 +    ​
 +    public enum WeatherType {Unknown,​Sunny,​Rainy,​etc.};​
 +    ​
 +    //A custom class to project the vendor'​s result into, and return to upper layers:
 +    public class WeatherSummary {
 +      public double Temp {
 +        get;
 +        set;
 +      }
 +      ​
 +      public WeatherType Cover {
 +        get;
 +        set;
 +      }
 +    }
 +
 +    public class SomeWeatherService :​ISomeService {
 +      ...
 +      //returns our custom result object:  ​
 +      public WeatherSummary ​ GetByCity(int id) {
 +         ​WeatherInfo weatherInfo = _superWeatherService.GetByCityId(id);​
 +
 +         //Map the vendor object into a custom projection
 +         ​WeatherSummary weatherSummary = new WeatherSummary {Temp = weatherInfo.Temps[0].Last(),​ Cover=weatherInfo.CurrentWeather.MapTo();​ };
 +
 +         //​before return it:
 +         ​return weatherSummary;​
 +      }
 +      ...
 +    }
 +</​sxh>​
 +
 +Now, as the invoker is getting back a class defined in the same assembly as the service it invoked, it can stay blithfully ignorant of the depencies. No Vendor Bleed occurred.
 +
 +Note that in the above example, mapping ints and strings is trivial, but you had to define a MapTo() extension method to map whatever the vendor returned to your enum, etc. It's not difficult, and you'll find that an [[IT/​AD/​Patterns/​Service Pattern|ObjectMappingService]] can help considerably with this kind of rote type mapping.
 +
 +
 +#### Vendor Wrapping ####
 +
 +But it's not always that straight forward. For example, when wrapping compex objects like objects returned by ActiveDirectory,​ the properties are not all hydrated. Some of the properties accessed are empty until accessed, at which point the entity has enough smarts to contact the LDAP/AD server for additional information. ​
 +
 +Therefore, accessing such properties to project them onto another object becomes much more costly than using the original ActiveDirector result object, and it's not the recommended way to go about it.
 +
 +An alternate solution is to wrap the returned vendor object (in this example an GroupPrincipal) within a custom object, with get/set properties that access the inner object. ​ Think of it as passing around a custom app-specific egg shell, containing the vendor'​s yolk...except that the code invokees can't only get to the yolk's properties indirectly:
 +
 +<sxh csharp>
 +
 +  public class MyADObject {
 +      ADThing _innerObject ;
 +  ​
 +    public MyADObject(object myInnerOBject) {
 +      if myInnerObject is not of right type..throw an exception...
 +      _innerObject = myInnerObject;​
 +    }
 +    ​
 +    public string Name { 
 +      get {return _innerObject.Name;​}
 +      set {_innerObject.Name = value;}
 +    }
 +    ...etc...
 +  }
 +</​sxh>​
 +
 +You then use the above object as follows:
 +
 +<sxh csharp>
 +      ...
 +      //returns a wrapping object:  ​
 +      public MyADObject GetByName(string name) {
 +         ​return new MyADObject( _innerADService.GetByName(name));​
 +      }
 +      ...
 +</​sxh>​
 +
 +The advantage of the above wrapping strategy -- beyond the fact that it doesn'​t trigger a fetch of additional information until actually needed -- is that one can pass around this object without causing a reference to the namespace/​assembly of the inner object. It's ...cloaked, if you will. 
 +
 +The end result -- with a little work -- you can safely use 3rd party vendor code while still adhering to [[IT/​AD/​Patterns/​DDD/​]] design strategies of not polluting your code with vendor rot.
 +
 +** Important:​**  ​
 +The above AD example has one more issue to cover that comes up in 4-Tier ([[IT/​AD/​Patterns/​Tier Strategy/​]]) applications... When the wrapping object is passed up to the upper tier it will get serialized by WCF...which will trigger a lazy fetch of additional data for all the properties. Oops.
 +
 +The strategy I use in such cases is to then map the above wrapper to another object for upper layers, that contains only the variables I want. As the upper layer was going to serialize these objects anyway if it was a pure AD object -- I was going to have to project them anyway... There'​s no escaping some call back if it was going to occur.
 +
 +
 +## Resources ##