Wrap It Up - Deferring Unknown Dependencies with the Wrapper Pattern

April 16, 2024#Software Development
Article
Author image.

Scott DePouw, Senior Consultant

Dependencies on external resources can make code difficult to test or maintain. This is hardly a hot take. Recently, a user in the devBetter Discord server had to work with data that came from outside their application, and that they wouldn’t have access to that data for a while. They were ready to get coding, and wanted to implement access and use of that data, but had no means of getting it. It wasn’t even made clear if there was an existing abstraction or interface that could be mocked or faked, and if they guessed wrong it would require re-work.

The solution? Wrap it up!

The Wrapper Pattern

Simply-put, if you have some resource you’re depending on then you can create your own abstraction, wrapping the real implementation up. The application running in production will use the wrapped implementation to get to the real resource, while you are free to stub, fake, or mock out the abstraction when developing.

Example Wrapper Pattern Implementation

Let’s say you’re working against the local file system and need to read data in from a known file on disk. The quick way to get this done would be to call File.ReadAllText("Path/To/File") and call it a day.

public class MyClass
{
  public void DoStuff()
  {
    string filePath = @"C:\Foo\bar.txt"; // Normally stored in configuration, but hardcoded here for the demo.
    string fileContents = File.ReadAllText(filePath);
    
    // Do stuff with the file's contents.
  }
}

Ignoring the other implications of how difficult this is made to unit test (you would need the file on disk at the time of the test runs, etc.), imagine you didn’t have this magical file yet. What’s a developer to do?

Wrap it up!

public interface IFileSystem
{
  string ReadAllText(string filePath);
}

public class FileSystemWrapper : IFileSystem
{
  public string ReadAllText(string filePath) => File.ReadAllText(filePath);
}

public class MyClass(IFileSystem _fileSystem)
{
  public void DoStuff()
  {
    string filePath = @"C:\Foo\bar.txt";
    string fileContents = _fileSystem.ReadAllText(filePath);
    
    // Do stuff with the file's contents.
  }
}

Now you have a handy abstraction in the form of IFileSystem, and it does not matter that you don’t have the resource available to you at development time. Just like any other abstraction you are free to fake it until it’s available, and there’s no need to wrestle with the file system in testing or builds.

Wrappers Mimic Their Contents

Notice we didn’t do any funny business within FileSystemWrapper. Its method signature matches precisely what it’s wrapping. Generally-speaking, you don’t want to mutate what’s being wrapped, as you don’t want to deviate from what other developers (including yourself) may be used to. File.ReadAllText() is widely-known, but if you made your wrapper method GetTextContents() that may not read as obviously as the former.

There are occasions where you may find yourself wanting to deviate from this. Maybe you want to add a check to see if the file in question exists before attempting to read it, and return a default value, or throw an exception. Whatever makes the most sense for your business concerns and application is what you should roll with.

Implement Later, Wrap Now

Another perk of the wrapper pattern is that you need only define the abstraction to get started. Let’s say instead of file access, you want to fetch data from a remote API resource that requires special authentication and other key pieces of information to properly access and format. Let’s also say you don’t necessarily know the shape of the data being returned, just what pieces you’re eventually going to be given. Like our friend in the Discord server, you’re ready to implement your application but are stuck waiting on all those important details. Essentially, you don’t know how to implement your wrapper class, and won’t be able to get that knowledge for some time.

Don’t worry about it! You can define your abstraction and fret over the implementation details when they become available. Until then, you can freely work in your own application by defining your own abstraction, and leaving the rest for later.

// You know what data is coming, and only care about those properties. The original shape doesn't matter.
public class CriticalData
{
  public string LockerCombination { get; set; }
  public int[] WinningLottoNumbers { get; set; }
  public bool IsTheArcadeStillOpen { get; set; }
}

// As before, define your abstraction so that you can start writing and testing your code.
public interface ICriticalResourceRepository
{
  Task<CriticalData> GetCriticalStuffAsync(CancellationToken cancellationToken);
}

// You don't know the full implementation details yet, so this will throw for now.
// If you're just writing unit tests, you don't need to have this class at all yet!
public class CriticalResourceRepository : ICriticalResourceRepository
{
  public Task<CriticalData> GetCriticalStuffAsync(CancellationToken cancellationToken)
  {
    throw new NotImplementedException();
  }
}

If you find yourself needing to run the application, you can stub an implementation just like with any other interface.

public class CriticalResourceRepository : ICriticalResource
{
  public Task<CriticalData> GetCriticalStuffAsync(CancellationToken cancellationToken)
  {
    // TODO: Replace with real implementation once details are available.
    CriticalData fakeData = new()
    {
      LockerCombination = "12345",
      WinningLottoNumbers = [ 4, 8, 15, 16, 23, 42 ],
      IsTheArcadeStillOpen = true
    };
    return Task.FromResult(fakeData)
  }
}

The rest of your application is safe from depending on the real resource being implemented or available, and unit testing, of course, is a breeze. Whether the resource being accessed is a known quantity (the File static class) or unknown (an API call with implementation details pending), you can always wrap those dependencies up and push them off until later, and build/test your application without delay.

Wrapping Up Wrap It Up

Whether the resource you’re wrapping is a known quantity (e.g. the file system) or an unknown (e.g. an API that will be defined later in the development cycle), the wrapper pattern lets you get coding immediately. You can write and test your application without fretting over the external dependency you now inject into your application, freeing you up from having to wait.

Of course, when the real dependency is available (now or later), you absolutely run and test your application with the real resource in store. Testing with a mocked instance is all well and good, but at the end of the day, production runs with the real thing. But with the wrapper pattern in place you can test and build the application all the way to that point, and then make adjustments as needed when integrating with the real deal.


Copyright © 2024 NimblePros - All Rights Reserved