Application Server: Hosting Services

I am at QCON SF this week, an like last year, there is a whole lot of talk about microservices.

The application server supports Http services via WEB API. It’s a minimal implementation at the moment. If you associate your feature to an ApiController, then when the feature is started, the ApiController becomes available.

    [WebApiHost(typeof(YabbaDabbaDooController))]
    public class DemoFeature1 : Feature
    {
			public Task OnStart() { .... }
       public Task OnStop() { .... }
    }

The, YabbaDabbaDooController is just an ApIController.

public class YabbaDabbaDooController : ApiController
    {
        [Route("date")]
        public DateTime GetNow()
        {
            return DateTime.Now;
        }
    }

 

Now, the ApiController is bound to the feature. If the feature is started, you can reach the controller. If the feature is stopped, it will respond with a 404 (if any other controllers are started), or it won’t generate a response at all (if the webapi feature isn’t running because nothing needs it.)

The thought process behind that is that there may be several things related to a feature. Scheduled jobs, web api endpoints, custom code, etc. The feature is the in-app container that manages that stuff as a unit.

But, what if you only want to do one thing, and that one thing is host an API Controller? Then you have to create the feature, create the controller, then tie them together. That’s now awful, but you’ll have 2 classes for every service. And, when thinking about microservices and the possibility that there’s going to be many of them, it can be done easier.

My brain has been churning non-stop thinking about this. I had no intention on working on it, but this morning it got the better of me and I accidentally woke up 2.5 hours earlier than I normally would. So, I figured I might as well get to work on it.

I had to make fundamental change to features, which worked out better all around. To implement a feature, you subclass the Feature class, then provide Start and Stop methods, even if you don’t need them. The feature manager uses the TypeCache and DependencyResolver to find and build features based on the understanding that they are all of type Feature. Then, there are attributes on the feature to indicate what type of feature it may be. Currently, Core, Server, Application.

What I want to be able to do is this:

    [HttpFeature]
    public class HttpServiceDemo1 : ApiController
    {
        [Route("date")]
        public DateTime GetNow()
        {
            return DateTime.Now;
        }
    }

It’s a single class with a single attribute. It is the feature and the functionality associated to the feature. Of course, it can’t inherit from ApiController and Feature, and I didn’t want to do a bridge, so the base class had to go. I could have switched it to an interface, but then if I would feel obligated to put the Start and Stop methods on it, as I am not a fan of marker interfaces. And, if Start and Stop are on the interface, then you’d have to implement them even if you don’t need them. Now that I have been doing this a while, I find that often they are not needed, such as in HttpServiceDemo1 class.

So, it’s based on the attribute. The feature manager looks for all types that have an attribute that inherits from FeatureAttribute. Depending on where the feature manager is (primary app domain, or child application), it looks for either ServerFeatureAttribute or ApplicationFeatureAttribute (from which Core and Http descend).

If you need a start method, then add a “void Start()” method. If you need a stop method, then add a “void Stop()”  method. The methods will be found regardless of their access modifier.

Support of all this required some changes. The typecache/dependency resolver stuff had to be changed to work off the attribute rather than the type or type name.

  • The TypeCache had to be updated to return descendants of the requested attribute. Previously, when I said “give me all types that have the ServerFeatureType attribute”, it would only return those that had exactly that attribute, not any of it’s subclasses.
  • I added a method to the type cache to retrieve a type of a particular name that had the requested attribute.
  • The code that calls the OnStart and OnStop methods has been changed to use reflection. The method names have been renamed to just Start and Stop. Previously, these were awaitable methods. That has been lost during this change, but can be put back if needed. But, the feature manager was just waiting on them anway, so no loss. (Because it’s now using reflection, multiple Start/Stop signatures can be supported.)

The first success was eliminating the feature class and get everything working as it did before. But, it wasn’t done quite yet.

The Feature manager would recognize the type as a feature because it has a Feature attribute. But, how does it figure out that that the feature needs to be associated to another feature without another attribute? In this case, we want the HttpService1Demo class, which is an API Controller and also a Feature, to be associated to the WebApiHostFeature, which is what actually hosts the controller. Previously, that was done by adding a [WebApiHost] attribute to the feature, which we can still do, but the point of this exercise was to eliminate as much as possible.

Thus, the HttpFeatureAttribute class was made.

    public class HttpFeatureAttribute : ApplicationFeatureAttribute
    {
        public HttpFeatureAttribute(StartMode startMode = StartMode.Started)
            : base(startMode)
        {
        }

        public override IEnumerable<Attribute> GetOtherAttributes(object attributeOwner)
        {
            if (!attributeOwner.GetType().IsSubclassOf(typeof(ApiController)))
            {
                // TODO:
                Debug.Fail("need to be an api controller");
                return null;
            }
            return new List<Attribute> { new WebApiHostAttribute(attributeOwner.GetType()) };
        }
    }

The GeOtherAttributes method is new. It allows the attribute to accept an instance of a class, and return additional attributes for that class. In this case, it’s returning the WebApiHostAttribute.

The RegisterFeatureWithOtherFeatures method of the feature manager is responsible for the child to parent relationships. I added the second block of code to pull in the additional attributes that are provided by the Feature attribute.

            // this is a super hack. Consolidate with the method version.
            // get the attribute assigned directly to the class.
            var classAttributes =
                Attribute
                    .GetCustomAttributes(state.Feature.GetType(), typeof(DependentFeatureAttribute), true)
                    .OfType<DependentFeatureAttribute>();

            // the feature type attribute of the feature may indicate that additional attributes are needed.
            // IE: the HttpFeatureAttribute returns a WebApiHostAttribute so that features of that type are automatically associated
            // to the web api feature, without requiring a hand-added attribute.
            var featureTypeAttribute = (FeatureTypeAttribute)Attribute.GetCustomAttribute(state.Feature.GetType(), typeof(FeatureTypeAttribute));
            var other = featureTypeAttribute.GetOtherAttributes(state.Feature);
            if (other != null)
            {
                classAttributes = classAttributes.Union(other.OfType<DependentFeatureAttribute>());
            }

And that’s it! Now, you can simply put an HttpFeature attribute on an ApiController, and it is a self-contained feature and feature implementation. Of course, you could still associate the feature to other sorts of things if you want, but the point of this was to quickly stand up a service, as a feature, when you don’t really need anything else.

image

Because HttpServiceDemo1 relies on WebApiHost, you cannot stop WebApiHost unless HttpService1 (and any other WebApi dependent feature) are stopped first. You can hit the stop button, but it will respond and tell you that you can’t because of the dependencies.

If WebApiHost is stopped, and you start a feature that requires it, then it will start automatically.

One thing that i didn’t do, which I only just now realized looking at the screen shot, is give the new feature attribute it’s own group id. The UI orders thing by Group id, which is why all of the core features are always at the bottom of the list and in blue. I can set the group id for the new HttpFeature attribute and have them all grouped together too, and add it to the CSS so that they all show up as muave or something else distasteful. It’s all easy stuff.

In conclusion, this all went quick and easy, and Features are now simpler and cleaner. The thing that started it all was the idea of being able to stand up a service real quick, and that has been achieved. You may use it for a microservice, or any other type of service you like.

If you like the old way of doing things, then you can do that too. The old way is better when your feature does many things. The new way is better if you just want to host a service.

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: