Latest Replies
Wednesday
Oct212009

Infrastructure Shards and Optional Components

Component-driven development really helps to deliver multiple applications and systems efficiently. It happens because:

  • important functionality is abstracted in components;
  • components are coded and unit-tested only once;
  • every component has multiple use cases that provide additional feedback and help to find problems or limitations, if there are any;
  • whenever a component is fixed or improved, all its consumers would benefit from that automatically.

As we go up in the complexity of the development, it becomes too cumbersome to manage components on the individual basis. That's where infrastructure shards come into the play.

Infrastructure Shards

Infrastructure shard is a group of components that work together to carry out some specific job in the application or system. They are joined to simplify reuse in multiple applications and contexts.

Normally all these components are developed to support Inversion of Control, but if needed they could be wired together manually. In the IoC scenario, components are fit together using registration modules (or xml configuration blocks for the legacy containers). In the rare cases, when IoC context is not available (i.e.: we need to offer minimal feedback reporting to deal with possible unhandled exceptions on startup), a simple factory method would do the job.

A single infrastructure shard could join components coming from the different libraries and application blocks.

Feedback infrastructure shard (sources), for example, would include:

  • Model-View-Controller elements:
    • User control view to be shown in the currently available IViewspace;
    • Immutable models used to pass feedback reports and information around;
    • Controller responsible for managing the view and user interactions;
    • Validation rules injected into the view in order to check and enforce user input.
  • Feedback helper components, aggregating system information to be attached to the report.

However, if we attempt to start our feedback reporting window, just with these few components, we would fail. This would happen because usually in the IoC environment an infrastructure shard is not self-sufficient. It has to interact with the other components and services as well. The view, for instance, does not even have a window to present itself in; it is merely a user control:

So in order for the shard to work, we need to provide some external services. These are really easy to spot in the code if we consistently stick to the guidelines of:

  • only constructor injection is used (no property injection);
  • every component has only a single constructor;
  • components can inherit only from the interfaces (except for the views that have to derive from some user control).

Given that we could find out that core feedback service components use these components:

  • IViewspace - responsible for displaying view properly in the UI environment available;
  • IReportService - responsible for establishing contact with the communication service shard (which will handle all the exception handling and reliability on its own) and sending the feedback report;
  • IClientInteractions - provides UI-specific methods that allow to interact with user;
  • Optional{ServiceConnection}.

The last item is the most interesting one here. Here's why and how it works.

Optional Components

When we show feedback report window - we want to suggest user to use his existing email address and connection information. This way the feedback could be routed to the appropriate server and associated with the user's account.

And we want to have code working in situations, when user context is not available (i.e.: user logged off or context is not even known).

That's what the Optional{ServiceConnection} does. It allows to pass some information via IoC container, while indicating that the reference could be empty at the moment of retrieval. It's declaration is extremely simple:

/// <summary> Simple delegate that returns optional result </summary>
public delegate Maybe<TResult> Optional<TResult>();

That's it. Yes, that resembles Lazy of MEF, but is a bit simpler, does not require MEF libraries and can benefit from the synergy with Maybe Monad.

For example, if we want to get current user name in one of the views (if the user name is available), the code might look like:

// somewhere in the class
readonly Optional<ServiceConnection> _contextProvider;

// somewhere in the code
var sender = _contextProvider()
  .Convert(sc => sc.Username)
  .GetValue(AnonymousUserName);

While the registration of the optional might look like:

// extension method for Autofac ContainerBuilder
public static void RegisterExport<TComponent, TExported>(
  this ContainerBuilder builder,
  Func<TComponent, Maybe<TExported>> func)
{
  builder.Register<Optional<TExported>>(c => 
    () => func(c.Resolve<TComponent>()));
}

// actual registration
builder.RegisterExport<IApplicationController, ServiceConnection>(c => 
  c.Context.Convert(ec => ec.Connection));

With this registration at hand, all trusted components that need to have access to the current connection context, can do that by requesting an instance of Optional{ServiceContext}.

This technique is really useful when defining boundaries of infrastructure shards, required and optional dependencies that they might have.

Unit Testing

We can even easily isolate the shard in unit test in order to allow manual testing of feedback reporting:

[Test, Explicit]
public void Show_synthetic_feedback()
{
  var data = RandFeedbackModels.NextTextDatas(0, 3);
  CreateService().ReportFeedback(data);
}

Where CreateService explicitly establishes all dependencies in the MockContainer and grabs the service:

IFeedbackService CreateService()
{
  Build(cb =>
  {
    // factories
    cb.Register<Func<ServiceConnection, ILokadService>>(ServiceFactory.GetConnector);
    cb.Register<Optional<ServiceConnection>>(() => Maybe<ServiceConnection>.Empty);

    // services
    cb.Register<FeedbackService>().As<IFeedbackService>();
    cb.Register<ReportService>().As<IReportService>();

    cb.Register<FeedbackSenderController>().As<IFeedbackSenderState>();
  });

  WhenViewIsShown.Do(f => f.ShowDialog());

  return Resolve<IFeedbackService>();
}

As you can see for the service connection provider we are passing a lambda expression that returns an empty connection context.

Note that we are not establishing any view-related components, since ViewFixture (this test fixture derives from it) already does this:

cb.Register<TView>().As<TView, TInterface>();
cb.Register<TestViewspace>().As<IViewspace, TestViewspace>();
cb.Register<TestInteractions>().As<IClientInteractions, TestInteractions>();
cb.Register(Specifics.Empty);
OnBuild(cb);

Result looks like this:

Filling in the information and clicking Send would create a ticket in Lokad support system. By the way, you can use this opportunity to say what you think about the company.

Shard Synergies

Here are some additional synergy effects that come from using infrastructure shards to reuse similar components between multiple applications:

  • Business functionality could be used to extend multiple applications. Since UI is shared, we could use shared documentation, tutorials and screenshots as well.
  • Sometimes context requires infrastructure shard to be evolved. This brings potential improvements in all existing and new applications.
  • When a group of components is developed to be used in multiple scenarios from the start, this polishes the overall design even further than TDD or CDD would alone.
  • Localization or UI changes across the software product line become easier, since the effort does not have to be duplicated and tested in multiple codebases.

What do you think about all that?

Notes

I understand that this looks really similar to what Microsoft Extensibility Framework does with Lazy and Export . However, my belief is that:

  • extensibility discovery and management framework should stay completely detached from the components;
  • inversion of control container should stay completely detached from the framework as well;
  • attribute-based declarations on the components are evil;
  • all component dependencies should be explicitly declared in the orchestrating code in strongly-typed manner.

This means that the core components should not use extensibility framework. Infrastructure shard boundaries might do that, while acting as a simple gateways. But that's another story.

You can subscribe to this journal to get all the updates. Voting for the next article to be written is also possible.

« Fixing Icon Overlays for Dropbox + TortoiseSVN | Main | Efficient Development, Deployment and Customer Feedback at Lokad »

References (1)

References allow you to track sources for this article, as well as articles that were written in response to this article.

Reader Comments (2)

Please don't overload the word "shard".

October 22, 2009 | Unregistered CommenterOh please

Definition of shard from the Merriam-Webster: a piece or fragment of a brittle substance (shards of glass); broadly : a small piece or part

I think my usage of the term shard matches the meaning this word conveys in the English.

October 22, 2009 | Registered CommenterRinat Abdullin
Comments for this entry have been disabled. Additional comments may not be added to this entry at this time.