- Published on
I, interface
- Authors
- Name
- Damian Płaza
- @raimeyuu
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>>Service
s 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:
- Create a class, name it accordingly (preferably focusing on the noun).
- Add public methods and immediately add the code inside each of them.
- Create an interface with the same name as the class, then add
I
as a prefix (only for .NET pros). - Make sure you covered all public methods.
- 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:
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":
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?
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!
I
prefix
Dreaded 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
intoX -> Y
- and
at some point in the future
intoFuture<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:
Am I defining a bag of methods or a role with responsibilities?