- Published on
A solid grasp of responsibility
- Authors
- Name
- Damian Płaza
- @raimeyuu
No person was harmed while preparing this blog post.
Massive amount of conceptual code ahead, prepare!
"The Ministry of Taste"
Meet Eddy.
public class EddyTheTasteExpert
{
}
Eddy is the expert in composing food so that the taste is splendid, marvelous and addictive.
He graduated from The University Of Taste (TUOT, you knew it, didn't you?), worked with the most well-known chefs and other food experts in the world.
Living in various countries made him aware of the plethora of combinations, mixtures, and recipes.
He was tired of working in luxurious restaurants where only the profit mattered.
Eddy wanted to get challenges, to get freedom to decide about creative tastes.
That's why he founded "The Ministry of Taste" startup.
The business model was fairly simple - a customer pays for getting custom, tasty dish based on description given.
How cool is that!
Sad Story
In fact, Eddy wasn't interested in any business models - he wanted to think, sense, feel and respond with his vast experience to please the customer call for a splendid dish.
In his imagination, there was no chopping, cutting, boiling.
Just the pure, custom taste invention to give a heaven-like sensual experience for the customer.
Soon, Eddy was unsatisfied with the reality.
A part of his job was actual food experience design, but "the boring stuff" fell on his shoulders too.
public class EddyTheTasteExpert
{
public async Task PrepareAndServeCustomizedDish(TasteDescription tasteDescription)
{ /*...*/ }
}
He knew that to do what he love - discovering new taste recipes - he need to be stubborn and just do all the chopping, serving, etc.
But the part of his job wasn't only sitting in the kitchen.
Eddy hated checking availability of the ingredients.
public class EddyTheTasteExpert
{
/* ...previous ones...*/
private Task<IngredientsAvailability> CheckIngredientsAvailability(TastyDishSuggestion customizedTastyDishSuggestion)
{ /*...*/ }
}
Calling the nearby ingredients warehouses was driving him nuts.
But the worst non-food related activity was talking to the customer when there were no ingredients for the initial custom dish recommendation and no ingredients for the alternative dish (in case of the initial dish was impossible to be prepared because of the shortage on the ingredients).
public class EddyTheTasteExpert
{
/* ...previous ones...*/
private Task<CustomerAnswer> AskCustomerWhatToDo()
{ /*...*/ }
}
Then, depending on the customer answer, Eddy needed to do one of the following steps:
- return the money back
- be creative and prepare highly creative taste, taking into account ingredients availability (actually, this is what Eddy loves!)
- suggest a voucher for future visits
public class EddyTheTasteExpert
{
/* ...previous ones...*/
private Task StartReturningMoney()
{ /*...*/ }
private TastyDishSuggestion RecommendCreativeCustomizedTastyDish(
IngredientsAvailability ingredientsAvailability,
IngredientsAvailability alternativeIngredientsAvailability
)
{ /*...*/ }
private Task SuggestAVoucher()
{ /*...*/ }
}
What's more, each of the step required from Eddy being transparent and honest - meaning, Eddy needed to notify about the action taken.
public class EddyTheTasteExpert
{
/* ...previous ones...*/
private Task NotifyAbout(Notification notification)
{ /*...*/ }
}
You might be wondering - where is the food experience design? Where is Eddy's gem hidden?
If everything was fine with the ingredients of the initial taste description or if the ingredients of an alternative dish were there, Eddy could do his virtuoso, masterpiece work and recommend the best food experience.
public class EddyTheTasteExpert
{
/* ...previous ones...*/
private TastyDishSuggestion RecommendAlternativeDishBasedOn(
TasteDescription tasteDescription,
List<UnavailableIngredients> unavailableIngredients
)
{ /*...*/ }
private TastyDishSuggestion RecommendTastyDishBy(TasteDescription tasteDescription)
{ /*...*/ }
}
Oh, there's one interesting detail - Eddy was quite well-recognized persona in the cooking world so he had many "important" customers he needed to treat a bit differently.
People knew his creativity and sometimes folks were describing premium tastes - so Eddy needed to verify if they are VIP customers - because only VIP customers were allowed to ask for premium tastes.
public class EddyTheTasteExpert
{
/* ...previous ones...*/
private Task<bool> IsVipCustomer()
{ /*...*/ }
private Task<bool> AnyPremiumTastes(TasteDescription tasteDescription)
{ /*...*/ }
}
And finally, after all, Eddy needed to prepare the dish and serve it.
public class EddyTheTasteExpert
{
/* ...previous ones...*/
private Task<CustomizedTastyDish> ServeDish(CustomizedTastyDish dish)
{ /*...*/ }
private Task<CustomizedTastyDish> PrepareDish(TastyDishSuggestion creativeTastyDishSuggestion)
{ /*...*/ }
}
Let's sum up all the responsibilities that Eddy had on his shoulders:
public class EddyTheTasteExpert
{
public async Task PrepareAndServeCustomizedDish(TasteDescription tasteDescription)
{ /*...*/ }
private Task<IngredientsAvailability> CheckIngredientsAvailability(TastyDishSuggestion customizedTastyDishSuggestion)
{ /*...*/ }
private Task<CustomerAnswer> AskCustomerWhatToDo()
{ /*...*/ }
private Task StartReturningMoney()
{ /*...*/ }
private TastyDishSuggestion RecommendCreativeCustomizedTastyDish(
IngredientsAvailability ingredientsAvailability,
IngredientsAvailability alternativeIngredientsAvailability
)
{ /*...*/ }
private Task SuggestAVoucher()
{ /*...*/ }
private Task NotifyAbout(Notification notification)
{ /*...*/ }
private TastyDishSuggestion RecommendAlternativeDishBasedOn(
TasteDescription tasteDescription,
List<UnavailableIngredients> unavailableIngredients
)
{ /*...*/ }
private TastyDishSuggestion RecommendTastyDishBy(TasteDescription tasteDescription)
{ /*...*/ }
private Task<bool> IsVipCustomer()
{ /*...*/ }
private Task<bool> AnyPremiumTastes(TasteDescription tasteDescription)
{ /*...*/ }
private Task<CustomizedTastyDish> ServeDish(CustomizedTastyDish dish)
{ /*...*/ }
private Task<CustomizedTastyDish> PrepareDish(TastyDishSuggestion creativeTastyDishSuggestion)
{ /*...*/ }
}
Now you know why Eddy hated his job, don't you?
How do create the value?
What's even more complicated is that Eddy needed to remember all the steps he need to perform in order to bring value to the customer - tasty, customized, and heavenly food.
Instead of focusing on the essence, immersing himself in the revolution of tastes, he needed to do "the boring stuff" - it was a drudgery.
Help is on the way!
How could we help poor Eddy?
What could we suggest doing, to relief the pain of doing "the boring stuff"?
We want to accelerate Eddy in his precious work so that he can save people from dull tastes!
Firstly, we need to understand all the steps Eddy needs to do.
Then we would be able to see what could be done.
Event Storming
Let's imagine we figured out, collaboratively, together with Eddy, what he does to deliver the best food experience to his customers.
The outcome of such session is here:
Clearly, it is much more than just food experience design. It is food experience delivery, not only design.
After asking Eddy what are his desired main points of responsibility, he rapidly pointed at highlighted ones:
It's not hard to guess that Eddy does too much. He is simply overwhelmed and can't focus on doing his best skill - food taste crafting.
Finally, here is the entire process that Eddy is responsible for:
public class EddyTheTasteExpert
{
public async Task PrepareAndServeCustomizedDish(TasteDescription tasteDescription)
{
if (await AnyPremiumTastes(tasteDescription) && !await IsVipCustomer())
{
throw new RegularCustomerAskingForPremiumTastesException();
}
var customizedTastyDishSuggestion = RecommendTastyDishBy(tasteDescription);
var ingredientsAvailability = await CheckIngredientsAvailability(customizedTastyDishSuggestion);
if (ingredientsAvailability.SomeUnavailable)
{
var alternativeTastyDishSuggestion = RecommendAlternativeDishBasedOn(
tasteDescription,
ingredientsAvailability.UnavailableIngredients
);
var alternativeIngredientsAvailability = await CheckIngredientsAvailability(alternativeTastyDishSuggestion);
if (alternativeIngredientsAvailability.SomeUnavailable)
{
var customerAnswer = await AskCustomerWhatToDo();
if (customerAnswer.ReturnMoney)
{
await NotifyAbout(Notification.MoneyReturn);
await StartReturningMoney();
}
else if (customerAnswer.JustPrepareAnythingTasty)
{
await NotifyAbout(Notification.CreativeTasteDesign);
var creativeTastyDishSuggestion = RecommendCreativeCustomizedTastyDish(
ingredientsAvailability,
alternativeIngredientsAvailability
);
var dish = await PrepareDish(creativeTastyDishSuggestion);
await ServeDish(dish);
}
else
{
await NotifyAbout(Notification.VoucherSuggestion);
await SuggestAVoucher();
}
}
else
{
await NotifyAbout(Notification.AlternativeDish);
var dish = await PrepareDish(alternativeTastyDishSuggestion);
await ServeDish(dish);
}
}
else
{
var dish = await PrepareDish(customizedTastyDishSuggestion);
await ServeDish(dish);
}
}
DDD
We need to be clever here and apply DDD principle.
As you probably know (I hope you do), I am very much into DDD approach for solving problems (which sometimes ends up with producing software).
Unfortunately, this time I am talking about different DDD method - not Domain-Driven Design.
It's a simple rule that says:
Delegate, Delete, Do. "Do" is the last resort.
We already know that Eddy can't do everything, he's simply not good at, e.g. talking to the warehouse folks.
Let's imagine we talked to Eddy and he definitely said that all parts of the food experience delivery process must be there (so none can be deleted).
What are we left with?
Delegate.
Think big, act small
Turns out that the only thing we can do is to delegate some parts of the process so that Eddy can do what he really loves.
How?
We asked Eddy if he could ask his friends to help.
And that was a jackpot!
Turned out that thanks to Eddy's massive network of known people, soon he found folks that would like to help him with his business!
In fact, business problems were secondary to Eddy, because we all know that he purely wanted to focus on the food experience design.
"The Ministry of Taste" food experience delivery crew
After more sessions with Eddy (and other people from the startup), we established new way of assigning the responsibilities.
Event though it was a tiny company, multiple folks were happy to help!
It's time to introduce the crew.
I think we should start with ColeTheTasteExperienceDeliveryCoordinator
.
His main responsibility is to take care of handling orders and making sure that experience is delivered.
public class ColeTheTasteExperienceDeliveryCoordinator
{
public async Task HandleCustomizedDishOrder(TasteDescription tasteDescription)
{
var cirille = new CirilleTheCustomerRelationshipManager();
if (await cirille.AnyPremiumTastes(tasteDescription) && !await cirille.IsVipCustomer())
{
throw new RegularCustomerAskingForPremiumTastesException();
}
var eddy = new EddyTheTasteExpert();
var will = new WillFromTheWarehouse();
var fiona = new FionaFromTheFrontDesk();
var cassidy = new CassidyTheChef();
var walter = new WalterTheWaiter();
var anna = new AnnaTheAccountant();
var customizedTastyDishSuggestion = eddy.RecommendTastyDishBy(tasteDescription);
var ingredientsAvailability = await will.CheckIngredientsAvailability(customizedTastyDishSuggestion);
if (ingredientsAvailability.SomeUnavailable)
{
await HandleRequestedTasteIngredientsUnavailable(
tasteDescription,
cirille,
eddy,
will,
fiona,
cassidy,
walter,
anna,
ingredientsAvailability);
}
else
{
var dish = await cassidy.PrepareDish(customizedTastyDishSuggestion);
await walter.ServeDish(dish);
}
}
/*...other actions...*/
}
You already know Eddy.
public class EddyTheTasteExpert
{
internal TastyDishSuggestion RecommendAlternativeDishBasedOn(
TasteDescription tasteDescription,
List<UnavailableIngredients> unavailableIngredients
)
{/*...*/}
internal TastyDishSuggestion RecommendCreativeCustomizedTastyDish(
IngredientsAvailability ingredientsAvailability,
IngredientsAvailability alternativeIngredientsAvailability
)
{/*...*/}
internal TastyDishSuggestion RecommendTastyDishBy(TasteDescription tasteDescription)
{/*...*/}
}
Eddy is finally happy. He can do only what he loves.
Now, say hi to WillFromTheWarehouse
.
public class WillFromTheWarehouse
{
internal Task<IngredientsAvailability> CheckIngredientsAvailability(TastyDishSuggestion customizedTastyDishSuggestion)
{/*...*/}
}
Will knows a lot of places where "The Ministry of Taste" can get the best quality ingredients.
Let's go to FionaFromTheFrontDesk
and greet her:
public class FionaFromTheFrontDesk
{
internal Task NotifyAbout(Notification notification)
{/*...*/}
internal Task<CustomerAnswer> AskCustomerWhatToDo()
{/*...*/}
}
Her main responsibility is to support the customer along the food experience delivery.
Who do we have next? Ah yes, the ying-yang of the kitchen: CassidyTheChef
and WalterTheWaiter
:
public class CassidyTheChef
{
internal Task<CustomizedTastyDish> PrepareDish(TastyDishSuggestion creativeTastyDishSuggestion)
{/*...*/}
}
public class WalterTheWaiter
{
internal Task<CustomizedTastyDish> ServeDish(CustomizedTastyDish dish)
{/*...*/}
}
We should not forget AnnaTheAccountant
who makes entire wheel spinning!
public class AnnaTheAccountant
{
internal Task StartReturningMoney()
/*...*/
}
}
In the food experience delivery, she's very focused on her single job.
Is there anyone left?
CirilleTheCustomerRelationshipManager
, very busy and important guy!
public class CirilleTheCustomerRelationshipManager
{
internal Task<bool> IsVipCustomer()
{/*...*/}
internal Task<bool> AnyPremiumTastes(TasteDescription tasteDescription)
{/*...*/}
internal Task SuggestAVoucher()
{/*...*/}
}
Here's the summary who belongs to the team:
AnnaTheAccountant
ColeTheTasteExperienceDeliveryCoordinator
CirilleTheCustomerRelationshipManager
CassidyTheChef
EddyTheTasteExpert
FionaFromTheFrontDesk
WalterTheWaiter
WillFromTheWarehouse
I think it's worth checking how do they collaborate to handle other scenarios from our delivery process.
Scarce ingredients
There are two scenarios that were painful just for Eddy - where there are no ingredients - both for initial dish and for alternative dish.
He really hated those. Here's how HandleRequestedTasteIngredientsUnavailable
looks like:
private async Task HandleRequestedTasteIngredientsUnavailable(
TasteDescription tasteDescription,
CirilleTheCustomerRelationshipManager cirille,
EddyTheTasteExpert eddy,
WillFromTheWarehouse will,
FionaFromTheFrontDesk fiona,
CassidyTheChef cassidy,
WalterTheWaiter walter,
AnnaTheAccountant anna,
IngredientsAvailability ingredientsAvailability)
{
var alternativeTastyDishSuggestion = eddy.RecommendAlternativeDishBasedOn(
tasteDescription,
ingredientsAvailability.UnavailableIngredients
);
var alternativeIngredientsAvailability =
await will.CheckIngredientsAvailability(alternativeTastyDishSuggestion);
if (alternativeIngredientsAvailability.SomeUnavailable)
{
await HandleAlternativeTasteIngredientsUnavailable(
ingredientsAvailability,
alternativeIngredientsAvailability,
cirille,
eddy,
fiona,
cassidy,
walter,
anna);
}
else
{
await fiona.NotifyAbout(Notification.AlternativeDish);
var dish = await cassidy.PrepareDish(alternativeTastyDishSuggestion);
await walter.ServeDish(dish);
}
}
Everyone does his or her job in the best possible way and ColeTheTasteExperienceDeliveryCoordinator
ensures that everything is in the right order.
We should also check more "interesting" case, namely HandleAlternativeTasteIngredientsUnavailable
:
private static async Task HandleAlternativeTasteIngredientsUnavailable(
IngredientsAvailability ingredientsAvailability,
IngredientsAvailability alternativeIngredientsAvailability,
CirilleTheCustomerRelationshipManager cirille,
EddyTheTasteExpert eddy,
FionaFromTheFrontDesk fiona,
CassidyTheChef cassidy,
WalterTheWaiter walter,
AnnaTheAccountant anna)
{
var customerAnswer = await fiona.AskCustomerWhatToDo();
if (customerAnswer.ReturnMoney)
{
await fiona.NotifyAbout(Notification.MoneyReturn);
await anna.StartReturningMoney();
}
else if (customerAnswer.JustPrepareAnythingTasty)
{
await fiona.NotifyAbout(Notification.CreativeTasteDesign);
var creativeTastyDishSuggestion = eddy.RecommendCreativeCustomizedTastyDish(
ingredientsAvailability,
alternativeIngredientsAvailability
);
var dish = await cassidy.PrepareDish(creativeTastyDishSuggestion);
await walter.ServeDish(dish);
}
else
{
await fiona.NotifyAbout(Notification.VoucherSuggestion);
await cirille.SuggestAVoucher();
}
}
This is the most collaborative part of their work. It looks like an orchestra - and everything is done to get the best possible (and addictive) food experience.
What the hell have just happened?!
Believe me, my fingers hurt. But going back to the point!
Well, we brought happiness to the world and removed some pain and suffering. Our fellow food experience designer, Eddy, is massively overjoyed right now!
So what was the actual problem that Eddy had?
He was massively overwhelmed. He didn't mention it, but he was close to quitting his startup dream.
Eddy simply did to much and got drowned in remembering all the bits and pieces.
We was the artist of taste - how the hell could he know every detail about returning money or vouchers?
In fact, he was a little bit egoistic - he just wanted to focus on things he was good at.
But, is it really a bad thing?
Responsibilities assignment
After understanding the problem (I can't stress how important understanding the problem is!), we went into exploration mode and thanks to Event Storming we got the feeling of the process.
Clear signals from Eddy, that he didn't want other responsibilities than food experience design, guided us into the responsibilities assignment task.
Initially, Eddy was responsible for:
- knowing when to suggest a voucher
- deciding what to do when there are no ingredients
- doing the dish preparation
- doing the dish serving
- knowing what taste to recommend
- deciding how to respond to customer's answer
- and so on.
He got too many responsibilities assigned to himself.
Driving the design through responsibilities
Turns out Rebecca Wirfs Brock already figured out that thinking about responsibilities is so important when we design our systems. She coined the term "Responsibility-Driven Design" (you should definitely read her book, it's a w e s o m e!)
We could generalize and say that there are three major types of responsibilities. One could say that "people" might be responsible for:
- doing
- knowing
- deciding
Referring to the "Responsibility-Driven Design", a responsibility could be defined as "a contract or an obligation" for doing/knowing/deciding.
Having such a mindset, collaborating "people" are giving or taking services, based on a contract or an obligation. (Why do I say "people"? Please check "Organization-Driven Design")
In other words, some "people" expect a certain level of service, knowing the provided contract.
Distribute the responsibilities!
Craig Larman, in his great book "Applying UML and Patterns", presented an approach for designing software: General Responsibility Assignment Software Principles - GRASP.
So yet again, responsibilities, and their proper distribution, is the key element.
What might be implicit here, is focus on the behavior. What "a person" can do. What service can be provided by it.
And if we now recall "The Ministry of Taste" crew - each "person" could contribute with a very precise set of skills - meaning, "behaviors".
No discussion about data structures, statuses - only about responsibilities, contracts and operations. (if you are curious why I specified statuses? Please check Bool considered harmful?). These are secondary, as the behavior is defining them. (Why secondary? Please check The cost of modeling and/or Language of the problem).
SOLID GRASP of Responsibility
In fact, the first letter in SOLID stands for "Single Responsibility Principle".
It suits nicely the cluster of best practices, harvested during building the commercial software. I have a feeling that it's not the major focus on SOLID.
The topic of "responsibility" is a part of SOLID, but in "only" 1/5 (I know, such quantification is so shabby), whereas GRASP and RDD are both all about responsibility.
Don't get me wrong, I am not devaluing or disregarding SOLID, it contributed so much to many people in our industry (at the same time doing some harm).
What I try to explore and suggest here is to go further with "single responsibility" and jump into RDD and GRASP, to broaden the understanding.
Seems like in most cases (every time?) it boils down to distributing doing/knowing/deciding responsibilities.
Software Design from human perspective
As Dan North wrote in the blog post about CUPID
To be human, properties need to read from the perspective of people, not code. CUPID is about what it feels like to work with code, not an abstract description of code in itself.
One could sum up that Responsibility-Driven Design and GRASP focus on responsibilities - as they are "assigned" to various "people" (collaborators), they need to agree what is expected from them and what they can expect from others.
Does it resemble how people organize themselves to work collaboratively toward a bigger goal?
Design Drama
What if we could play how our software components would interact so that we see if it makes sense?
You probably (I hope so) heard about so-called CRC cards - (Candidate) Class - Responsibilities - Collaborators.
Briefly, they were used as a method for conceptual modeling and design. By assiging various roles to the people inside of the room/in the team, we would be able to see how they interact with each other, see bottlenecks or possible problems.
Going back to the merit, yet again, "responsibilities" are mentioned. Not data structures, not statuses - "what can you do for me?".
The Trinity of Responsibility Types
One could arrive at interesting observation - assigning mentioned "fundamental" responsibilities creates three roles:
- information expert (knowing/deciding)
- executor or doer (doing)
- coordinator (deciding/knowing)
Such roles can be found on various levels, for instance on application level (F# functions or C# classes) or architecture level (services).
This might sound a bit "theoretical", but spend some time in your current project and try to identify roles, hence responsibilities.
Can they be classified with those three "fundamental" ones?
Software Design = Responsibility Assignment?
Our job as a software architects or software engineers is to design the solution for a particular problem, operating within a specific context.
We went through a journey - we started with a large and overwhelming set of mixed responsibilities, put on a single "person".
Finally, we ended up with nicely segregated responsibilities.
It wasn't "free" or "easy" - it required learning and reflection.
We might "lie" to ourselves that understanding will magically pop-up or become "visible" out of the box.
Programming is fun, but developing high quality software is hard.
I believe it really matters how we think about the surrounding world - what metaphors we use (consciously or subconsciously) - including software engineering and software design.
We are very skilled in reasoning about people, their stories and people topologies - that's possibly why "playing" the roles of "objects" was so damn useful and effective.
Conversation-Driven Design, "Organization-Driven Design" or "Microoffices" vs "Officeolith" might be a nice extension for this kind of reasoning.
Treating software components as collaborating experts, each having defined responsibility (according to the contract), working inside of an organization, might be an interesting "routine thinking" break.
Questions
Final and closing words - let's have some additional questions to broaden the example:
- Could "The Ministry of Taste" crew do the same or a better job without
ColeTheTasteExperienceDeliveryCoordinator
? - Is returning money back need to happen "immediately"? Could it be done a bit later?
- Does the work of
CassidyTheChef
andWalterTheWaiter
be coordinated or could they just "shout" loudly, expressing what happened? - Who needs to "be in the same room"? Does it make sense for
FionaFromTheFrontDesk
to "sit" together withWillFromTheWarehouse
? - Who should be responsible for calling customers and inviting them for "Tasty Jam Session"?