blog19

Result pattern vs Exceptions - The Battle for clean .NET APIs

  • When building APIs in .NET, one common design debate is
  • ๐—ฆ๐—ต๐—ผ๐˜‚๐—น๐—ฑ ๐—œ ๐—ฟ๐—ฒ๐˜๐˜‚๐—ฟ๐—ป ๐—ฎ ๐—ฅ๐—ฒ๐˜€๐˜‚๐—น๐˜<๐—ง> ๐—ผ๐—ฟ ๐˜๐—ต๐—ฟ๐—ผ๐˜„ ๐—ฒ๐˜…๐—ฐ๐—ฒ๐—ฝ๐˜๐—ถ๐—ผ๐—ป๐˜€ ๐˜„๐—ต๐—ฒ๐—ป ๐˜€๐—ผ๐—บ๐—ฒ๐˜๐—ต๐—ถ๐—ป๐—ด ๐—ด๐—ผ๐—ฒ๐˜€ ๐˜„๐—ฟ๐—ผ๐—ป๐—ด?
  • After working with both approaches across various projects, here's a practical comparison to help you decide what works best for your architecture.

๐—ง๐—ต๐—ฒ ๐—ฆ๐—ฐ๐—ฒ๐—ป๐—ฎ๐—ฟ๐—ถ๐—ผ

  • Letโ€™s say you're fetching a user by ID. If the user doesn't exist, do you:
  • Throw a UserNotFoundException?
  • Return a structured Result with a failure message?
  • Both approaches are valid, but they solve different problems.

๐—ง๐—ต๐—ฒ ๐—–๐—ฎ๐˜€๐—ฒ ๐—ณ๐—ผ๐—ฟ ๐—ฅ๐—ฒ๐˜€๐˜‚๐—น๐˜<๐—ง>

  • The Result pattern wraps success/failure states in a single object. Itโ€™s predictable, consistent, and ideal for business logic outcomes.
  • Why developers love it:
  • Clear separation of success and failure
  • No exception overhead
  • Consistent response shape for clients
  • Easier to test (no try-catch needed)

๐’๐ก๐จ๐ซ๐ญ-๐œ๐ข๐ซ๐œ๐ฎ๐ข๐ญ ๐ฉ๐š๐ญ๐ญ๐ž๐ซ๐ง

  • Sometimes you intentionally stop the pipeline early
  • Common for health checks and lightweight endpoints

๐—ง๐—ต๐—ฒ ๐—–๐—ฎ๐˜€๐—ฒ ๐—ณ๐—ผ๐—ฟ ๐—˜๐˜…๐—ฐ๐—ฒ๐—ฝ๐˜๐—ถ๐—ผ๐—ป๐˜€

  • Throwing exceptions is useful for signaling unexpected failures, like DB connection issues or null references.
  • Why exceptions still make sense:
  • Cleaner service code (no if checks)
  • Built-in middleware support in .NET
  • Better suited for truly exceptional cases

๐— ๐˜† ๐—ฅ๐˜‚๐—น๐—ฒ ๐—ผ๐—ณ ๐—ง๐—ต๐˜‚๐—บ๐—ฏ:

  • Use Result for expected, business-level outcomes.
  • Use exceptions for unexpected, system-level issues.

๐‚๐Ž๐‘๐’ ๐ฆ๐ข๐๐๐ฅ๐ž๐ฐ๐š๐ซ๐ž

  • CORS must run after routing and before authentication
  • Use named policies for clean control (per app / per endpoint)

๐€๐ฎ๐ญ๐ก๐ž๐ง๐ญ๐ข๐œ๐š๐ญ๐ข๐จ๐ง โ†’ ๐€๐ฎ๐ญ๐ก๐จ๐ซ๐ข๐ณ๐š๐ญ๐ข๐จ๐ง

  • Authentication identifies who the user is
  • Authorization decides what the user is allowed to do
  • The order is critical for correct security behavior

๐‘๐š๐ญ๐ž ๐ฅ๐ข๐ฆ๐ข๐ญ๐ข๐ง๐ 

  • Rate limiting protects APIs from abuse and traffic spikes.
  • It can be applied globally or per endpoint and returns 429 when limits are exceeded

๐‚๐จ๐ฆ๐ฉ๐ซ๐ž๐ฌ๐ฌ๐ข๐จ๐ง & ๐œ๐š๐œ๐ก๐ข๐ง๐ 

  • Response compression can significantly reduce payload size
  • Caching avoids unnecessary recomputation
  • Compression should run before caching, and caching should be limited to safe requests

๐‚๐ฎ๐ฌ๐ญ๐จ๐ฆ ๐ฆ๐ข๐๐๐ฅ๐ž๐ฐ๐š๐ซ๐ž

  • When built-in middleware is not enough:
  • request/response logging, tenant resolution, correlation IDs, feature flags, and custom guards all fit here
  • Middleware in .NET Applications often looks simple until it is not
  • Mastering order and responsibility is one of the fastest ways to level up as an ASP.NET Core developer