Published on

I, interface

Authors
Kudos! 🙏🏻

This tale wouldn't be here if I didn't have conversations with Grzegorz Galezowski.

We have tons of discussions (or rather me asking a plethora of questions and learning from Grzegorz Galezowski), especially on Object-Oriented Analysis & Design

Thanks Grzegorz Galezowski for your patience, your knowledge and sharing your experience with me!

A great mystery

How many times have you seen an interface with the pattern: I<<Your-Random-Name>>Service, followed by a single implementation: <<Your-Random-Name>>Service?

Of course I prefix might sound odd to you, if you don't come from .NET world, but we will get back to that.

It's not that rare to encounter such an interesting phenomena.

There might be various incetives and reasons to pursue such pattern.

Typically, such specimen attracts many methods (not conflating it with behaviors yet!), most of them fulfilling query and command functions.

Of course, one might say that "writing" <<Your-Random-Name>>Services is old-fashioned and now cool kids use CQRS, in the form of IQueryHandler and ICommandHandler family.

I don't attack this approach (can a human attact an approach?), what's more - I personally like the separation of concerns it potentially brings.

I am just worried we misunderstood interfaces.

Interface - public API

Are interfaces just a communication tool for letting consumers know on how to use them?

Or maybe they are required to enable loose coupling, open-closed principle and dependency injection through Inversion of Control (IoC) container?

Should they be used purely for "polymorphism" quality? (it's possibly the only "morphism" that doesn't make OO programmers scared)

Imagine that in a certain software product we need to work with a Feedback concept.

Some capabilities, related to it, are fulfilled by a FeedbackService, that implements, you won't guess, IFeedbackService interface.

If we go to its public API, we might see:

public interface IFeedbackService
{
    Task Give(Feedback feedback);
    Task<bool> Approve(Feedback feedback);
    Task<bool> Disapprove(Feedback feedback);
    Task<IEnumerable<Feedback>> Get(FeedbackSubject feedbackSubject);
}

It doesn't look incorrect. It even tries to capture the language of the given domain.

Of course, as it works with Feedback, some other parties are interested in getting help from it, when working with various processes.

Imagine that one process, based on some conditions, wants to Disapprove a Feedback.

We would like to specify such behavior (provided that we have tests) so we can mock it and teach the test double how it should behave, e.g. when someone calls Disapprove method.

What about other methods?

We probably would just leave them as they aren't used.

No harm, we could say.

A recipe

As we are concerned with other methods, we could introduce CQRS and then have three commands and one query.

One could say original code is missing segregation of interfaces, one of the princinples of SOLID.

Our little interface looks as if it got segregated in a pretty good way - it aggregates methods on Feedback, isn't it?

Such pairs, an interface, followed by the class named astonishingly similarly, are so common.

To create such structure, the recipe is simple:

  1. Create a class, name it accordingly (preferably focusing on the noun).
  2. Add public methods and immediately add the code inside each of them.
  3. Create an interface with the same name as the class, then add I as a prefix (only for .NET pros).
  4. Make sure you covered all public methods.
  5. Work with tests you wanted to touch and mock this new class with providing a mock for the interface.

Voila!

What's missing?

This recipe creates something in isolation. Without any feedback (pun intended) from its consumers.

Now when we work with our tests, we need to make a decision (consciously or subconsciously) what to do with the remaining methods we are not mocking.

Actually, this kind of interface (having multiple methods) is typically created AFTER writing any specification for its behavior.

And this leads to mocking an object, not a responsibilty, we might need from it.

Roles, responsibilities

Mocking object, not responsibility?

Fortunately for us, in their seminal work - "Mock Roles, Not Objects" - Steve Freeman, Nat Pryce, Tim Mackinnon, Joe Walnes - warn us from exactly what we described above.

If you haven't read this paper (yes, a whitepaper!), stop reading this tiny tale and rush for reading this paper.

Really. I am not joking.

I assume you did so and we can go further, my dear Reader.

As they write:

Conclusion 🔍

It is really a technique for identifying types in a system based on the roles that objects play.

So instead of focusing on what we did - meaning, focusing on objects - we should think of the roles that those objects are playing.

Of course this rises another question - what do we mean by "a role", in the context of playing it?

We could bring Sandi Metz's words from "Polly want a message":

Role-playing

Objects are more players of their roles than instances of their types.

Taking into account Sandi's great explanatory skills, it's really interesting perspective.

If we involve our fellow FeedbackService class and try to talk to it (yes, talk to it - if you're curious of what am I talking about, please check Conversation-Driven Design).

What its answers would be when asked about its role?

Talk
Listen carefully

Us: Hi FeedbackService!

FeedbackService: Yo! What's up?

Us: We are wondering, what is your role?

FeedbackService: Role? What do you mean by "a role"?

Us: You know, what is expected from you - what you need to know to be productive and useful.

FeedbackService: Ah. Typically in my role, I get all feedback , I give feedback , I approve feedback. Almost forgot, I disapprove feedback too.

Interesting. Maybe we can't really say what is FeedbackService's role just by looking at it when it's not "moving", or rather not doing anything.

It might be easier to get the notion of the role it plays when we think of what it is responsible for!

Dreaded I prefix

There are many jokes, confusions and interesting (but not productive?) discussions about .NET and I suffix.

But I believe we might use it for changing the way we think of interfaces.

Let's recap what FeedbackService mentioned that it is doing when it plays its role:

  • "I get all feedback"
  • "I give feedback"
  • "I approve feedback"
  • "I disapprove feedback"

Read it aloud, please.

Have you captured it?

In many lanuages, the most basic statement structure follows "SVO" pattern: <<Subject>> <<Verb>> <<Object>>.

With this amazing knowledge, FeedbackService words (actually those were mine but let's pretend, ok?), we can combine them into interfaces:

  • IGetAllFeedback
  • IGiveFeedback
  • IApproveFeedback
  • IDisapproveFeedback

When you read it aloud, it even sound as if a real person said so.

Just for our little tale, we could change FeedbackService from this:

public class FeedbackService : IFeedbackService
{
    //...details here
}

into this:

public class FeedbackService : 
                IGetAllFeedback,
                IGiveFeedback,
                IApproveFeedback,
                IDisapproveFeedback
{
    //...details here
}

One could go even further and try to "humanize" it a bit and say:

"as a FeedbackService, I get all feedback, I give feedback, I approve feedback, I disapprove feedback".

Exactly the same as FeedbackService replied when we had a short conversation with it!

Those interfaces, or those responsibilities, are highly fine-grained.

"It's not a real life example"

Maybe too fine-grained?

Yes, it might be.

One could argue it's very artificial, theoretical scenario.

Segregating them in that way might sound dogmatic too.

Eventually, it's all about the Feedback, so why bothering to cut a single interface into pieces?

Let's pay attention to the words, again. How probable it is that the same person would both approve/disapprove feedback and give it?

Not so much, I would say.

And what about getting all feedback and giving it? Might be similar to the first answer - not so much.

If we "translate" our example into the real world scenario - will it be a single person or two people?

Could it be that there are actually two people, playing two different roles?

The first role might be concerned about evaluating something, and the second might be interested in evaluating evaluation, aren't they?

Imagine there's a customer that bought a product and is asked to give feedback. When this feedback is gathered, someone needs to assess how trustworthy this feedback actually is.

Our FeedbackService might cease to exist. It will turn into two roles instead:

public interface IFeedbackGiver : IGiveFeedback
{
    //...
}

public interface IFeedbackAuditor : 
                    IGetAllFeedback,
                    IApproveFeedback,
                    IDisapproveFeedback
{
    //...
}

They have exemplary names, of course - role such as "feedback auditor" might not exist in the real life.

Domain exploration - the side-effect

But what if it does? Is it something we could verify with our domain experts?

Such segregation makes roles, bounded by responsibilities, oriented toward behaviors - meaning verbs.

And verbs summon the context.

All this makes fat "interfaces" (which turn out not to be real roles) get exercised through domain lenses and become fitting into a particular role.

When we challenge ourselves by resisting to name everything as a <<Your-Random-Name>>Service, we might discover missing concepts too. Concepts that exist in the lanugage but they are not materialized in the code.

"What I cannot see, it doesn't exist", as the saying goes.

Let's then revisit role that approves feedback:

public interface IApproveFeedback
{
    Task<bool> Approve(Feedback feedback);
}

We could ask: "is it any feedback I approve?"

Now, it might become straightforward that it's not every feedback - but the one that was given.

public interface IApproveFeedback
{
    Task<bool> Approve(GivenFeedback feedback);
}

Then, both Approve/Disapprove actions might create EvaluatedFeedback that has totally different rules associated with it.

Soon, we might become aware that we are using Language of the problem.

And as we broaden our language, Feedback gradually stops looking like "an entity" (what am I talking about? If you're intersted, please check Concept maps).

Responsibilitity means "function"?

If we stop being .NET pros, just for a while, and drop I prefix from our newly discovered responsibilities?

What would they become?

Each of those narrow interfaces require only one method, from an object, to implement.

Let's take IApproveFeedback and try this process on it.

public interface IApproveFeedback
{
    Task<bool> Approve(Feedback feedback);
}

It becomes just ApproveFeedback and as it has single method - can we try to approximate it with a .NET delegate?

public delegate Task<bool> ApproveFeedback(Feedback feedback);

So our responsibility became a delegate. As a delegate represents a function, responsibilities can be thought of as functions too.

We could say that a role is shaped by the functions it provides.

When we apply recently explored domain knowledge, we could say ApproveFeedback is a function (a delegate) that turns a GivenFeedback into an EvaluatedFeedback. So it becomes:

public delegate Task<EvaluatedFeedback> ApproveFeedback(GivenFeedback feedback);

Signature expresses the contract between two sides, and instead of using technical terms, let's read it aloud in a human-oriented way:

"EvaluatedFeedback will be provided back (in the future), when ApproveFeedback is provided with GivenFeedback".

It might sound a bit odd - the reason might lay in providing the expected results firstly and then required input.

Could we reverse it?

C# language has its syntax and we need to adhear to it. The best would be to express it as the following:

"ApproveFeedback promises to turn GivenFeedback into EvaluatedFeedback, at some point in the future."

Let's do small changes and translate:

  • promises to into =
  • turn X into Y into X -> Y
  • and at some point in the future into Future<Y>.

Then it all becomes:

ApproveFeedback = GivenFeedback -> Future<EvaluatedFeedback>

Interestingly, it looks astonishingly similar to a function type syntax in F#. The only thing to make it compile correctly is adding a type keyword before everything:

type ApproveFeedback = GivenFeedback -> Future<EvaluatedFeedback>

Also, there is no Future type in F# so we would need to "make" one:

type Future<'PromisedValue> = Async<'PromisedValue>

So I lied to you - those were two things that were missing, eventually.

With this little example, one might observe that "one-method", narrow, interfaces realize the same function as functions (pun intended) in functional(-first) languages. What's more, we can think in responsibility-terms in functional languages too!

What we actually gain through this segregation is that we might be very precise in selecting objects to play a very specific role - or fulfill a very specific function.

Similarly to function composition, such atomic "isolation" enables responsibilities compositionality into something bigger - a role. (yes - composition and compositionality are different terms!)

A responsibilitity is not a method, a role is not an interface

Both roles and responsibilities are something more than interfaces with bunch of methods glued together.

They express higher-level concepts by capturing the language that lives in the problem area.

Unfortunately, we often mix everything together, without paying much attention to underlying concepts, rules and contextual details.

"Mock Roles, Not Objects" enlightens the path by recommending to put our focus on "Need-Driven Development" in order to "discover the interfaces", or rather we should say - "to discover roles and responsibilities".

This can be done with "specification-driven development" (if you are interested in what I am talking about, please check "The ambiguity" of TDD) and orienting toward the usage - needs coming from the consumers that require such responsibilities.

Given that we are exploring the responsibilities and roles, or "interfaces", with respect to requirements put by the service takers, they eventually become narrower and smaller.

Next time, dear Reader, Slow down, consciously whisper to yourself "I don't know" and ask yourself an exploratory question:

Question 🤔

Am I defining a bag of methods or a role with responsibilities?