{"id":41,"date":"2024-02-28T19:49:53","date_gmt":"2024-02-28T19:49:53","guid":{"rendered":"https:\/\/twobitranch.io\/?p=41"},"modified":"2026-09-11T11:55:26","modified_gmt":"2026-09-11T11:55:26","slug":"creating-abstractions-to-avoid-possible-future-problems","status":"publish","type":"post","link":"https:\/\/twobitranch.io\/index.php\/2024\/02\/28\/creating-abstractions-to-avoid-possible-future-problems\/","title":{"rendered":"Creating abstractions to avoid possible future problems in ASP.NET Core"},"content":{"rendered":"\n<h3 class=\"wp-block-heading\">ASP.NET Core makes dependency injection so easy that creating abstractions can sometimes become automatic.<\/h3>\n\n\n\n<p>Create an interface.<br>Create an implementation.<br>Register both with the DI container.<br>Inject the interface into the controller or service.<\/p>\n\n\n\n<p>For example:<code>builder.Services.AddScoped&lt;IOrderService, OrderService&gt;();<\/code><\/p>\n\n\n\n<p>Then:<code>public class OrdersController : ControllerBase { private readonly IOrderService _orderService; public OrdersController(IOrderService orderService) { _orderService = orderService; } }<\/code><\/p>\n\n\n\n<p>This is a perfectly reasonable pattern.<\/p>\n\n\n\n<p>The problem starts when we assume that&nbsp;<strong>every dependency in an ASP.NET Core application needs an interface<\/strong>.<\/p>\n\n\n\n<p>We end up with interfaces that have one implementation, services that simply forward calls to another service, unnecessary layers, and a dependency graph that is harder to understand than the business problem it is supposed to solve.<\/p>\n\n\n\n<p>The real purpose of an abstraction is not to make dependency injection possible.<\/p>\n\n\n\n<p><strong>The purpose of an abstraction is to create a useful boundary around something that may need to change independently.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Dependency Injection Is Not the Same as Abstraction<\/h3>\n\n\n\n<p>ASP.NET Core has built-in dependency injection, which allows us to register concrete classes directly:<code>builder.Services.AddScoped&lt;OrderService&gt;();<\/code><\/p>\n\n\n\n<p>We can then inject the concrete type:<code>public class OrdersController : ControllerBase { private readonly OrderService _orderService; public OrdersController(OrderService orderService) { _orderService = orderService; } }<\/code><\/p>\n\n\n\n<p>There is nothing inherently wrong with this.<\/p>\n\n\n\n<p>We don&#8217;t need:<code>IOrderService<\/code><\/p>\n\n\n\n<p>simply because ASP.NET Core has a dependency injection container.<\/p>\n\n\n\n<p>An interface becomes valuable when it represents a boundary that we actually care about.<\/p>\n\n\n\n<p>For example:<code>builder.Services.AddScoped&lt;IPaymentGateway, StripePaymentGateway&gt;();<\/code><\/p>\n\n\n\n<p>Here, the abstraction makes sense because the application shouldn&#8217;t need to know that the payment implementation happens to use a particular provider.<\/p>\n\n\n\n<p>The controller or application service depends on:<code>IPaymentGateway<\/code><\/p>\n\n\n\n<p>rather than:<code>StripePaymentGateway<\/code><\/p>\n\n\n\n<p>That is a meaningful dependency boundary.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Don&#8217;t Create Interfaces Automatically<\/h3>\n\n\n\n<p>A common ASP.NET Core pattern looks like this:<code>public interface IProductService { Task&lt;Product?&gt; GetAsync(int id); } <\/code><code>public class ProductService : IProductService { public async Task&lt;Product?&gt; GetAsync(int id) { \/\/ Implementation } }<\/code><\/p>\n\n\n\n<p>And then:<code>builder.Services.AddScoped&lt;IProductService, ProductService&gt;();<\/code><\/p>\n\n\n\n<p>This may be completely appropriate.<\/p>\n\n\n\n<p>But suppose&nbsp;<code>ProductService<\/code>&nbsp;is an internal application service with one implementation and no meaningful alternative implementation.<\/p>\n\n\n\n<p>Then we should ask:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>Why does&nbsp;<code>IProductService<\/code>&nbsp;exist?<\/p>\n<\/blockquote>\n\n\n\n<p>If the answer is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>&#8220;Because we use dependency injection.&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p>that&#8217;s not a particularly strong reason.<\/p>\n\n\n\n<p>We could simply write:<code>builder.Services.AddScoped&lt;ProductService&gt;();<\/code><\/p>\n\n\n\n<p>and:<code>public class ProductsController : ControllerBase { private readonly ProductService _productService; public ProductsController(ProductService productService) { _productService = productService; } }<\/code><\/p>\n\n\n\n<p>The application is still using dependency injection.<\/p>\n\n\n\n<p>We&#8217;ve simply avoided an abstraction that doesn&#8217;t currently provide meaningful value.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Where Abstractions Are Particularly Useful in ASP.NET Core<\/h3>\n\n\n\n<p>ASP.NET Core applications frequently interact with infrastructure and external systems.<\/p>\n\n\n\n<p>These are strong candidates for abstraction.<\/p>\n\n\n\n<p>Examples include:<\/p>\n\n\n\n<ul>\n<li>Payment providers<\/li>\n\n\n\n<li>Email providers<\/li>\n\n\n\n<li>SMS providers<\/li>\n\n\n\n<li>Cloud storage<\/li>\n\n\n\n<li>Message queues<\/li>\n\n\n\n<li>External REST APIs<\/li>\n\n\n\n<li>Third-party SDKs<\/li>\n\n\n\n<li>Authentication providers<\/li>\n\n\n\n<li>Search engines<\/li>\n\n\n\n<li>File systems<\/li>\n\n\n\n<li>Time providers<\/li>\n\n\n\n<li>Infrastructure-specific&nbsp;services<\/li>\n<\/ul>\n\n\n\n<p>Consider an application&nbsp;that sends emails.<\/p>\n\n\n\n<p>Instead of allowing application code to depend directly on a specific email provider:<code>public class OrderService { private readonly SendGridClient _client; public OrderService(SendGridClient client) { _client = client; } }<\/code><\/p>\n\n\n\n<p>we can introduce a business-oriented abstraction:<code>public interface IEmailSender { Task SendAsync( string recipient, string subject, string body); }<\/code><\/p>\n\n\n\n<p>The infrastructure implementation can depend on the external provider:<code>public class EmailSender : IEmailSender { private readonly SendGridClient _client; public EmailSender(SendGridClient client) { _client = client; } public async Task SendAsync( string recipient, string subject, string body) { \/\/ Send using the provider } }<\/code><\/p>\n\n\n\n<p>And register it:<code>builder.Services.AddScoped&lt;IEmailSender, EmailSender&gt;();<\/code><\/p>\n\n\n\n<p>Now the rest of the application depends on:<code>IEmailSender<\/code><\/p>\n\n\n\n<p>rather than the provider&#8217;s SDK.<\/p>\n\n\n\n<p>If the email provider changes later, the change is largely isolated to the infrastructure implementation.<\/p>\n\n\n\n<p>This is the kind of future-proofing that is worth paying for.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Don&#8217;t Abstract ASP.NET Core Itself<\/h3>\n\n\n\n<p>Another common mistake is wrapping framework functionality without a clear reason.<\/p>\n\n\n\n<p>For example, developers sometimes create abstractions such as:<code>public interface IControllerBase { }<\/code><\/p>\n\n\n\n<p>or wrappers around framework services simply because they believe every dependency should be hidden.<\/p>\n\n\n\n<p>But ASP.NET Core already provides well-defined abstractions.<\/p>\n\n\n\n<p>For example:<code>ILogger&lt;T&gt; IConfiguration IHttpClientFactory IHostEnvironment IOptions&lt;T&gt;<\/code><\/p>\n\n\n\n<p>These are already abstractions.<\/p>\n\n\n\n<p>We generally don&#8217;t need to create:<code>IMyLogger IMyConfiguration IMyHttpClientFactory<\/code><\/p>\n\n\n\n<p>unless our application has a specific domain reason for doing so.<\/p>\n\n\n\n<p>Wrapping a framework abstraction with another abstraction can simply add another layer without protecting us from anything meaningful.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Avoid the &#8220;Interface for Every Service&#8221; Rule<\/h3>\n\n\n\n<p>Imagine an ASP.NET Core application containing:<code>IUserService UserService IOrderService OrderService IProductService ProductService ICustomerService CustomerService IAddressService AddressService IReportService ReportService<\/code><\/p>\n\n\n\n<p>And every interface has exactly one implementation.<\/p>\n\n\n\n<p>This isn&#8217;t automatically bad.<\/p>\n\n\n\n<p>But it is worth questioning.<\/p>\n\n\n\n<p>If every class is abstracted, developers now have to navigate between interfaces and implementations constantly.<\/p>\n\n\n\n<p>For example:<code>OrdersController \u2193 IOrderService \u2193 OrderService \u2193 IOrderRepository \u2193 OrderRepository \u2193 ApplicationDbContext<\/code><\/p>\n\n\n\n<p>A simple request may now cross five architectural boundaries.<\/p>\n\n\n\n<p>The important question becomes:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>Does each boundary represent something meaningful?<\/p>\n<\/blockquote>\n\n\n\n<p>If not, we may have created architecture for the sake of architecture.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Controllers Should Depend on Application Behavior<\/h3>\n\n\n\n<p>One useful abstraction boundary in ASP.NET Core is the boundary between the HTTP layer and application logic.<\/p>\n\n\n\n<p>A controller should generally not contain complicated business logic.<\/p>\n\n\n\n<p>Instead of:<code>[HttpPost] public async Task&lt;IActionResult&gt; Create(CreateOrderRequest request) { \/\/ Validate everything \/\/ Calculate totals \/\/ Apply discounts \/\/ Save order \/\/ Send email \/\/ Publish event return Ok(); }<\/code><\/p>\n\n\n\n<p>we can delegate the application behavior:<code>[HttpPost] public async Task&lt;IActionResult&gt; Create(CreateOrderRequest request) { var order = await _orderService.CreateAsync(request); return Ok(order); }<\/code><\/p>\n\n\n\n<p>Whether&nbsp;<code>_orderService<\/code>&nbsp;is represented by an interface depends on the application.<\/p>\n\n\n\n<p>The important abstraction here is not necessarily:<code>IOrderService<\/code><\/p>\n\n\n\n<p>The important boundary is:<code>HTTP concerns \u2193 Application behavior<\/code><\/p>\n\n\n\n<p>That distinction matters.<\/p>\n\n\n\n<p>We shouldn&#8217;t confuse&nbsp;<strong>architectural boundaries<\/strong>&nbsp;with&nbsp;<strong>interfaces<\/strong>.<\/p>\n\n\n\n<p>An abstraction can be useful without necessarily requiring an interface, and an interface can exist without representing a useful architectural boundary.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Dependency Injection Should Describe Your Architecture<\/h3>\n\n\n\n<p>Your DI registrations are effectively a map of your application&#8217;s architecture.<\/p>\n\n\n\n<p>For example:<code>builder.Services.AddScoped&lt;OrderService&gt;(); builder.Services.AddScoped&lt;IEmailSender, EmailSender&gt;(); builder.Services.AddScoped&lt;IPaymentGateway, StripePaymentGateway&gt;();<\/code><\/p>\n\n\n\n<p>These registrations tell us something meaningful:<\/p>\n\n\n\n<ul>\n<li><code>OrderService<\/code>&nbsp;is an application&nbsp;service.<\/li>\n\n\n\n<li><code>IEmailSender<\/code>&nbsp;represents an infrastructure&nbsp;boundary.<\/li>\n\n\n\n<li><code>IPaymentGateway<\/code>&nbsp;represents a replaceable&nbsp;external dependency.<\/li>\n<\/ul>\n\n\n\n<p>Compare that with:<code>builder.Services.AddScoped&lt;IOrderService, OrderService&gt;(); builder.Services.AddScoped&lt;IProductService, ProductService&gt;(); builder.Services.AddScoped&lt;ICustomerService, CustomerService&gt;(); builder.Services.AddScoped&lt;IAddressService, AddressService&gt;(); builder.Services.AddScoped&lt;IReportService, ReportService&gt;();<\/code><\/p>\n\n\n\n<p>If all of&nbsp;these interfaces simply&nbsp;exist because&nbsp;<em>&#8220;that&#8217;s our standard&nbsp;pattern,&#8221;<\/em>&nbsp;the DI configuration&nbsp;isn&#8217;t communicating much&nbsp;about the architecture.<\/p>\n\n\n\n<p>It&#8217;s just registering&nbsp;layers.<\/p>\n\n\n\n<p>Good dependency injection&nbsp;configuration should help&nbsp;us understand the&nbsp;system.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Use Abstractions at&nbsp;the Point of&nbsp;Instability<\/h3>\n\n\n\n<p>A useful way&nbsp;to think about&nbsp;dependency injection is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p><strong>Abstract things that&nbsp;are likely to&nbsp;vary.<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p>Suppose your application&nbsp;uses a payment&nbsp;provider.<code>CheckoutService | v IPaymentGateway | v StripePaymentGateway<\/code><\/p>\n\n\n\n<p>The payment provider&nbsp;is outside your&nbsp;control.<\/p>\n\n\n\n<p>It may change.<\/p>\n\n\n\n<p>Therefore, an abstraction&nbsp;is valuable.<\/p>\n\n\n\n<p>Now consider a&nbsp;simple domain calculation:<code>public class OrderTotalCalculator { public decimal Calculate(Order order) { return order.Items.Sum(x =&gt; x.Price); } }<\/code><\/p>\n\n\n\n<p>There may be&nbsp;no reason to&nbsp;create:<code>public interface IOrderTotalCalculator { decimal Calculate(Order order); }<\/code><\/p>\n\n\n\n<p>unless there is&nbsp;an actual reason&nbsp;for multiple implementations&nbsp;or another meaningful&nbsp;boundary.<\/p>\n\n\n\n<p>This gives us&nbsp;a useful principle:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p><strong>Put abstractions around&nbsp;volatility, not around&nbsp;everything.<\/strong><\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">HttpClient Is a&nbsp;Good Example of&nbsp;a Boundary<\/h3>\n\n\n\n<p>ASP.NET Core&nbsp;provides&nbsp;<code>IHttpClientFactory<\/code>, which already solves&nbsp;many concerns associated&nbsp;with managing&nbsp;<code>HttpClient<\/code>.<\/p>\n\n\n\n<p>We can register&nbsp;a typed client:<code>builder.Services.AddHttpClient&lt;PaymentClient&gt;(client =&gt; { client.BaseAddress = new Uri(\"https:\/\/payments.example.com\"); });<\/code><\/p>\n\n\n\n<p>Then:<code>public class PaymentClient { private readonly HttpClient _httpClient; public PaymentClient(HttpClient httpClient) { _httpClient = httpClient; } public async Task&lt;PaymentResponse&gt; ChargeAsync( PaymentRequest request) { \/\/ HTTP communication } }<\/code><\/p>\n\n\n\n<p>We don&#8217;t necessarily need to introduce multiple abstractions around this.<\/p>\n\n\n\n<p>But if the rest of the application shouldn&#8217;t know anything about HTTP or the external payment API, we might introduce:<code>public interface IPaymentGateway { Task&lt;PaymentResult&gt; ChargeAsync( decimal amount, string customerId); }<\/code><\/p>\n\n\n\n<p>The implementation can then use the typed HTTP client.<code>Application | v IPaymentGateway | v PaymentGateway | v PaymentClient | v External API<\/code><\/p>\n\n\n\n<p>Now each boundary has a reason.<\/p>\n\n\n\n<p>The application understands payments.<\/p>\n\n\n\n<p>The infrastructure understands HTTP.<\/p>\n\n\n\n<p>The external API remains an implementation detail.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Don&#8217;t Use Interfaces Just for Unit Testing<\/h3>\n\n\n\n<p>One of the most common arguments for interfaces is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>&#8220;We need an interface so we can mock it.&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p>For example:<code>public interface IProductService { Task&lt;Product?&gt; GetAsync(int id); }<\/code><\/p>\n\n\n\n<p>The interface exists primarily so a test can create:<code>var mock = new Mock&lt;IProductService&gt;();<\/code><\/p>\n\n\n\n<p>Testing considerations are important, but this shouldn&#8217;t automatically determine the architecture.<\/p>\n\n\n\n<p>If the interface doesn&#8217;t represent a meaningful boundary, we may be creating production complexity to make a particular testing technique easier.<\/p>\n\n\n\n<p>A better question is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>What should actually be isolated in this test?<\/p>\n<\/blockquote>\n\n\n\n<p>External systems are excellent candidates.<\/p>\n\n\n\n<p>For example:<code>public interface IPaymentGateway { Task&lt;PaymentResult&gt; ChargeAsync(...); }<\/code><\/p>\n\n\n\n<p>A checkout test can replace the external payment dependency with a test double.<\/p>\n\n\n\n<p>That is valuable because payment processing is an external boundary.<\/p>\n\n\n\n<p>By contrast, creating interfaces for every internal class simply to mock every interaction can lead to tests that describe implementation structure rather than business behavior.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Configuration Is Another Important Abstraction<\/h3>\n\n\n\n<p>ASP.NET Core&#8217;s options pattern provides another example of a useful abstraction.<\/p>\n\n\n\n<p>Instead of injecting&nbsp;<code>IConfiguration<\/code>&nbsp;everywhere:<code>public class PaymentService { private readonly IConfiguration _configuration; public PaymentService(IConfiguration configuration) { _configuration = configuration; } }<\/code><\/p>\n\n\n\n<p>we can model configuration explicitly:<code>public class PaymentOptions { public string ApiKey { get; set; } = string.Empty; public string BaseUrl { get; set; } = string.Empty; }<\/code><\/p>\n\n\n\n<p>Register it:<code>builder.Services.Configure&lt;PaymentOptions&gt;( builder.Configuration.GetSection(\"Payment\"));<\/code><\/p>\n\n\n\n<p>Then consume it:<code>public class PaymentGateway { private readonly PaymentOptions _options; public PaymentGateway( IOptions&lt;PaymentOptions&gt; options) { _options = options.Value; } }<\/code><\/p>\n\n\n\n<p>This is a useful abstraction because the application is expressing a meaningful configuration concept:<code>PaymentOptions<\/code><\/p>\n\n\n\n<p>rather than spreading configuration keys throughout the codebase.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Abstractions Can Protect You From Framework Changes Too<\/h3>\n\n\n\n<p>There are situations where an abstraction can protect application code from infrastructure or framework-specific details.<\/p>\n\n\n\n<p>For example, suppose your application needs the current time.<\/p>\n\n\n\n<p>You could write:<code>var now = DateTime.UtcNow;<\/code><\/p>\n\n\n\n<p>throughout the application.<\/p>\n\n\n\n<p>But if business logic needs deterministic time during testing or a consistent time source, an abstraction may be useful.<\/p>\n\n\n\n<p>Modern .NET also provides the&nbsp;<code>TimeProvider<\/code>&nbsp;abstraction.<\/p>\n\n\n\n<p>You can inject it:<code>public class SubscriptionService { private readonly TimeProvider _timeProvider; public SubscriptionService(TimeProvider timeProvider) { _timeProvider = timeProvider; } public bool HasExpired(DateTimeOffset expiresAt) { return _timeProvider.GetUtcNow() &gt;= expiresAt; } }<\/code><\/p>\n\n\n\n<p>Register:<code>builder.Services.AddSingleton(TimeProvider.System);<\/code><\/p>\n\n\n\n<p>This is a good example of using an abstraction because the dependency represents something that can vary in a meaningful way, especially during testing.<\/p>\n\n\n\n<p>There is no need to invent:<code>IMyTimeProvider<\/code><\/p>\n\n\n\n<p>when the framework already gives us an appropriate abstraction.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Don&#8217;t Create an Abstraction for a Future You Cannot Describe<\/h3>\n\n\n\n<p>Consider this:<code>public interface ICustomerProvider { Task&lt;Customer&gt; GetCustomerAsync(Guid id); }<\/code><\/p>\n\n\n\n<p>Someone might say:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>&#8220;We may eventually get customers from another system.&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p>That&#8217;s possible.<\/p>\n\n\n\n<p>But what does the alternative system look like?<\/p>\n\n\n\n<p>A database?<\/p>\n\n\n\n<p>A CRM?<\/p>\n\n\n\n<p>A remote API?<\/p>\n\n\n\n<p>A cache?<\/p>\n\n\n\n<p>An event stream?<\/p>\n\n\n\n<p>We don&#8217;t know.<\/p>\n\n\n\n<p>If we don&#8217;t know what the variation actually looks like, we may not know what the correct abstraction should be.<\/p>\n\n\n\n<p>This is one of the biggest problems with speculative abstraction.<\/p>\n\n\n\n<p>We can easily create an abstraction today that prevents us from designing the right abstraction tomorrow.<\/p>\n\n\n\n<p>Sometimes the best decision is to wait.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Refactoring an Abstraction Later Is Often Cheaper Than Maintaining a Bad One<\/h3>\n\n\n\n<p>Developers sometimes fear removing an abstraction because they believe that once an interface exists, it must remain forever.<\/p>\n\n\n\n<p>It doesn&#8217;t.<\/p>\n\n\n\n<p>Suppose we start with:<code>public class CustomerService { }<\/code><\/p>\n\n\n\n<p>Later we discover that there are two implementations:<code>DatabaseCustomerService CrmCustomerService<\/code><\/p>\n\n\n\n<p>At that point we can introduce:<code>public interface ICustomerService { }<\/code><\/p>\n\n\n\n<p>and update the DI registration:<code>builder.Services.AddScoped&lt;ICustomerService, DatabaseCustomerService&gt;();<\/code><\/p>\n\n\n\n<p>The important thing is that we introduced the abstraction when we understood the problem.<\/p>\n\n\n\n<p>This is often preferable to maintaining an unnecessary interface for years because we were afraid that we might eventually need it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">A Practical Decision Framework<\/h3>\n\n\n\n<p>Before adding an interface to an ASP.NET Core application, ask:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Does this represent a meaningful boundary?<\/h3>\n\n\n\n<p>If it represents an external system, infrastructure dependency, or important business concept, that&#8217;s a strong signal.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Is the dependency likely to change independently?<\/h3>\n\n\n\n<p>If a third-party provider can change without the business logic changing, abstraction is often valuable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Would changing it later be expensive?<\/h3>\n\n\n\n<p>If replacing the dependency would require changes throughout the application, isolate it now.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Is there already meaningful variation?<\/h3>\n\n\n\n<p>Two implementations are stronger evidence than one hypothetical implementation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Does ASP.NET Core already provide the abstraction?<\/h3>\n\n\n\n<p>Before creating your own interface, check whether the framework already provides an appropriate abstraction such as:<code>ILogger&lt;T&gt; IHttpClientFactory IOptions&lt;T&gt; TimeProvider IHostEnvironment<\/code><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Does the abstraction make the code easier to understand?<\/h3>\n\n\n\n<p>If adding an interface makes the architecture harder to understand without protecting anything meaningful, reconsider it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">A Simple ASP.NET Core Architecture<\/h3>\n\n\n\n<p>A pragmatic ASP.NET Core application might look like this:<code> ASP.NET Core | Controller | v OrderService | +----------+----------+ | | v v IPaymentGateway IEmailSender | | v v StripePaymentGateway EmailSender | | v v External API Email Provider<\/code><\/p>\n\n\n\n<p>Notice that not everything is an interface.<\/p>\n\n\n\n<p><code>OrderService<\/code>&nbsp;can remain concrete if there is no reason to abstract it.<\/p>\n\n\n\n<p>The interfaces exist where they provide valuable boundaries.<\/p>\n\n\n\n<p>This keeps the dependency graph relatively simple while still protecting the application from important external changes.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">The Goal Isn&#8217;t Maximum Flexibility<\/h3>\n\n\n\n<p>It is tempting to think that good architecture means maximum flexibility.<\/p>\n\n\n\n<p>But maximum flexibility often means maximum complexity.<\/p>\n\n\n\n<p>An application that supports ten hypothetical implementations of every component isn&#8217;t necessarily better designed than an application that supports the one implementation it actually needs.<\/p>\n\n\n\n<p>The goal should be&nbsp;<strong>appropriate flexibility<\/strong>.<\/p>\n\n\n\n<p>We want to make important changes easier without making ordinary development unnecessarily difficult.<\/p>\n\n\n\n<p>In an ASP.NET Core application, that often means:<code>Concrete by default \u2193 Identify real boundaries \u2193 Abstract meaningful variation \u2193 Register abstractions with DI \u2193 Keep infrastructure behind those boundaries<\/code><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Dependency injection makes it easy to introduce abstractions in ASP.NET Core.<\/p>\n\n\n\n<p>That convenience can be both a strength and a weakness.<\/p>\n\n\n\n<p>It is easy to fall into a pattern where every service gets an interface, every interface gets an implementation, and every implementation gets registered with the DI container.<\/p>\n\n\n\n<p>But dependency injection is a mechanism\u2014not an architectural goal.<\/p>\n\n\n\n<p>The more important question is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p><strong>What are we protecting ourselves from?<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p>If an application depends on a payment provider, email service, external API, message broker, or other infrastructure that may change independently, an abstraction can be extremely valuable.<\/p>\n\n\n\n<p>If a class is simple, internal, stable, and has one obvious implementation, a concrete dependency may be the better design.<\/p>\n\n\n\n<p>The best ASP.NET Core applications aren&#8217;t necessarily the ones with the most interfaces.<\/p>\n\n\n\n<p>They&#8217;re the ones where the interfaces that&nbsp;<em>do<\/em>&nbsp;exist have a clear reason for being there.<\/p>\n\n\n\n<p><strong>Don&#8217;t abstract because you can. Abstract because the boundary matters.<\/strong><\/p>\n\n\n\n<p>That is how dependency injection becomes an architectural tool rather than just a registration mechanism.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>ASP.NET Core makes dependency injection so easy that creating abstractions can sometimes become automatic. Create an interface.Create an implementation.Register both with the DI container.Inject the interface into the controller or service. For example:builder.Services.AddScoped&lt;IOrderService, OrderService&gt;(); Then:public class OrdersController : ControllerBase { private readonly IOrderService _orderService; public OrdersController(IOrderService orderService) { _orderService = orderService; } } This is&hellip;&nbsp;<a href=\"https:\/\/twobitranch.io\/index.php\/2024\/02\/28\/creating-abstractions-to-avoid-possible-future-problems\/\" class=\"\" rel=\"bookmark\">Read More &raquo;<span class=\"screen-reader-text\">Creating abstractions to avoid possible future problems in ASP.NET Core<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/posts\/41"}],"collection":[{"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/comments?post=41"}],"version-history":[{"count":2,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/posts\/41\/revisions"}],"predecessor-version":[{"id":63,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/posts\/41\/revisions\/63"}],"wp:attachment":[{"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/media?parent=41"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/categories?post=41"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/tags?post=41"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}