talk: finish pres and yt script
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
---
|
||||
title: A Trick for Designing Friendly APIs in Go
|
||||
theme: default
|
||||
layout: center
|
||||
class: text-center
|
||||
fonts:
|
||||
sans: "CaskaydiaCove Nerd Font Mono"
|
||||
---
|
||||
|
||||
# Why I love control flow in C#
|
||||
|
||||
---
|
||||
layout: center
|
||||
class: text-center
|
||||
---
|
||||
|
||||
# The false dichotomy
|
||||
|
||||
<div class="grid grid-cols-2 gap-12 mt-16 text-left">
|
||||
<v-click>
|
||||
<div class="border-l-4 border-red-500 pl-6">
|
||||
<h3 class="text-red-400 text-2xl font-bold mb-2">Team "Never Throw"</h3>
|
||||
<p class="text-gray-400">"Exceptions are expensive! Treat errors as values! Not every failure is exceptional!"</p>
|
||||
</div>
|
||||
</v-click>
|
||||
<v-click>
|
||||
<div class="border-l-4 border-blue-500 pl-6">
|
||||
<h3 class="text-blue-400 text-2xl font-bold mb-2">Team "Always Throw"</h3>
|
||||
<p class="text-gray-400">"Results are noisy! Just throw and let the middleware handle it! Keep the code clean!"</p>
|
||||
</div>
|
||||
</v-click>
|
||||
</div>
|
||||
<v-click>
|
||||
<div class="mt-16 text-2xl text-green-400">
|
||||
> Why not both?
|
||||
</div>
|
||||
</v-click>
|
||||
|
||||
---
|
||||
layout: center
|
||||
---
|
||||
|
||||
# The sweet spot
|
||||
|
||||
<div class="grid grid-cols-4 gap-4 mt-8 h-full">
|
||||
<v-click>
|
||||
<div class="bg-gray-800 p-4 rounded border-t-4 border-red-500">
|
||||
<div class="font-bold mb-2 text-center">Global Handler</div>
|
||||
<div class="text-sm text-center">Prevent crashes</div>
|
||||
</div>
|
||||
</v-click>
|
||||
<v-click>
|
||||
<div class="bg-gray-800 p-4 rounded border-t-4 border-green-500">
|
||||
<div class="font-bold mb-2 text-center">Controller</div>
|
||||
<div class="text-sm text-center">Matches result</div>
|
||||
</div>
|
||||
</v-click>
|
||||
<v-click>
|
||||
<div class="bg-gray-800 p-4 rounded border-t-4 border-blue-500">
|
||||
<div class="font-bold mb-2 text-center">Service Layer</div>
|
||||
<div class="text-sm text-center">Returns results</div>
|
||||
</div>
|
||||
</v-click>
|
||||
<v-click>
|
||||
<div class="bg-gray-800 p-4 rounded border-t-4 border-purple-500">
|
||||
<div class="font-bold mb-2 text-center">Deep / Domain</div>
|
||||
<div class="text-sm text-center">Throws exceptions</div>
|
||||
</div>
|
||||
</v-click>
|
||||
</div>
|
||||
|
||||
---
|
||||
layout: center
|
||||
---
|
||||
|
||||
# An example in a web API
|
||||
|
||||
---
|
||||
layout: center
|
||||
---
|
||||
|
||||
# Use exceptions for context agnostic code
|
||||
|
||||
```csharp
|
||||
public async Task<User?> GetUserAsync(int userId)
|
||||
{
|
||||
// NOTE: EF Core might throw exceptions for connection issues, etc.
|
||||
var user = await _dbContext.Users.FindAsync(userId);
|
||||
|
||||
// NOTE: Domain logic might throw exceptions for business rules
|
||||
if (user?.IsBanned)
|
||||
{
|
||||
throw new UserBannedException(userId);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Service layer returns results
|
||||
|
||||
```csharp
|
||||
public async Task<Result<User>> GetUserAsync(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _userService.GetUserAsync(userId);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return Result.Failure<User>(new UserNotFoundException(userId));
|
||||
}
|
||||
|
||||
return Result.Success(user);
|
||||
}
|
||||
catch (UserBannedException ex)
|
||||
{
|
||||
return Result.Failure<User>(ex);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Match results in the controller
|
||||
|
||||
```csharp
|
||||
public static async Task<IResult> GetUserHandler(int userId, IUserService _service)
|
||||
{
|
||||
var result = await _service.GetUserAsync(userId);
|
||||
|
||||
return result.Match(
|
||||
user => Ok(user),
|
||||
error => error switch
|
||||
{
|
||||
UserNotFoundException => Results.NotFound(),
|
||||
UserBannedException => Results.Forbid(),
|
||||
_ => Results.InternalServerError()
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Global handler for unexpected errors
|
||||
|
||||
```csharp
|
||||
public async ValueTask<bool> TryHandleAsync(
|
||||
HttpContext httpContext,
|
||||
Exception exception,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
_logger.LogError(
|
||||
exception,
|
||||
"Exception occurred: {Message}", exception.Message
|
||||
);
|
||||
|
||||
httpContext.Response.StatusCode =
|
||||
StatusCodes.Status500InternalServerError;
|
||||
|
||||
await httpContext.Response
|
||||
.WriteAsync("An unexpected error occurred.", cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
layout: center
|
||||
---
|
||||
|
||||
# C# gives me the freedom to choose
|
||||
|
||||
<ul>
|
||||
<v-click>
|
||||
<li>Use exceptions when deep in the call stack.</li>
|
||||
</v-click>
|
||||
<v-click>
|
||||
<li>Use result types where business logic is centralized.</li>
|
||||
</v-click>
|
||||
<v-click>
|
||||
<li>Use global handlers for unexpected exceptions and to prevent crashes.</li>
|
||||
</v-click>
|
||||
</ul>
|
||||
@@ -0,0 +1,116 @@
|
||||
# Video Script: Why I Love Control Flow in C#
|
||||
|
||||
---
|
||||
|
||||
### The Hook
|
||||
|
||||
**[VISUAL: Face Camera]**
|
||||
|
||||
"If you hang around .NET social circles long enough, you eventually stumble into the 'Error Handling War'."
|
||||
|
||||
"It's usually a false dichotomy between two entrenched camps."
|
||||
|
||||
"On one side, you have **Team 'Never Throw'**. They scream that exceptions are expensive! They want you to treat errors as values—like Go or Rust—and use `Result<T>` for everything."
|
||||
|
||||
"On the other side, you have **Team 'Always Throw'**. They argue that Result objects are noisy and pollute your method signatures. They say: 'Just throw the exception and let the middleware handle it! Keep the code clean!'"
|
||||
|
||||
"But here is the thing: **Why not both?**"
|
||||
|
||||
"C# is unique because it allows us to mix paradigms comfortably. Today, I want to show you the 'Sweet Spot' architecture that uses exceptions where they make sense, and results where they belong."
|
||||
|
||||
---
|
||||
|
||||
### The Architecture Overview
|
||||
|
||||
**[VISUAL: Slide 3]**
|
||||
|
||||
"I structure my Web APIs using a gradient of control flow. As you move up the stack—from the database to the API endpoint—the strategy shifts."
|
||||
|
||||
"It looks like this:"
|
||||
|
||||
**[CLICK]**
|
||||
|
||||
"Deep in the stack, in the Domain and Infrastructure, we **Throw Exceptions**. The context is unknown, and failures here are usually unrecoverable."
|
||||
|
||||
**[CLICK]**
|
||||
|
||||
"In the Service Layer, we catch those exceptions and **Return Results**. This is the boundary where we translate chaos into order."
|
||||
|
||||
**[CLICK]**
|
||||
|
||||
"In the Controller, we **Match Results**. We map the result state directly to HTTP status codes."
|
||||
|
||||
**[CLICK]**
|
||||
|
||||
"And finally, at the top, a **Global Handler** catches the crashes we didn't plan for."
|
||||
|
||||
---
|
||||
|
||||
### Layer 1: Deep in the Stack
|
||||
|
||||
**[VISUAL: Slide 4]**
|
||||
|
||||
"Let's look at the code. Starting deep in the stack."
|
||||
|
||||
"When I am writing infrastructure code or core domain logic, I don't want to return `Result<User>`. That code doesn't know it's part of an HTTP request. It doesn't know about `404 Not Found`."
|
||||
|
||||
"If the database is down, or a business invariant is violated—like a user being banned—I want to throw. This is an exceptional state for this specific logic."
|
||||
|
||||
---
|
||||
|
||||
### Layer 2: The Service Layer
|
||||
|
||||
**[VISUAL: Slide 5]**
|
||||
|
||||
"This is where the magic happens. The Service Layer acts as the translator."
|
||||
|
||||
"It calls that 'dangerous' deep code, but it wraps it in a `try/catch` block."
|
||||
|
||||
"We catch the *specific* exceptions we expect—like `UserBannedException`—and convert them into explicit `Result` failures."
|
||||
|
||||
"This keeps the exception 'blast radius' contained. The Service Layer is saying: 'I know this might fail, and I am making that failure part of the contract.'"
|
||||
|
||||
---
|
||||
|
||||
### Layer 3: The Controller
|
||||
|
||||
**[VISUAL: Slide 6]**
|
||||
|
||||
"By the time we reach the Controller, `try/catch` blocks are usually a 'code smell'. The Controller shouldn't be worrying about stack traces; it should be worrying about HTTP."
|
||||
|
||||
"Because our Service returned a `Result`, we can use C#'s pattern matching."
|
||||
|
||||
"Look how clean this is. If it's a success? Return `200 OK`. If it's `NotFound`? Return `404`. If it's `Banned`? Return `403`. It reads like a list of instructions."
|
||||
|
||||
---
|
||||
|
||||
### Layer 4: The Safety Net
|
||||
|
||||
**[VISUAL: Slide 7]**
|
||||
|
||||
"Finally, what about the truly unexpected? The Null References, the Out of Memory errors?"
|
||||
|
||||
"That is what the Global Exception Handler is for. It sits at the very top of the pipeline."
|
||||
|
||||
"If an exception gets past the Service Layer, it means we didn't plan for it. So we log it as an error, and we return a generic 500. We don't try to be clever here; we just try not to crash."
|
||||
|
||||
---
|
||||
|
||||
### Conclusion
|
||||
|
||||
**[VISUAL: Slide 8]**
|
||||
|
||||
"This is why I love C#. It doesn't force you into one box."
|
||||
|
||||
"You don't have to pick a team. You don't have to be 'Team Throw' or 'Team Result'."
|
||||
|
||||
**[CLICK]**
|
||||
"Use exceptions for errors that occur deep in the stack where context is limited."
|
||||
|
||||
**[CLICK]**
|
||||
"Use results when failure is a valid business outcome."
|
||||
|
||||
**[CLICK]**
|
||||
"And use global handlers to catch the things you didn't see coming."
|
||||
|
||||
"Thanks for watching."
|
||||
Reference in New Issue
Block a user