Don't Marry Third Party Services


We all use third party services, things like Stripe for payments, Sendgrid for email or Sentry for error reporting. Those services offer to solve some of our engeneering problems in exchange for money.

Most of the time they are very convenient to use, as rolling your own solution would take considerable time and effort, and an in-house solution could even be more expensive than just paying for the service in the first place.

That being said, when applications consume third party services, sadly they all make the same mistake of marrying their services. What do I mean by marry? I mean that once you commit to a service, replacing it is not as easy.

Code should be easy to change

Code is always changing, and one of the most important qualities of good code is that it must be easy to change. This also applies to any kind of service (or dependency) your application has.

Let’s say you use Sendgrid for sending emails, your code might look something like this:

const msg = {
  to: 'test@example.com', // Change to your recipient
  from: 'test@example.com', // Change to your verified sender
  subject: 'Sending with SendGrid is Fun',
  text: 'and easy to do anywhere, even with Node.js',
  html: '<strong>and easy to do anywhere, even with Node.js</strong>',
}

const response = await sendgrid.send(msg);
console.log(response[0].statusCode);
console.log(response[0].headers);

The example above is taken from Sendgrid docs. Nothing wrong with that code, the problem is when you use that code directly on your app.

Imagine every time you send a mail you just import sendgrid and call the send method. If you are using sendgrid in 10 different places, and you have to replace Sendgrid with Amazon SES, you’ll have to make 10 changes to your code.

That’s not what good code looks like, and it breaks the Single Responsibility Principle:

Gather together the things that change for the same reasons. Separate those things that change for different reasons — Robert C. Martin

So how can we solve it? Let’s put the things that change together in a class:

interface Message {
  to: string;
  from: string;
  subject: string;
  text: string;
  html: string;
}

class Mailer {
  send(message: Message): MailResponse {
    sendgrid.send(message);
  }
}

Now, it doesn’t matter how many instances of Mailer you are using, if you want to replace sendgrid, you only need to make a change in one place. That’s clearly better design, and it didn’t really take much work, right?

What you want to do is to isolate third party dependencies as much as possible. In other words, they should be used in as few places as possible. A common way is to wrap them in a class, and use that class instead of using the service directly.

You could even have multiple mailers, all implementing the same interface. That way you can choose at runtime which mailer you want to use, or just configure them in your Dependency Injection system if you use one. This also makes it trivial to mock this class for unit testing.

Conclusion

Don’t use third party services directly in your code. Wrapping them in classes in an easy way to be able to change them easily in the future if you ever need to.

Using interfaces is a more fancy alternative, with some added benefits such as being able to use multiple mailers and easier testing.

Following SOLID design principles makes it easy to adapt our code to whatever the needs are, even if they change (and they will!).

Want updates on our latest blog posts?
Subscribe to our newsletter!

Previous Post
Dreamforce 2024 Recap
Next Post
Rome FileMaker® Week 2024: iOS Widgets & Local File Technique