Intro:
In this article we'll look at a technique for building an API from the outside in. I'll just be going over defining the surface areas (the outer part) of the API and will not build out the implementation (the inner part) which is something that would be done later.
Modeling a domain using this process has some significant benefits most of which are derived from the fact that we are coding from the outside-in rather than coding inside-out. Just like when we wear our cloths inside out and see some of the seams and how the piece was constructed, code constructed from the inside-out usually has poor encapsulation/abstraction and some of the plumbing (or seams) leak into the consumer's layer. Using this technique we'll be focusing on building the surface area of our classes first and later worry about the implementation. This technique really helps code bloat because we will only be coding what we need and not what we think we might need at some point in the future. We also have all the benefits of a TDD (Test Driven Development) coding process.
The resulting surface area of our library should be relatively simple and other programmers consuming our classes should find it very intuitive because it should make sense linguistically. Sometimes this means some additional complexity will end up inside our library in order to present a simpler surface are. Code written against our API will end up being much more readable than if we were to code the guts first. We can take advantage of intellesense to provide developers consuming our library with a logical "next step" when they are coding. All of this will be accomplished while we use a TDD approach so we end up with a nice suite of tests for our library. Even if we don't exclusively use this approach for your development some of the techniques are useful in certain places.
Defining Our Domain:
This is the most difficult part of the process and often I find myself needing a couple tries to get it right. The idea is to write down how we would think of a domain using common language and try to convert it to code using method and properties. The trick is figuring out exactly what we are modeling and how it would be consumed in a manner that correctly abstracts the complexity of the domain. This is the most significant change from the inside-out approach to coding where a junior programmer would start with a general idea of the functionality required and just can't wait to start putting code on the page. By investing more time thinking about the surface are of the functionality we want to expose before we start implementation we end up with a much better product in the end and have less wasted work due to surface are changes later.
Let's say we have to model a marathon and its participants. Using the outside-in approach we'll always define how we want our code to read first, before we write a single line of code. We would begin by writing some statements about how the domain should be consumed with the goal of making the API as close to spoken or written language as possible. The following is our first sketch of how we would like our code to read. Because this code is readable by non-technical people, we would potentially go through many discussions with domain experts to make sure we have the structure correct. There are many details hidden in the nuances of language that we often miss in code and being able to discuss code with a domain expert in this way before we set anything in stone will make our library much more accurate and complete.
|
Racer Joe, Frank, Fred; Race race1 = Joe.Races(Frank); race1. IsJoinedBy(Fred); race1. IsJoinedBy(Bill);
|
This first overly simplified sketch of the API would probably go though a few iterations of change and become much more complex before really being solidified. For the purposes of this article you can get the general idea of what we're shooting for so we'll leave it as is. In a real project we would probably go through a few iterations by ourselves or with a domain expert on this sketch alone to really flesh out the domain before moving forward. Let's say we have gone through this process and are now ready for building an object model that behaves as close to our sketch as possible.
Building the Surface Area:
First we'll build the Racer class and some basic functionality. We figure that we'll need a way to distinguish our racers so we'll make the first slight alteration to our sketch by adding a "Name" property. On a real project it would be a good idea to keep our sketch up-to-date and modify it every time we make a change like this in order to ensure everything is consistent and all the changes make sense together with our sketch. For this article we'll wing it for the sake of brevity and to give ourselves some elbow room.
|
public class Racer |
Next, we'll build a unit test in a separate test project to pin down our functionality.
|
[TestMethod()] |
I know we are not strictly following the TDD methodology, but we get the same benefits in the end as long as we are patient enough to ensure we have a test before we move on. For those of you who this just rubs the wrong way… not to worry: after this we'll be better about following TDD a bit more strictly.
We now will define our next entity, the "Race" class with no implementation.
|
public class Race |
And we'll write a test for construction of Race using our Racer class. Take note that there is currently no "Racer.Races()" method. Using this technique we will first be writing code in the way we want to consume it and then write the implementation. This is similar to TDD where we write the test first but taken to the next level. We will write the consuming code first, before the method stubs even exist.
|
[TestMethod()] |
Now we'll take advantage of the IDE (In this case, Visual Studio 08) in order to stub out the method we want. "Races()" is not implemented, but using Studio08 we can right click on the unimplemented method and generate a method stub. There are other tools available as well to help with this style of development. The one I currently use is from DevExpress and is a great addition to VisualStudio. Because everyone might not have this product, we'll just go with the VisualStudio functionality for this article. We right click on the method that has not been defined and we see a "Generate Method Stub" in the menu and select it.

And now the IDE writes the method for us in our Racer class with the NotImplementedException in place.






Lakshmikanth MattaPosted Feb 10, 2009, 5:24 AM
Dear Mr. Cochran, I would need your great help regarding the requirement mentioned as below - Requirements- I need to know when to use Interface Based Programming using .NET Framework 2.0 and if you could send me a sample would be great. Note : Interface Based Programming with 3 tier Architecture along with MVC concept. Awaiting your reply Regards, Lakshmikanth Matta
hvPosted Jan 26, 2009, 4:55 AM
Coding from the outside in seems to be an instance of MIT-style: "It is more important for the interface to be simple". I agree that this is a "good" style - don't code it, if it ain't needed. The competing style is New Jersey style: "It is more important for the implementation to be simpler than the interface". This has the benefit that the API can be used for many other purposes than it was intented. Not meaning to start a war of religions, but: Examples of New Jersey style seems to be Java API and Linux. Examples of MIT style seems to be .Net library and Windows. The whole point is that code must be "simple", but is it the interface or the implementation that must be simple? That is the question. see also: http://en.wikipedia.org/wiki/Worse_is_Better
Mike GoldPosted Jan 26, 2009, 1:22 AM
It always amazes me how much code is written with the straight implementation in mind. Since most programmers don't want to do visual design up front, this is a great approach to coding a scaffolding API around the later implemented code. All of my current projects use the approach you mentioned. (Writing an interface API, coding unit tests against the interface. Mocking services to allow the interfaces to work, and implementing LAST.) This approach inevitably makes for a much better implementation because it forces you to think about the entire project and what is required in all of your interfaces first. It also forces you to go through the thinking process of how the interface will meet the business requirements without writing a line of code.