Services Return, Callers Present
A service should return a result and let the caller decide how to show it — the single most useful separation-of-concerns rule.
#clean-code #architecture #separation-of-concerns #services
One rule that quietly fixes a lot of design problems: a service computes and returns; the caller presents. The service should not know whether its result ends up in a terminal, an HTTP response, a test assertion, or a log line.
The smell
A login service that prints its own outcome:
public class LoginService
{
public void Login(string username, string password)
{
var user = Find(username, password);
if (user is not null)
Console.WriteLine($"Welcome, {user.Username}!"); // ← service knows about the console
else
Console.WriteLine("Invalid credentials.");
}
}
This works, but the service is now welded to the console. You can't reuse it in a
web API, you can't unit-test the outcome without capturing stdout, and the
"what happened" is trapped inside a void.
The fix
Return the outcome. Let whoever called it decide what to do with it:
public class LoginService
{
public User? Login(string username, string password)
{
return Find(username, password); // just the result
}
}
// The CALLER presents — and this can differ per context:
var user = loginService.Login(username, password);
if (user is not null)
Console.WriteLine($"Welcome, {user.Username}!"); // console app
else
Console.WriteLine("Invalid credentials.");
// A web API caller would instead: return user is not null ? Ok(user) : Unauthorized();
null, rather than
reaching for the console:public record LoginResult(bool Success, User? User, string? Error);
Why it pays off
- Reusable — the same service serves a console app, an API, and a background job.
- Testable — you assert on a returned value, not on captured output.
- Honest signatures — a method that returns
User?tells you what it does; avoidthat secretly prints does not.
It's the same rule you already follow
In Laravel you don't echo from a service — the service returns data and the
controller turns it into a response. Same instinct, same payoff. The console
is just this project's "view".
See the C# services note for the full example in context → Services & helpers.
Naming Things
Practical rules for naming variables, functions and types so code reads like prose.
AI Prompting
Getting reliable, useful results out of language models.