Architecture

  • Dec 5 2009 - build it (so it's easy) and they will come (make the right decision) ...

    One of the biggest lessons I think I’ve learned over the past few years is that you have to be very careful with what you make easy to do in a software system.



    When you are working within a preexisting system, it is very hard to work effectively outside the bounds of that system. Whether you are limited by time constraints, peer pressure, political decisions or just pure technical inertia, those early/legacy decisions in a system will have long reaching impacts on the decisions of those who follow.

    I’ll give you an example. On a product I worked on for years, the decision to use an object relational mapper (ORM) in early stages was based on a desire to eliminate boilerplate code, reduce the learning curve for new developers and generally push the development of new entities down to the entire team rather than specializing this role in one person.  All in all the reasoning was sound, but the inability to see some of the psychological aspects that would impact the future had some serious impact on the future of the system.
    1. Developers stop thinking about database impacts because they never really SEE the database
    2. The object model and the schema become inexorably tied
    3. Accessibility to the DAL ends up being given to junior developers who may not have otherwise dealt with it yet.** 
    4. Things that could reasonably be built OUTSIDE the ORM end up dropped in without consideration because developers are following the template. 
    This can be avoided by having data contracts that are specific to the DAL
    ** There is nothing inherently wrong with ORM, and if your DAL is properly abstracted so that the ORM isn’t propagated throughout the stack then this too isn’t necessarily a problem. 

    I add those two caveats because I really don’t have an issue with ORM, in fact I think used properly it makes way more sense then to waste days of development doing repetitive and simple CRUD work.

    However the deeper issues that arose for us still focused on convenience. It was convenient to expose the friendly querying methods of the domain objects that mapped to our tables directly to the business logic assemblies. It was convenient to let junior developers write code that accessed those objects as if retrieval and persistence were magically O(1) operations. Of course in reality we discovered embarrassingly late that we had more than a few graphs of objects that were being loaded upon traversal, leading to a separate mapper triggered SELECT for each object and its children. This is the kind of thing that only becomes apparent when you test with large datasets and get off of your local machine and see some real latency.

    And yes, in this case, I think you could argue that QA dropped the ball here. But as a professional software developer you really never want to see issues like this get that far.

    I’ve picked one example, but there are many many others in the system I’m referring to. Including but not limited to a proliferation of configuration options and files, heavy conceptual reuse of classes and functionality that are only tangentially related to each other, and an increasing reliance on a “simple” mechanism to do work outside the main code base.

    Ultimately, this post has less to do with ORM and proper abstraction and more to do with understanding how your current (and future) developers will react to those decisions. I think a conscious effort has to be paid to how a human will game your system. You need to come up with penalties for doing dumb things if possible, and the path of least resistance for the right ones.  There are entire books dedicated to framework and platform development that encompass some of these ideas, but they apply at every level really in my opinion. (except maybe the one man shop?)



  • Sep 1 2009 - lessons learned from online gambling - predicting scalability ...

    I work with someone who has spent a few years working for an online poker company who shall remain nameless. This company was responsible for a poker platform that supported both their own branded poker offering as well as being an engine for other companies who would layer on their branding. My colleague played an important role in taking their fairly well built existing system from thousands of users to tens of thousands of users, and in the process exposing a large handful of very deep bugs, some of which were core design issues.

    Looking at this site http://www.pokerlistings.com/texas-holdem and seeing just this small sample of some of the top poker sites is a bit insane.  We’re talking close to 100,000 concurrent players at peak hours JUST from the 16 top “texas hold-em” sites listed here. Who are these people? (I’m totally gonna waste some money online one of these days by the way) I’m sure this is just the tip of the iceberg too.  


    It’s a great domain for learning critical systems in my opinion. Real time, high concurrency, real money, third party integrations for credits and tournaments and the vast reporting that goes on for all that data being generated.

    My colleague’s experience in dealing with real time load and breaking the barriers of scalability are truly fascinating and a good source of learning for me. While I realize there are bigger puzzles out there, but it’s not every day you have direct access to that experience where you work.  In any case, one such learning that I am in the process of trying to apply is a more forward looking approach to load modeling. That is, rather than to simply design and test for scalability; to actually drill down into the theoretical limit of what you are building in an attempt to predict failure.

    This prediction can mean a lot of different things of course, being on a spectrum with something like a vague statement about being IO bound to much more complicated models of actual transactions and usage to enable extrapolating much richer information about those weak points in the system. In at least one case, my boss has taken this to the point where the model of load was expressed as differential equations prior to any code being written at all. Despite my agile leanings I have to say I’m extremely impressed by that. Definitely something I’d throw on my resume. So I’m simultaneously excited and intimidated at the prospect of delving into our relatively new platform that we’re building in the hopes of producing something similar. I definitely see the value in at least the first few iterations of highlighting weak points and patterns and usage. How far I can go from there will be a big question mark.

    For now I’ll be starting at http://www.tpc.org/information/benchmarks.asp and then moving into as exhaustive list as I can of the riskiest elements of our system. From there I’ll need to prioritize and find the dimensions that will impact our ability to scale. I expect with each there will be natural next steps to removing the barrier (caching, distributing load, eliminating work, etc) and I hope to be able to put a cost next to each of those.

    Simple!

  • Sep 1 2009 - sometimes it's helpful to think about what NOT to do ...

    Came across this list of “anti-patterns” on wikipedia tonight. I’m tempted just to copy and paste the contents here but that would make me feel dirty.

    Definitely a good list though and something worth reminding ourselves of every once in a while when thinking about the systems we build.

    http://en.wikipedia.org/wiki/Anti-pattern

  • May 8 2009 - Simple Extensibility in .NET ...

    I’ve used this approach a few times when I essentially need a really simple plugin / provider model within my applications so I thought I’d jot down the relevant details here for posterity using an old project for adding post commit hooks to subversion.

    Consider this a somewhat simplistic approach, not suitable for production code without a bit more plumbing. If you are going all out and need true add-in’s for your .NET based product I recommend checking out the managed add-in framework , very robust stuff and not that hard to implement. In a lot of cases though the isolation, discoverability, communication pipelines etc are a bit overkill. The example I’ll show is a subversion hook that allows for very simple addition of new .NET “actions” to execute on PostCommit. In this case the “add-ins” are only written in house, and editing a config file to hook them up is completely acceptable etc etc.

    The solution

    Subversion.Contracts : This project is the bridge between our dispatcher and the plugins that will do the work.
    Subversion.Plugins : Any of the actions we wish to take post commit are added here, but could just as easily be distributed across as many assemblies and projects as necessary as long as they reference the contracts.
    Subversion.Dispatcher : This is the console application that actually receives the arguments from subversion and translates them into our contracts, then executes the appropriate actions (note no references to the plugins project)



    The Contract

    The contracts are relatively simple, but whatever you put in them this is the interface for the “plugin” that will need to implement. In our case this is IPostCommitHandler :

    using System;

    namespace Subversion.Contracts
    {
    public interface IPostCommitHandler
    {
    void ExecuteCommand(PostCommitArgs a);
    }
    }

    Pretty simple, essentially just a “do whatever you want” method that passes the arguments from subversion wrapped up in a simple class. See the attached zip if you want the guts of the subversion specific stuff.


    The Plugin

    using System;
    using Subversion.Contracts;

    namespace Subversion.Plugins
    {
    public class ExecuteForAllCommits : IPostCommitHandler
    {
    #region IPostCommitHandler Members

    public void ExecuteCommand(PostCommitArgs a)
    {
    SendEmailNotification.SendEmail(a.Argument, a.Revision);
    }

    #endregion
    }
    }


    Again, very simple and in this case we’re passing off the execution to a static class that again is not shown, but what gets executed isn’t all that important in this case.. simply fill in what you need.

    The Dispatcher (Plugin Host)

    using System;
    using System.Collections;
    using System.Text.RegularExpressions;
    using System.Configuration;
    using Subversion.Contracts;

    namespace Subversion.Dispatcher
    {
    ///
    ///
    /// Summary description for PostCommit.
    ///

    class PostCommit
    {

    private static string subversionPath = ConfigurationSettings.AppSettings[“SubversionPath”];

    static void Main(string[] args)
    {
    SubversionRevision rev = ParseRevision(args);
    ArrayList commands = DispatchGlobalCommands(rev);
    DispatchNamedCommands(rev, commands);
    }

    private static SubversionRevision ParseRevision(string[] args)
    {
    SubversionRevision rev;
    if (args.Length == 2)
    {
    rev = new SubversionRevision(subversionPath, args[0], args[1]);
    }
    else
    {
    rev = new SubversionRevision(subversionPath, string.Empty, string.Empty);
    }
    return rev;
    }

    private static void DispatchNamedCommands(SubversionRevision rev, ArrayList commands)
    {
    string[] commitLines = rev.CommitLog.Split(Environment.NewLine[0]);
    // Handle Named Commands
    string registeredCommands = String.Join("|", (string[])commands.ToArray(typeof(string)));
    Regex CommandSearch = new Regex(@"(" + registeredCommands + @")\s*:\s*(.+)?", RegexOptions.IgnoreCase);
    foreach (string line in commitLines)
    {
    string lowerline = line.ToLower();
    for (Match Matches = CommandSearch.Match(lowerline); Matches.Success; Matches = Matches.NextMatch())
    {
    string handlerString = ConfigurationSettings.AppSettings[“command:” + Matches.Groups[1].ToString()];
    DispatchCommand(handlerString, Matches.Groups[2].ToString(), rev);
    }
    }
    }

    private static ArrayList DispatchGlobalCommands(SubversionRevision rev)
    {
    // Handle global commands
    ArrayList commands = new ArrayList();
    for (int i = 0; i < ConfigurationSettings.AppSettings.Count; i++)
    {
    string key = ConfigurationSettings.AppSettings.GetKey(i);
    string val = ConfigurationSettings.AppSettings.Get(i);
    string[] cmdParts = key.Split(’:’);
    if (cmdParts.Length == 2 && cmdParts[0] == “command”)
    {
    if (cmdParts[1].StartsWith(""))
    {
    DispatchCommand(val, cmdParts[1].Substring(cmdParts[1].IndexOf(",") + 1), rev);
    }
    else
    {
    commands.Add(cmdParts[1]);
    }
    }
    }
    return commands;
    }


    ///
    /// Call the appropriate method for the command name given with the argument given
    /// no processing of the argument happens here.
    ///

    private static void DispatchCommand(string handlerString, string argument, SubversionRevision rev)
    {
    // We don’t want properly configured commands to stop working because of errors so trap
    // everything here…
    try
    {
    if (handlerString != null && handlerString.Length > 0)
    {
    string[] typeAndAssembly = handlerString.Split(’,’);
    if (typeAndAssembly.Length == 2)
    {
    System.Reflection.Assembly a = System.Reflection.Assembly.Load(typeAndAssembly[1]);
    System.Type t = a.GetType(typeAndAssembly[0], true);
    object handler = System.Activator.CreateInstance(t);
    if (handler is IPostCommitHandler)
    {
    ((IPostCommitHandler)handler).ExecuteCommand(new PostCommitArgs(argument,rev));
    }
    }
    }
    }
    catch (Exception)
    { //TODO: log errors
    }
    }
    }
    }


    There is some plumbing in this class that isn’t directly related to this post, but I’ve left it all anyway. Subversion will run this command every time a checkin is made, and the process ends and starts over again each time. This allows for some pretty simple handling of loaded assemblies and whatnot, if you have a longer running process or are dealing with some scale be cautious. ;-)

    The Main function has two jobs, parse and create the revision, then read the application configuration file and start issueing commands for the received revision. Commands are in two parts, those defined in config to be executed always (global commands) and those that are interpreted from the subversion commit log itself, parsed out and executed with arguments from the revision log.

    Here are some example commands defined in the config
    <!– Commands –>
    <add key=“command:
    ,chris” value=“Subversion.Plugins.ExecuteForAllCommits,Subversion.Plugins” />
    <add key=“command:,check-ins” value=“Subversion.Plugins.ExecuteForAllCommits,Subversion.Plugins” />
    <add key=“command:bug” value=“Subversion.Plugins.UpdateBugTracker,Subversion.Plugins” />
    <add key=“command:cc” value=“Subversion.Plugins.SendEmailNotification,Subversion.Plugins” />


    • in the key we have “command:[name]” signifies a command arriving in a revision where somewhere in the revision log we’ll see the command name followed by a colon, anything following the colon is then passed to the plugin as an argument. If the name is an asterisk then we simply execute for all, with an optional argument being passed to the plugin. (so the first example emails chris for all revisions, and the second emails an account named check-ins
    • the value portion here is what directs the program where to look for the appropriate plugin and class to execute. I copied the format I found in a web.config file which is to put the class name followed by the assembly name separated by a comma.

    In retrospect if I were doing something similar again I’d probably create a better structured format rather than relying on all this string parsing… but old code is what it is in this case.

    Finally we call DispatchCommand for each parsed out command which is the last piece of this old code that I’m attempting to document here for reuse. DispatchCommand will read the class name and assembly name, load the assembly name and attempt to instantiate the class/type named in order to call it using our IPostCommitHandler interface.

    There are a few ways to do this, and for this project I’m simply calling “System.Reflection.Assembly.Load” which relies on the fact that my plugins are located in my bin directory. I’ve also done this using a “plugin store” which is a fancy way to say I had a dynamic path configured that I could read my assemblies from. In this case you can use LoadFile or LoadFrom, LoadFrom will load dependencies automatically while LoadFile loads just the assembly and will potentially load duplicate copies. (see the documentation) In order to get the dll’s in place for this project we just simply add a post build event like so…

    copy $(TargetDir).* $(SolutionDir)\Subversion.Dispatcher$(OutDir)

    If after instantiating the named type from the loaded assembly we actually have an IPostCommitHandler then make the call! Done.

    System.Reflection.Assembly a = System.Reflection.Assembly.Load(typeAndAssembly[1]);
    System.Type t = a.GetType(typeAndAssembly[0], true);
    object handler = System.Activator.CreateInstance(t);
    if (handler is IPostCommitHandler)
    {
    ((IPostCommitHandler)handler).ExecuteCommand(new PostCommitArgs(argument,rev));
    }

    So that’s that. You can download the code here - it should basically work as is if you are looking for a shortcut to extending subversion with .NET. I was relatively lazy with getting this posted - so if you got this far, can use the code, and have problems with it leave a comment and I’ll try to help if I can.

  • Sep 6 2008 - frequent beachballs in mac os x caused by bad fonts ...

    I’ve been testing the latest nightly builds of firefox 3.1 over the past few days and while generally impressed with the performance improvements in javascript was quite disapointed that it was causing my iMac to go into frequent hangs where I would see the spinning beachball of death for many seconds before I could continue working again. It became bad enough that I finally had to ditch my firefox testing efforts.

    Much to my chagrin the problem continued well after I had stopped using good ole Minefield. I began to explore running processes via activity explorer and just generally clean up my machine. So after uninstalling a bunch of apps and services I wasn’t running anymore and still experiencing the same problem.

    At this point I was worried because the cpu was not the issue, iTunes would continue to play with no problem (and even respond to the hotkeys on my keyboard for switching tracks) it was just the UI that was freezing, and generally as I was opening files. Disk issue? Memory? Maybe even something where the network was introducing some latency? After some googling and consternation over my potentially failing disk I finally did what I should have in the first place and started to dig into the system logs via Console.

    Sure enough one group of messages stood out right away…

    06/09/08 9:21:29 PM com.apple.ATSServer[14462] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    06/09/08 9:21:29 PM com.apple.ATSServer[14462] 2008.09.06 21:21:29.63
    06/09/08 9:21:29 PM com.apple.ATSServer[14462] ATSServer got a fatal error (status: -4) while processing a message (id: 20) from pid=14309.
    06/09/08 9:21:29 PM com.apple.launchd[386] (com.apple.ATSServer) Throttling respawn: Will start in 10 seconds
    06/09/08 9:21:39 PM com.apple.ATSServer[14465] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    06/09/08 9:21:39 PM com.apple.ATSServer[14465] 2008.09.06 21:21:39.90
    06/09/08 9:21:39 PM com.apple.ATSServer[14465] ATSServer got a fatal error (status: -4) while processing a message (id: 20) from pid=14309.
    06/09/08 9:21:39 PM com.apple.launchd[386] (com.apple.ATSServer) Throttling respawn: Will start in 10 seconds
    06/09/08 9:21:41 PM quicklookd[14463] [QL ERROR] ‘Creating thumbnail’ timed out for ‘<QLThumbnailRequest /Library/Fonts/LiberationSans-Regular.ttf>’
    06/09/08 9:21:50 PM Console[14309] Failure with ATSFontGetUnicodeCharacterCoverage(). Disabling font fallback optimization for characters not renderable.
    06/09/08 9:21:51 PM com.apple.ATSServer[14468] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    So the ATSServer (Apple Type Server) was choking and it looks like the beachballs I was seeing were related to the throttling of the respawn of the server. So I’d be staring at a beachball for up to 10 seconds while the rest of the OS hummed along fine. A quick google search revealed the most common reason for this kind of failure is a corrupt font. At this point I shot around in my chair and asked my wife pleadingly and only somewhat accusatorily whether she had by any remote chance installed any fonts lately…..  Yes.  So I gave her control of my screen so she could clean up what had been added and like magic the beachballs ended. No reboot or anything required, ATSServer has not crashed since and I am writing this in Minefield 3.1b1pre with no problems! (sorry to blame you firefox)

    Sadly I do not have the patience to go through the exercise of finding which fonts specifically caused the problems. I really don’t have much use for the extra fonts so I’m just as happy to have them all gone. Still hopefully this helps someone.

    Now if I could just clean up all these damn mds errors that keep cropping up …
    mds[34]: (Error) Import: importer:0x84b600 Importer start failed for 501 (kr:268435459 (ipc/send) invalid destination port)

  • Jul 21 2008 - Documenting architecture ...
    One of my side projects at work right now is documenting the architecture of a product that has already been built but will be going through a re-architecting with a focus on a more robust schema and applying some of the learning we've gone through in discovering exactly how our product is being used and ways in which our users want to extend the platform. SaaS and SOA are two good buzz words we'll be throwing around a lot, although to be honest we've been in the SaaS model for years now, just not following all of the best practises. (examples, check out litwareHR)

    So despite documentation being at the heart of the architect's role I find it extremely difficult to find good documentation on how to approach a task like this. I have Craig Larman's book Applying UML Patterns which I've enjoyed, but I still find myself grappling for where to even begin sometimes.

    These articles on IBM have been good reads for this and I'd reccommend giving them a read if you are facing similar challenges.

    Part1
    http://www.ibm.com/developerworks/library/ar-archdoc1/index.html?S_TACT=105AGX20&S_CMP=EDU

    Part2
    http://www.ibm.com/developerworks/library/ar-archdoc2/index.html?S_TACT=105AGX20&S_CMP=EDU

    Part 3
    http://www.ibm.com/developerworks/library/ar-archdoc3/index.html?S_TACT=105AGX20&S_CMP=EDU

    I'm assuming there will be more of these which I'm looking forward to.
  • Jul 17 2008 - Microsoft's add-in framework and the need for diligence ...
    We've recently put Microsoft's managed add-in framework (part of .NET 3.5) into very effective use building a plug-in system for a large asp.net application at work. Essentially the framework in place allows other developers (and our own team for out of stream releases) to develop new functionality for our platform that runs the entire life-cycle for a given widget. In our case for this particular widget we're talking about plugins being responsible for up to 4 asp.net controls in different contexts (for example data collection and reporting as two separate controls) as well as a script injection point where plug-ins are able to extend the scriptability of our platform. 
    For us going with the framework gave us a few things we didn't have with our original design for the add-ins. 
    1. Tools to help enforce the pattern
    2. An extra layer of versioning over the somewhat naive approach we started with
    3. Built in discovery, provisioning, and a communication pipeline for serializing types and calls across the contracts that make up the interface between host and plugin
    4. And last but not least support from Microsoft. This is somewhat more minor than the points above, but it helps legitimize our design when we are following the best practices laid out by Microsoft and used by others in similar situations. The documentation and training available also make getting other developers up to speed on the framework that much easier.
    There have been numerous challenges in using the framework, but perhaps the most surprising of all for me was the human element and how simple it became over the life of the project to break the pattern by coupling components across or outside the pipeline.

    Examples :
    1. Referencing an assembly from both the addin and the host that shared code that should have been passed across the pipeline. 
    2. Bypassing the pipeline completely by calling web services from the addin code (client side or server side calling code) 
    3. Conditional code in the host making decisions based on the type of the addin
    4. Loose coupling based on common knowledge (that shouldn't be common) 
    These all basically come down to a breach of contract or an absence of contract for various operations that we needed addins to handle. On some level all of these things can be excused and safely done without compromising the framework if they are done right. It's a slippery slope though and requires a commitment to not be lazy to avoid the temptation to sidestep the pipeline.

    In the case of #1 above the shared assembly started off very benign. Essentially some shared utility code for handling urls and some common resource tasks. Why rewrite when that code already existing the main project? Break it off from the project so that it has no dependencies then drop it in. Except that slowly the terrible pain of building contracts, views and adapters for every little interface or interface change drives you towards shortcuts. "Oh I'll just put this code here to test and then fix it later"  Even worse are those cases where you've chosen the path of least resistence in dealing with a bug resulting from unexpected behavior with serialization across the pipeline. It only took a few weeks of not being completely on top of this before I discovered our project was littered with types that were being shared directly between host and addin. Any change meant a recompilation of both projects, completely defeating the purpose.

    #2 is a legitimate need in our scenario, and we've found ourselves needing to creating proxy services that wrap our own services just to protect against the inevitable change that will follow. Given that third party developers may be writing code for the platform we have to make an effort to protect from change in all of our interfaces, web service or otherwise. In retrospect I think it would have made more sense to strictly enforce a team division so that no one writing addin code was also writing host code.This probably would have gone a long way to preventing these types of problems.

    #3 and #4 are a little more insidious and harder to spot without strict code review. #3 for us isn't technically breaking anything in terms of the interface or future versioning, but adds cruft and generally points to a missing method or property on the interface. The last thing you need as the host is to have case statements littered throughout your code looking for addins. #4 took many forms, and in some cases it's fine. An ok example might be sharing enums, which provided they are defined in the contracts or slightly worse something like a utility class is ok. A not ok example for me was code like this :  extension.GetSetting("Menu_Text");  which in this case has two errors. One "GetSetting" shouldn't really exist because how an addin chooses to configure itself should be transparent to the host. Second this code depends on the addin having a value defined in it's config file for the key "Menu_Text". This is next to impossible to enforce and can of course easily break.

    Replacing this with extension.MenuText; should be trivial, and a no-brainer. When we started using the framework back in December we were rolling the supporting code by hand. To give you a sense of what this entails, this is how you would define an extension who's only job is to return MenuText as in the code above :

    IExtensionContract.cs
    using System.AddIn.Pipeline;
    using System.AddIn.Contract;

    namespace SimpleExtensionContracts
    {
    [AddInContract]
    public interface ExtensionContract : IContract
    {
    string MenuText { get; set; }
    }
    }

    IExtension.cs
    namespace SimpleExtensionContracts.AddInViews
    {

    [System.AddIn.Pipeline.AddInBaseAttribute()]
    public interface IExtension
    {
    string MenuText
    {
    get;
    set;
    }
    }
    }

    IExtension.cs
    namespace SimpleExtensionContracts.HostViews
    {

    public interface IExtension
    {
    string MenuText
    {
    get;
    set;
    }
    }
    }

    IExtensionContractToViewHostAdapter.cs
    namespace SimpleExtensionContracts.HostSideAdapters
    {

    [System.AddIn.Pipeline.HostAdapterAttribute()]
    public class IExtensionContractToViewHostAdapter : SimpleExtensionContracts.HostViews.IExtension
    {
    private SimpleExtensionContracts.ExtensionContract _contract;
    private System.AddIn.Pipeline.ContractHandle _handle;
    static IExtensionContractToViewHostAdapter()
    {
    }
    public IExtensionContractToViewHostAdapter(SimpleExtensionContracts.ExtensionContract contract)
    {
    _contract = contract;
    _handle = new System.AddIn.Pipeline.ContractHandle(contract);
    }
    public string MenuText
    {
    get
    {
    return _contract.MenuText;
    }
    set
    {
    _contract.MenuText = value;
    }
    }
    internal SimpleExtensionContracts.ExtensionContract GetSourceContract()
    {
    return _contract;
    }
    }
    }

    IExtensionHostAdapter.cs
    namespace SimpleExtensionContracts.HostSideAdapters
    {

    public class IExtensionHostAdapter
    {
    internal static SimpleExtensionContracts.HostViews.IExtension ContractToViewAdapter(SimpleExtensionContracts.ExtensionContract contract)
    {
    if (((System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract) != true)
    && contract.GetType().Equals(typeof(IExtensionViewToContractHostAdapter))))
    {
    return ((IExtensionViewToContractHostAdapter)(contract)).GetSourceView();
    }
    else
    {
    return new IExtensionContractToViewHostAdapter(contract);
    }
    }
    internal static SimpleExtensionContracts.ExtensionContract ViewToContractAdapter(SimpleExtensionContracts.HostViews.IExtension view)
    {
    if (view.GetType().Equals(typeof(IExtensionContractToViewHostAdapter)))
    {
    return ((IExtensionContractToViewHostAdapter)(view)).GetSourceContract();
    }
    else
    {
    return new IExtensionViewToContractHostAdapter(view);
    }
    }
    }
    }

    IExtensionViewToContractHostAdapter.cs
    namespace SimpleExtensionContracts.HostSideAdapters
    {

    public class IExtensionViewToContractHostAdapter : System.AddIn.Pipeline.ContractBase, SimpleExtensionContracts.ExtensionContract
    {
    private SimpleExtensionContracts.HostViews.IExtension _view;
    public IExtensionViewToContractHostAdapter(SimpleExtensionContracts.HostViews.IExtension view)
    {
    _view = view;
    }
    public string MenuText
    {
    get
    {
    return _view.MenuText;
    }
    set
    {
    _view.MenuText = value;
    }
    }
    internal SimpleExtensionContracts.HostViews.IExtension GetSourceView()
    {
    return _view;
    }
    }
    }

    IExtensionAddInAdapter.cs
    namespace SimpleExtensionContracts.AddInSideAdapters
    {

    public class IExtensionAddInAdapter
    {
    internal static SimpleExtensionContracts.AddInViews.IExtension ContractToViewAdapter(SimpleExtensionContracts.ExtensionContract contract)
    {
    if (((System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract) != true)
    && contract.GetType().Equals(typeof(IExtensionViewToContractAddInAdapter))))
    {
    return ((IExtensionViewToContractAddInAdapter)(contract)).GetSourceView();
    }
    else
    {
    return new IExtensionContractToViewAddInAdapter(contract);
    }
    }
    internal static SimpleExtensionContracts.ExtensionContract ViewToContractAdapter(SimpleExtensionContracts.AddInViews.IExtension view)
    {
    if (view.GetType().Equals(typeof(IExtensionContractToViewAddInAdapter)))
    {
    return ((IExtensionContractToViewAddInAdapter)(view)).GetSourceContract();
    }
    else
    {
    return new IExtensionViewToContractAddInAdapter(view);
    }
    }
    }
    }

    IExtensionContractToViewAddInAdapter.cs
    namespace SimpleExtensionContracts.AddInSideAdapters
    {

    public class IExtensionContractToViewAddInAdapter : SimpleExtensionContracts.AddInViews.IExtension
    {
    private SimpleExtensionContracts.ExtensionContract _contract;
    private System.AddIn.Pipeline.ContractHandle _handle;
    static IExtensionContractToViewAddInAdapter()
    {
    }
    public IExtensionContractToViewAddInAdapter(SimpleExtensionContracts.ExtensionContract contract)
    {
    _contract = contract;
    _handle = new System.AddIn.Pipeline.ContractHandle(contract);
    }
    public string MenuText
    {
    get
    {
    return _contract.MenuText;
    }
    set
    {
    _contract.MenuText = value;
    }
    }
    internal SimpleExtensionContracts.ExtensionContract GetSourceContract()
    {
    return _contract;
    }
    }
    }

    IExtensionViewToContractAddInAdapter.cs
    namespace SimpleExtensionContracts.AddInSideAdapters
    {

    [System.AddIn.Pipeline.AddInAdapterAttribute()]
    public class IExtensionViewToContractAddInAdapter : System.AddIn.Pipeline.ContractBase, SimpleExtensionContracts.ExtensionContract
    {
    private SimpleExtensionContracts.AddInViews.IExtension _view;
    public IExtensionViewToContractAddInAdapter(SimpleExtensionContracts.AddInViews.IExtension view)
    {
    _view = view;
    }
    public string MenuText
    {
    get
    {
    return _view.MenuText;
    }
    set
    {
    _view.MenuText = value;
    }
    }
    internal SimpleExtensionContracts.AddInViews.IExtension GetSourceView()
    {
    return _view;
    }
    }
    }

    Yeah, seriously. One interface and one string accessor requires nine class/interfaces and over 200 lines of code (which obviously could be made less with formatting etc).  It's also possible to share the views between addin and host but then you lose part of the more compelling robustness of the framework. If you are interested in where these classes come into play and how the add-in framework actually works check out this link for a good description.
    Anyway, I can sympathize with the developers in wanting to speed up the process a bit, but the answer is not to bypass the pipeline. The answer is code generation! Thankfully by the time we realized our mistake Microsoft had released a CTP of their pipeline generator which is a nifty little visual studio addin which picks up the output of the Contracts project and uses reflection to find all of the contracts and generate the necessary projects and files for the pipeline. It literally saved us tons of hours and made the addin framework actually usable. Of couse the code generation is only going to work until we version one side or the other, but at that point we should have solidified those interfaces considerably so it will matter a lot less.

    Anyway, long story short, the add-in framework is great, but it's really important for the entire team to understand the goal and be diligent in ensuring that all that extra framework code isn't just being wasted by introducing dependencies.
  • Mar 30 2008 - On the need for re-architecting ...




    I have been grappling with the concept of a rewrite of a relatively successful software product that I’ve been involved with since start-up mode. In truth I was googling for compelling stories about why NOT to disappear for a year to do a complete re-engineering of your product (ala Netscape). Mostly because I’m concerned about the size of the effort and the resources we have to pull it off. Instead came across this article, espousing the need to fire all your developers!

    http://mikemason.ca/blog/?p=19

    Well I’m the guy who’s been promoted to a more senior developer role (not architect, our group shares that responsibility) but still by Mike’s logic the guy who should be fired (?!). I’m the guy who now is learning the ropes in management and who happens to really know the product and the business but now swimming in very exciting waters when it comes to our rearchitecting plans.

    What I do have going for me is an incredibly strong team to work with. I’m undecided yet as to whether we actually need an ivory tower architect or if our guys can work this with the guidance of our CTO. We’ll see, as it’s still not too late to change course.

    In any case I think I’ll keep an eye on more perspectives like Mike’s…