<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Integration | Alan P. Barber</title><link>https://alanbarber.com/tags/integration/</link><atom:link href="https://alanbarber.com/tags/integration/index.xml" rel="self" type="application/rss+xml"/><description>Integration</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><copyright>© 2026 Alan P. Barber &amp;middot; Hosting by [CldSvr.com](http://cldsvr.com)</copyright><lastBuildDate>Sat, 16 May 2026 00:00:00 -0400</lastBuildDate><image><url>https://alanbarber.com/img/portrait.jpg</url><title>Integration</title><link>https://alanbarber.com/tags/integration/</link></image><item><title>The Adapter Pattern for C# Developers</title><link>https://alanbarber.com/post/the-adapter-pattern-for-csharp-developers/</link><pubDate>Sat, 16 May 2026 00:00:00 -0400</pubDate><guid>https://alanbarber.com/post/the-adapter-pattern-for-csharp-developers/</guid><description>&lt;p>The Adapter pattern is one of the most practical patterns you&amp;rsquo;ll use. It lets you make two incompatible interfaces work together. If you&amp;rsquo;ve ever wrapped a third-party library to fit your application&amp;rsquo;s conventions, you&amp;rsquo;ve already used an adapter.&lt;/p>
&lt;h2 id="lets-define-the-pattern">Let&amp;rsquo;s Define the Pattern&lt;/h2>
&lt;p>An adapter is a class that translates one interface into another. It wraps an existing class and exposes a different interface that your code expects. The adapter handles the translation between what you have and what you need.&lt;/p>
&lt;p>The key idea is compatibility. You have code that expects interface A. You have a class that implements interface B. The adapter sits in the middle and makes B look like A.&lt;/p>
&lt;p>What an adapter is &lt;em>not&lt;/em>: it&amp;rsquo;s not about adding functionality. It&amp;rsquo;s about making existing functionality accessible through a different interface. The wrapped class still does the same work. The adapter just changes how you talk to it.&lt;/p>
&lt;h2 id="the-problem-it-solves">The Problem It Solves&lt;/h2>
&lt;p>Imagine you&amp;rsquo;re building an application that sends SMS messages. You define a clean interface:&lt;/p>
&lt;pre>&lt;code class="language-csharp">public interface ISmsService
{
Task&amp;lt;SmsResult&amp;gt; SendAsync(string phoneNumber, string message);
}
&lt;/code>&lt;/pre>
&lt;p>Your application code uses this interface everywhere. Then you integrate with a third-party SMS provider. Their SDK looks like this:&lt;/p>
&lt;pre>&lt;code class="language-csharp">public class TwilioClient
{
public MessageResource Create(CreateMessageOptions options)
{
// Twilio's actual implementation
}
}
&lt;/code>&lt;/pre>
&lt;p>The signatures don&amp;rsquo;t match. The Twilio client uses different parameter types, different return types, and a different method name. You could change your interface to match Twilio, but then:&lt;/p>
&lt;ul>
&lt;li>Your code becomes coupled to Twilio&amp;rsquo;s API&lt;/li>
&lt;li>Switching providers means changing your interface and all call sites&lt;/li>
&lt;li>Testing becomes harder because you&amp;rsquo;re tied to their types&lt;/li>
&lt;/ul>
&lt;h2 id="core-structure-and-roles">Core Structure and Roles&lt;/h2>
&lt;p>The Adapter pattern has three parts:&lt;/p>
&lt;p>&lt;strong>Target interface&lt;/strong>: The interface your application expects.&lt;/p>
&lt;pre>&lt;code class="language-csharp">public interface ISmsService
{
Task&amp;lt;SmsResult&amp;gt; SendAsync(string phoneNumber, string message);
}
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Adaptee&lt;/strong>: The existing class with an incompatible interface (often a third-party library).&lt;/p>
&lt;pre>&lt;code class="language-csharp">// This is Twilio's class - you don't control it
public class TwilioClient
{
public MessageResource Create(CreateMessageOptions options) { ... }
}
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Adapter&lt;/strong>: The class that implements the target interface and wraps the adaptee.&lt;/p>
&lt;pre>&lt;code class="language-csharp">public class TwilioSmsAdapter : ISmsService
{
private readonly TwilioClient _client;
private readonly string _fromNumber;
public TwilioSmsAdapter(TwilioClient client, string fromNumber)
{
_client = client;
_fromNumber = fromNumber;
}
public Task&amp;lt;SmsResult&amp;gt; SendAsync(string phoneNumber, string message)
{
var options = new CreateMessageOptions(new PhoneNumber(phoneNumber))
{
From = new PhoneNumber(_fromNumber),
Body = message
};
var result = _client.Create(options);
return Task.FromResult(new SmsResult
{
Success = result.Status != MessageResource.StatusEnum.Failed,
MessageId = result.Sid
});
}
}
&lt;/code>&lt;/pre>
&lt;p>Now your application code works with &lt;code>ISmsService&lt;/code>. It doesn&amp;rsquo;t know or care that Twilio is behind it.&lt;/p>
&lt;h2 id="adapter-vs-similar-patterns">Adapter vs Similar Patterns&lt;/h2>
&lt;p>&lt;strong>Adapter vs Facade&lt;/strong>: A facade simplifies a complex interface. An adapter converts one interface to another. A facade might combine multiple calls into one; an adapter typically wraps a single class and changes its shape.&lt;/p>
&lt;p>&lt;strong>Adapter vs Decorator&lt;/strong>: A decorator adds behavior while keeping the same interface. An adapter changes the interface without adding behavior. If the input and output interfaces are the same, it&amp;rsquo;s probably a decorator. If they&amp;rsquo;re different, it&amp;rsquo;s an adapter.&lt;/p>
&lt;p>&lt;strong>Adapter vs Proxy&lt;/strong>: A proxy controls access to an object while keeping the same interface. An adapter changes the interface. A proxy might add lazy loading, caching, or access control. An adapter just translates.&lt;/p>
&lt;p>&lt;strong>Adapter vs Wrapper&lt;/strong>: &amp;ldquo;Wrapper&amp;rdquo; is a general term. Adapters, decorators, and proxies are all wrappers. When someone says &amp;ldquo;wrapper&amp;rdquo; without being specific, they often mean an adapter.&lt;/p>
&lt;h2 id="adapter-with-dependency-injection">Adapter with Dependency Injection&lt;/h2>
&lt;p>Adapters fit naturally into DI. You register the adapter as the implementation of your interface:&lt;/p>
&lt;pre>&lt;code class="language-csharp">services.AddSingleton&amp;lt;TwilioClient&amp;gt;();
services.AddScoped&amp;lt;ISmsService, TwilioSmsAdapter&amp;gt;();
&lt;/code>&lt;/pre>
&lt;p>If you have multiple adapters (say, for different SMS providers), you might use a factory or keyed services:&lt;/p>
&lt;pre>&lt;code class="language-csharp">services.AddScoped&amp;lt;TwilioSmsAdapter&amp;gt;();
services.AddScoped&amp;lt;SendGridSmsAdapter&amp;gt;();
services.AddScoped&amp;lt;ISmsService&amp;gt;(sp =&amp;gt;
{
var config = sp.GetRequiredService&amp;lt;IConfiguration&amp;gt;();
var provider = config[&amp;quot;SmsProvider&amp;quot;];
return provider switch
{
&amp;quot;Twilio&amp;quot; =&amp;gt; sp.GetRequiredService&amp;lt;TwilioSmsAdapter&amp;gt;(),
&amp;quot;SendGrid&amp;quot; =&amp;gt; sp.GetRequiredService&amp;lt;SendGridSmsAdapter&amp;gt;(),
_ =&amp;gt; throw new InvalidOperationException($&amp;quot;Unknown SMS provider: {provider}&amp;quot;)
};
});
&lt;/code>&lt;/pre>
&lt;p>The rest of your application just injects &lt;code>ISmsService&lt;/code> and doesn&amp;rsquo;t care which provider is configured.&lt;/p>
&lt;h2 id="testing-with-adapters">Testing with Adapters&lt;/h2>
&lt;p>Adapters make testing easier in two ways.&lt;/p>
&lt;p>&lt;strong>Testing your application code&lt;/strong>: You mock the target interface, not the third-party SDK. Much simpler.&lt;/p>
&lt;pre>&lt;code class="language-csharp">[Fact]
public async Task NotifyUser_SendsSms()
{
var mockSms = new Mock&amp;lt;ISmsService&amp;gt;();
mockSms
.Setup(x =&amp;gt; x.SendAsync(It.IsAny&amp;lt;string&amp;gt;(), It.IsAny&amp;lt;string&amp;gt;()))
.ReturnsAsync(new SmsResult { Success = true });
var service = new NotificationService(mockSms.Object);
await service.NotifyUser(&amp;quot;+15551234567&amp;quot;, &amp;quot;Your order shipped!&amp;quot;);
mockSms.Verify(x =&amp;gt; x.SendAsync(&amp;quot;+15551234567&amp;quot;, &amp;quot;Your order shipped!&amp;quot;), Times.Once);
}
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Testing the adapter itself&lt;/strong>: You can test that the adapter correctly translates between your interface and the third-party SDK. This is where you verify the mapping logic.&lt;/p>
&lt;pre>&lt;code class="language-csharp">[Fact]
public async Task SendAsync_CreatesTwilioMessageWithCorrectOptions()
{
var mockTwilio = new Mock&amp;lt;TwilioClient&amp;gt;();
var adapter = new TwilioSmsAdapter(mockTwilio.Object, &amp;quot;+15559999999&amp;quot;);
await adapter.SendAsync(&amp;quot;+15551234567&amp;quot;, &amp;quot;Hello&amp;quot;);
mockTwilio.Verify(x =&amp;gt; x.Create(
It.Is&amp;lt;CreateMessageOptions&amp;gt;(o =&amp;gt;
o.To.ToString() == &amp;quot;+15551234567&amp;quot; &amp;amp;&amp;amp;
o.Body == &amp;quot;Hello&amp;quot;)));
}
&lt;/code>&lt;/pre>
&lt;h2 id="common-pitfalls-and-code-smells">Common Pitfalls and Code Smells&lt;/h2>
&lt;p>&lt;strong>Adapters that add business logic&lt;/strong>: An adapter should translate, not make decisions. If you&amp;rsquo;re adding validation, calculations, or business rules, that logic belongs elsewhere.&lt;/p>
&lt;p>&lt;strong>Leaky adapters&lt;/strong>: If your adapter exposes types from the adaptee in its public interface, you haven&amp;rsquo;t fully isolated the dependency. Keep the adaptee&amp;rsquo;s types internal to the adapter.&lt;/p>
&lt;p>&lt;strong>One adapter per method&lt;/strong>: If you find yourself creating a new adapter class for every method on a third-party SDK, step back. You might need a single adapter with multiple methods, or you might be over-abstracting.&lt;/p>
&lt;p>&lt;strong>Adapters that don&amp;rsquo;t handle errors&lt;/strong>: Third-party SDKs throw their own exceptions. Your adapter should catch those and translate them into your application&amp;rsquo;s error handling approach (exceptions, result types, whatever you use).&lt;/p>
&lt;pre>&lt;code class="language-csharp">public async Task&amp;lt;SmsResult&amp;gt; SendAsync(string phoneNumber, string message)
{
try
{
var result = await _client.SendAsync(phoneNumber, message);
return SmsResult.Success(result.Id);
}
catch (TwilioException ex)
{
return SmsResult.Failed(ex.Message);
}
}
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Not adapting configuration&lt;/strong>: If the third-party SDK needs configuration (API keys, endpoints, timeouts), the adapter should handle that. Don&amp;rsquo;t leak configuration concerns to callers.&lt;/p>
&lt;h2 id="when-not-to-use-an-adapter">When Not to Use an Adapter&lt;/h2>
&lt;p>&lt;strong>When the interfaces already match&lt;/strong>: If the third-party SDK already fits your needs, wrapping it adds indirection without benefit.&lt;/p>
&lt;p>&lt;strong>For internal code you control&lt;/strong>: If you own both sides of the interface mismatch, consider changing one of them instead of adding an adapter.&lt;/p>
&lt;p>&lt;strong>When you&amp;rsquo;ll never switch implementations&lt;/strong>: If you&amp;rsquo;re absolutely certain you&amp;rsquo;ll never change providers, the isolation benefit is reduced. But be honest with yourself about how certain &amp;ldquo;absolutely certain&amp;rdquo; really is.&lt;/p>
&lt;p>&lt;strong>For trivial translations&lt;/strong>: If the adapter is just renaming a method with no other changes, it might not be worth the extra class.&lt;/p>
&lt;h2 id="practical-guidelines">Practical Guidelines&lt;/h2>
&lt;p>A few things to keep in mind:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Name adapters after what they adapt.&lt;/strong> &lt;code>TwilioSmsAdapter&lt;/code> or &lt;code>SendGridSmsAdapter&lt;/code> makes the purpose clear.&lt;/li>
&lt;li>&lt;strong>Keep adapters focused.&lt;/strong> One adapter per third-party integration. Don&amp;rsquo;t create a mega-adapter that wraps multiple unrelated SDKs.&lt;/li>
&lt;li>&lt;strong>Handle the adaptee&amp;rsquo;s quirks inside the adapter.&lt;/strong> If Twilio uses different status codes, the adapter normalizes them.&lt;/li>
&lt;li>&lt;strong>Define your target interface based on your needs, not the adaptee.&lt;/strong> Design the interface you wish you had, then write adapters to make it work.&lt;/li>
&lt;li>&lt;strong>Consider async from the start.&lt;/strong> Most third-party integrations are I/O bound. Design your target interface with &lt;code>Task&lt;/code> return types even if your first adapter wraps a synchronous SDK.&lt;/li>
&lt;/ul>
&lt;p>The Adapter pattern is a straightforward way to isolate your code from external dependencies. It keeps third-party quirks contained, makes testing easier, and gives you the flexibility to swap implementations later. Use it whenever you integrate with something you don&amp;rsquo;t control.&lt;/p></description></item></channel></rss>