Welcome to Kartones.Net Sign in

What should be public in an API?

When you design a framework/engine/library, there is always the fact that you have to think about the “surface area” of the API: how many classes, methods,… the user will see and be able to use.

In Wave, we have divided the engine in several assemblies:

  • WaveEngine.Common: this assembly contains the most basic functionality of the engine. For example, the math library sits here. It also contains a set of core interfaces that represent the engine in its most barebones state: IAdapter, IGraphicDevice, IIOManager, IInput,…
  • WaveEngine.Adapter: this is not really an assembly, but a set of assemblies with the same name, one for each platform we support. An adapter is simply the code that is needed for the engine to work on a given platform. These Adapters implement the core interfaces defined in WaveEngine.Common (IAdapter,…).
  • WaveEngine.Framework: this is the assembly the users of Wave Engine will be using to code. Framework wraps over the low level operations defined in the core interfaces, and presents the end user with a higher level API that is more powerful and simpler to use.
  • There are a few other assemblies, but they are not more extra sugar candy and not mandatory really (WaveEngine.Components, WaveEngine.Materials,…).

So, in a perfect world, when a user starts creating a project with Wave Engine, he will just reference Framework and start working happily. In reality, that person will probably also need to reference Common, as some types that sit there (specially the math types) are commonly needed in a normal project.

Once you add a reference to Common, there is a problem. All the core interfaces (and a few other things) are public, but they are not designed to be used when making a game, they are designed to be used when writting an adapter to support a platform on where to run the engine. But the user will see them nevertheless, as they are public because they have to be consumed for the Adapter assemblies.

This is somewhat problematic. If by mistake we expose one of those levels interfaces in Framework (for example we used to have quite a few references to IAdapter), the user can bypass the high level API (by mistake or by his own choice) and then start using things that are not designed to be used in his scenario (making a game). A solution would be to split the Common assembly, one with really basic types, and another with the types used in the adapters, but the problem is that those assemblies would be quite small if separated.

In the end, we have made Framework so none of this classes and interfaces are exposed publicly anywhere, so the user doesn’t have a way to get a reference to an object implementing them (they are always interfaces or abstract classes). The user could implement them himself, but that doesn’t make much sense, so we do not worry about that case. The user could also get them using reflection, but we do not think that’s an interesting scenario to take into account either.

So, the main place where we should spend more time (and where we have spent more time by far) making sure the public API was solid is WaveEngine.Framework. Take into account that Wave Engine was developed at the same time as our first game, ByeByeBrain (BBB), was developed, so both evolved quite a lot during their main development (which lasted more or less a year). It was an interesting thing for me to see how the olders part of the code where for example accessing Common, while the newer ones were using Framework, it shows how the engine evolved, providing only very basic services at the start, and then growing to provide more higher level and powerful classes to simplify daily scenarios, or handle some platform differences that even with Common couldn’t be abstracted. For example serialization works in some minor details differently in .NET (and different between the PC and the Xbox360) and Monotouch/Monodroid.

As we dediced to review Framework, we went through all public attributes, properties and methods and start deciding what to do with every one of them. Some things were public, but were not used outside Framework (maybe it was thought they would be useful but they were not in the end), and were made internal. Others were public and where used, but the same could be achieved in the same way and we did not want to expose two ways of doing the same thing. And so on and on. In general, we tried to make as much as possible private/protected/internal, yet it is hard sometimes to gauge if you have done it right as we have only BBB as a case study (and some internal samples, but they are fairly simple). Did we make something internal because it was not useful or it was just not useful in BBB? That was a very important question for us, and sometimes we did not have a clear idea of what was the answer, so in those cases we decided to go with hiding things, as making something public has less chances of breaking things than making something private.

We think in the end we have ended with a very clean API, with enough power to be usable but not overly complex, but we need to validate it ourselves first. Right now the current Wave Engine build and BBB sit in a Stable branch, while this new rewrite sits in a Development branch, which does not compile BBB yet. We have now some work to migrate BBB to this new branch and see how it performs in a “real scenario”. BBB is a very graphic intensive game and during its original fast development there were some design decissions that are lost today. For example, did we expose a low level operation from the IAdapter because using the high level version was too slow for a common scenario? Or more importantly: have we introduced some subtle bug without realizing? Those are the questions we will be able to answer when we port BBB and validate our work. We have ported the samples and they work nicely, but switching BBB while we finished its release in iOS and Android was simply not feasible for a small team like us. Now that the iOS has been released and the Android version is coming to an end, we can start with this final validation and fix any pending issues we find.

Published 14 May 2012 17:30 by Vicente
Filed under: ,

Comments

# What should be public in an API?

14 May 2012 17:54 by Miemblogs

When you design a framework/engine/library, there is always the fact that you have to think about the

# re: What should be public in an API?

14 May 2012 20:07 by Kartones

I would not be afraid of having multiple namespaces, even if they look small.

You can always join multiple logical namespaces inside the same assembly (like NDepend's author suggests here: www.simple-talk.com/.../partitioning-your-code-base-through-.net-assemblies-and-visual-studio-projects).

As for the rest, I agree that until you do a real-word scenario or prototype, everything else is guessing (more or less accurate).

Just use a real intensive scenario (like you are doing with BBB), because sometimes a light one might lie and then a big one can destroy performance.

I cannot give more details but I've seen it happen at least once at work (and we had huge delays due to having to rewrite the code again).

# re: What should be public in an API?

15 May 2012 13:51 by Olmo

One solution for the interfaces would be to make them Internal and add InternalVisibleToAttribute for each adapter assembly.

On the other side, look at JavaScript... They don't have protected or internal and private is a clousure trick. No sealed, no abstract. Everything is virtual since you can override every function and hey... They are doing ok.

I will focus more on simplicity, performance and orthogonal concepts rather than locking the framework. It's going to be used by programmers at the end, not users.

I'm also much more aggressive making breaking changes. I understand that the .Net framework have to be conservative, but with such a fast evolving language as C#, if you don't break things at then the API looks full of scars. I will even make some changes on .Net framework:

* remove ArrayList & HashTable (favor generics)

* make reflection return IEnumerable<T> instead of arrays (performance)

* remove custom delegates in favor of Func & Action

* make Enum.Parse generic

I would go even further and remove the event keyword from c# and replace it by

public EventHandler Click { add; remove; }

It will be much easier to teach what an event actually is.

There are big risk in taking this approach on .net c# (like splitting the community because some project will never update) but with a joung library is a no brainer: A mice can not move as slow as an elephant.

# re: What should be public in an API?

15 May 2012 17:41 by Vicente

@Kartones

Thanks for the great link! Very interesting read (and I didn't know about NDepend).

# re: What should be public in an API?

15 May 2012 17:53 by Vicente

@Olmo

The InternalVisibleToAttribute is a nice solution, although if I remember right it was added to help with testing, so I'm a little torn over using it or not.

About the breaking changes part, I strongly disagree. Usually in a project you take several dependencies, if every time one of those dependencies updates you have breaking changes after a little while the whole situation sucks.

As a creator I want to be creating things not fixing things that were already working, unless there is a very good reason for it. It's not that we will have a policy of zero breaking changes, but we want to minimize them as much as possible because in general we think they are a pain for end users (I could be wrong here, makes for an interesting blog post).

# re: What should be public in an API?

16 May 2012 00:49 by Olmo

It think the breaking factor depends on two numbers: how much code depends on the library right now, and how much code will depend on the future.

Maiking a breaking change on COBOL right now will be a stupid idea, but living the rest of your life with a mistake you make on V1 is also not pleasant:

Some ideas for responsible breaking changes.

* prefer compile time change over runtime one, that will produce more subtle bugs.

* promote coding style that simplify changes, avoiding magic strings and values.

* provide tools that simplify changes (whe have schema sync and documentation sync on our framework)

* provide regex/Roslyn code issues that fix the breaking changes.

# re: What should be public in an API?

16 May 2012 09:18 by Vicente

@Olmo

Agreed that breaking changes have to be evaluated case by case, to see how painful is to update versus how much they improve the library. But we do want to try to minimize them as much as we can from the start.

About how to perform a breaking change, very nice idea about using Roslyn for that, I have not thought about it, I'm writing it down for the future :)

# re: What should be public in an API?

14 February 2013 16:50 by Den

@Olmo

"On the other side, look at JavaScript... They don't have protected or internal and private is a clousure trick. No sealed, no abstract. Everything is virtual since you can override every function and hey... They are doing ok."

"They" don't have a real choice. And everyone wants to hide this "assembly of the Web" - Dart, Rust, Kotlin, TypeScript, CofeeScript etc.

Leave a Comment

(required ) 
(required ) 
(optional )
(required ) 

Captcha