Sunday, November 25, 2012

Bye bye attributes-based APIs! Hello fluent APIs!

Cool kids are not doing this style of programming anymore:


public class Flight
{
    [DisplayWidth(200)]
    [FinderController(UseFinder=typeof(CountryFinder), SelectedID="SelectedCountryCode")]
    public string CountryCode { get; set; }
    
    [DisplayWidth(200)]
    [Color(Color.Blue)]
    [FinderController(UseFinder=typeof(CityFinder), SelectedID="SelectedCityID", CascadingField="CountryCode")]
    public int CityID { get; set; }
    
    
    [DisplayWidth(100)]
    [Spinner(From=1,To=30)]
    public int StayDuration { get; set; }
}



Not that there's something fundamentally wrong with that, however, with the confluence of the language's new features and the desire of many programmers to have their code be free from the clout of the tyrants, many programmers nowadays are realizing that their codes can be geared towards using plain classes. Classes that are not encumbered with infrastructure concerns, classes that best mirror the problem domain, classes that are just plain. Hence we just want the following class above to be like this now:



public class Flight
{
    public int CountryID { get; set; }
    public int CityID { get; set; }
    public int StayDuration { get; set; }
}

For example, if you used that class in Entity Framework yet you want to re-use that class in other ORM(say NHibernate), you won't want any EF infrastructure concerns be carried over from EF to NHibernate. You wanted your classes be in POCO form so you can re-use those classes in any way you deem them they fit.


Given that we strive towards writing POCO classes now, how can we deliver the input functionality around your domain classes without tainting them? One way is to use XML, but as any discerning programmers knows, XML-based APIs are going the way of the dodo now


With the confluence of C# 3's Expression and fluent programming, we can come up with an elegant way to map the domain classes to input. We can avoid XML and attributes-based API altogether.


But before we head to that, there's something inelegant with attributes-based APIs that needed be pointed out. It promotes social-based programming, it's good to be social, with code however, it's a different matter altogether. With social-based programming, every time you need to know about something, you often have to ask someone for their expertise. With attributes-based API, you'll often find yourself asking someone if there's already an API that does this or does that. As attributes are not discoverable, you can't quickly know which attribute needed be used for certain functionality, thus oftentimes you need to ask a colleague. This is not good for an industry such as ours, our industry is prone to bus factor. Whom to ask if the experts are not around? Though some might suggest you read the manual, but as we all know, most programmers don't read manuals


Another bad thing with .NET attributes is they are designed to be simple, i.e. you cannot use generics on it. Some just contend with the fact that they can only fashion stringly-typed API on top of it, e.g.


[FinderController(UseFinder=typeof(CityFinder), SelectedID="SelectedCityID", CascadingField="CountryCode")]
public int CityID { get; set; }



With stringly-typed API, it's not readily apparent from where you should get your CascadingField, is it something that is inside the CityFinder class? or is it in the Flight class?


However, with C# 3's Expression + fluent programming, you can make your API more discoverable, you'll be able to discover things by yourself fairly automatically.





So here's how we can make our input API be more discoverable with the use of C# 3's Expression and fluent programming.



public class Flight
{
    public int CountryCode { get; set; }
    public int CityID { get; set; }
    public int StayDuration { get; set; }
}


public class FlightInput : InputMap<Flight>
{
    public FlightInput ()
    {
        Input(x => x.CountryCode) // Use C# 3's Expression
            .DisplayWidth(200)
            .Color(KnownColor.Blue)
            .UseFinder<CountryFinder>().SelectedID(x => x.SelectedCountryCode);

        Input(x => x.CityID)
            .DisplayWidth(200)
            .Color(KnownColor.Blue)
            .UseFinder<CityFinder>().SelectedID(x => x.SelectedCityID).CascadingField(x => x.CountryCode);

        Input(x => x.StayDuration)
            .DisplayWidth(100).Color(KnownColor.Green)
            .UseSpinner(1,10); 
    }
}




With fluent-based API, you can check where the CountryCode on CascadingField come from(F12 or Ctrl+B(if using Resharper)). And while writing the CascadingField, the IDE can show you where the  CascadingField is getting its value from:





That's it! that's how elegant fluent-based API is! It keeps your domain classes in their pristine form (termed as POCO if you may), making your domain classes be very amenable to be re-used on many parts of your system.


One more thing, your code is refactoring-friendly with Expression and fluent-based API.


Sample Fluent API barebone code: http://www.ienablemuch.com/2012/12/sample-fluent-api-barebone-code.html


Happy Coding! ツ

No comments:

Post a Comment