2

All the examples of Clean Architecture that I see have very simple use cases, such as:

  • OpenAccountUseCase
  • DepositMoneyUseCase

However, real world applications have much more complex logic.

Let's say I have a use case which gets the youngest Student on a Course for a given year. I could have:

  • GetYoungestStudentOnCourseForYearUseCase

...but you see the issue here, because it can quickly grow to:

  • GetInterpolatedChartDataForStudentAgesOnCourseForYearUseCase

...and so on.

Doesn't this become a problem, or is there a more fundamental issue related to having so much specific functionality tied into the use case?

4
  • 2
    (1) Don't use the UseCase suffix. (2) If the logic is truly complex, break it down. (3) Some of your use cases (or their names) might be too specific - nobody needs all that detail in the name itself. Try making them slightly more general and/or use parameters to communicate intent (e.g. FindStudent(filter: s => s.Year == 2022, searchStrategy: minBy(s => s.Age)). (4) Look for established terminology in the domain - if they call it "age chart" just use GetAgeChart(course, year), and maybe write in the doc comment that it's an "interpolated age chart". Omit generic things like "get data". Commented Dec 1, 2022 at 1:36
  • 1
    @FilipMilovanović: sounds like enough stuff for a good answer.
    – Doc Brown
    Commented Dec 1, 2022 at 7:03
  • Find a language with named arguments.
    – gnasher729
    Commented Dec 1, 2022 at 13:16
  • GetInterpolatedChartData(filter: filters, strategy: ForStudentAgesOnCourseForYear) where ForStudentAgesOnCourseForYear is a predefined function declared as constant (if possible). Or a class. It's still readable code and allows you to look for more charts where a specific strategy is applied.
    – Laiv
    Commented Dec 1, 2022 at 13:49

1 Answer 1

2

Here are a couple of guidelines:

1. Don't use the UseCase suffix. UseCase, within Clean Architecture, is more of a role that a piece of code has within the system. While it might be helpful at first to have it, once you've familiar with your code structure, it just becomes noise - something that you learn to ignore, cause what you're really interested is the bit in front.

2. If the logic is truly as complex as you say, break it down. You'll have a function calling other, more specific functions. You should do this anyway, but the side benefit is that each of these functions will have a narrower job, and you'll be able to name them more easily.

3. The name doesn't have to reflect all the details of what a function is doing internally - if someone is interested, they can look inside. Coming up with good names might require some practice, but try to communicate concisely the purpose of the function, what it's for (as in, it's more helpful if, when seen in its context at the call site, the name reveals why would someone call it in the first place, what they were trying to do in the overall flow).

Some of your use cases (or their names) might be too specific, both in the name and in implementation. E.g. GetYoungestStudentOnCourseForYearUseCase seems oddly specific to me. Nobody needs all that detail in the name itself. Try making them slightly more general and/or use parameters (or well-named arguments) to communicate intent. This might require you to redesign your code a bit.

E.g.

var student = FindStudents(
    filter: s => s.Year == 2022, 
    searchStrategy: minBy(s => s.Age)
).FirstOrDefault();

or, if your language doesn't support named parameters:

var filter = (student) => student.Year == 2022;
var searchStrategy = MinBy((student) => student.Age);

var student = FindStudents(filter, searchStrategy).FirstOrDefault();

and/or, divide the meaning between the class and the methods on it.

class StudentRegistry {
    public List<Student> FilterBy(...) {...}
}

// somewhere else:
var stundentRegistry = new StudentRegistry(...);
//...
var sophomores = stundentRegistry.FilterBy(/* filter for sophomores */);

Also, the operationGetYoungestStudentOnCourseForYear, being so specific, seems more like it's a part of a proper use case, rather than a use case in it's own right. It's unlikely they your users randomly need the option to get the youngest student just for giggles, they probably want to use that information to achieve something in their day-to-day work. Find out what they are actually trying to achieve and organize your use cases around that.

4. Look for established terminology in the domain - people who are working in that space may already have specific jargon for certain things. E.g. if they call it "age chart" and it's generally understood that it's interpolated student ages, just use something like GetAgeChart(course, year), and maybe add a documentation comment clarifying that it's an "interpolated age chart" for future reference.

Or combine both approaches (3. and 4.).

Generally speaking, avoid including generic things like "[getXyz]data" in the name if you can (we all know it's data, so usually it's just extra noise). But nothing is written in stone - it's up to you to decide when it's appropriate to break this and other rules of thumb for the purposes of clarity, etc.

The exact details of how you'll do it are up to you. It's helpful to think of your future self 6 months from now, coming back to this project after working on something else - will the names help you figure out what's going on, after you've forgotten half of it?

1
  • Sorry to revive a very old message, but when you say "'t use the UseCase suffix. UseCase, within Clean Architecture, is more of a role that a piece of code has within the system" - what do you use instead? Either XCommand or XQuery just?
    – MSOACC
    Commented Jan 10 at 0:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.