Best practices for integrating with third-party libraries in Elixir
December 19, 2018
When we think about what an application does, it's typical to think of how it behaves in context of its dependencies. For example, we could say a ficticious application sync's data with a third-party CRM.
The way we think about our application impacts how we make abstractions in our code. If we think about a typical web application, we might have a database, router, controllers and some business logic around how we use our data and show it on the page. In many cases, we need to integrate our app with external API's, third-party libraries and more.
It's critical for most web applications to abstract concepts to make the code both easier to read and change in the future.
In many other languages, we often see interfaces coupled with dependency injection in use to achieve these goals. In Elixir, the "best practice" approach isn't always as clear.
In this article, I will discuss a typical scenario of integrating with a third-party API and detail a potential approach you could use on your next project.
When we start writing an integration with a third-party, we should think about how the rest of the application will use it and how it should behave in certain circumstances. Our goal should be that we can have a single internal module whose responsibility is to interface with the external dependency.
In most cases, you shouldn't need to write any more code when requirements change - you might have to add extra functionality, but your business logic (the code using the internal module) shouldn't have to change too dramatically just because you moved from "Pretty Good CRM" to "Greatest CRM Ever".
That being said, I don't really subscribe to the idea that your code is ever going to be perfect and that you'll have a perfect abstraction around your CRM of choice, such that you could even swap modules at runtime and be able to use both simultaneously. However, I would expect that it's not going to be a particularly painful piece of work that involves rewriting any of your own business logic.
To help achieve a loose-coupling in our system, we can use a Hexagonal Architecture, a fancy way of saying our goal is to push all external dependencies to the edges of our application, separating our core business logic from some of the side effects that might be performed. Typically, this is implemented by wrapping external libraries (dependencies) and only using those wrapper modules throughout the rest of your code base. A good rule of thumb would be to only have one module that represents an external dependency in your code, whether that's an API or a Database.
In Elixir we use this approach already with the Repo module, which maps to an Ecto data store. When we create a module in our app adopting a certain behaviour, we create a wrapper around Ecto's Repo module.
defmodule MyApp.Repo do
use Ecto.Repo,
otp_app: :my_app,
adapter: Ecto.Adapters.Postgres
end
Now, everywhere in our code we use MyApp.Repo rather than using Ecto directly to run SQL commands. There are other reasons we use a Repo in this way, but I find it's a good conceptual model to represent a wrapper module.
How to go full-hexagonal
Imagine a world where the CRM you chose had a supported library written in Elixir, so you thought you'd use that in your application. It's called ExCRM (just go with it). For us to implement a hexagonal architecture, we would need to push this dependency to the boundary of our application, by creating a single module to wrap the behaviour of the library. Now, whenever we want to push something to our CRM, we need to call this wrapper module, rather than the library directly. In doing so, we only ever reference the library in one place and create a consistent interface with the rest of our application, through the wrapper module.
It might look something like this:
defmodule MyApp.CRM do
def save(user) do
user
|> to_crm()
|> ExCRM.save()
end
defdelegate list_users, to: ExCRM
defp to_crm(user) do
%{"name" => user.name}
end
end
defmodule MyApp.Data do
alias MyApp.{CRM, Repo}
def create(user) do
with {:ok, user} <- Repo.create(user) do
CRM.save(user)
end
end
end
At first this looks like a little bit of indirection, and because it's a contrived example it's hard to see the benefits straight away.
The effect of writing our code in this way is that it limits the blast radius should things change in the library or our own app's requirements. Through limiting the entry-points for the library in your application, we are able to minimize the impact of any such change. While it takes slightly more effort in the beginning to set up, however when business requirements change, you can change your implementation without refactoring lots of different files within your codebase.
Testing your code becomes easier through using this method as you will only need to mock your internal module, rather than the library itself, in all other parts of your codebase.
Isolating dependencies is not a new concept, and in most other programming languages there are clear examples of how to do this, particularly in Object-Oriented languages such as PHP or Ruby.
It's often thought that functional programming and object-oriented programming are at odds with each other and have very different approaches to solving these types of problems, but they actually have a lot of overlapping concepts. Both approaches have the goal of creating maintainable, bug-free applications, sharing concepts but differing in implementation.
While in Elixir, we think about transforming data from one form to another as opposed to instances of objects that have state, we can still use similar patterns and adapt them for Elixir. There are more parallels than you might think.
February 12, 2024
I spend most of my time thinking about performance improvements. Refactoring is tricky work, even more so when you’re unfamiliar with the feature or part of the codebase.
May 31, 2023
Asynchronous programming is a foundational building block for scaling web applications due to the increasing need to do more in each web request. A typical example of this is sending an email as part of a request.
April 01, 2022
I have mixed feelings about feature flags. They are part of the product development workflow and you would be hard pressed to find a product engineering team that doesn’t use them. Gone are the days of either shipping and hoping the code will work first time or testing the life out of a feature so much that it delays the project.
March 18, 2022
When I first started interviewing candidates for engineering roles, I was very nervous. The process can be quite daunting as both an interviewer and interviewee. The goal for the interviewer is to assess the candidate for their technical capabilities and make a judgement on whether you think they should move to the next round (there’s always a next round). Making a judgement on someone after an hour, sometimes a bit longer, is hard and error prone.
June 03, 2020
Dependency Injection is the method of passing objects to another (usually during instantiation) to invert the dependency created when you use an object. A Container is often used as a collection of the objects used in your system, to achieve separation between usage and instantiation.
April 17, 2020
Working from home has been thrust upon those lucky enough to still have a job. Many aren’t sure how to cope, some are trying to find ways to help them through the day. Make no mistake, this is not a normal remote working environment we find ourselves in, but nonetheless we should find ways to embrace it.
September 12, 2019
It’s been a while since I last wrote about why testing is important, but in this post I thought I would expand on that and talk about why not only unit testing is important, but how a full spectrum of automated tests can improve productivity, increase confidence pushing code and help keep users happy.
July 05, 2019
Design Patterns allow you to create abstractions that decouple sections of a codebase with the purpose of making a change to the code later a much easier process.
April 14, 2019
Ever get the feeling that adding this "one little hack", a couple of lines of code, won't have much of an impact on the rest of the codebase? You think nothing of it and add it, convincing your team members it was the correct decision to get this new feature over the line. In theory, and generally speaking, I would kind of agree with doing it, but every hack is different so it's hard to paint them all with the same brush. If you've been doing software development for long enough you can see this kind of code coming from a mile away. It's the kind of code that can haunt your dreams if you're not careful.
March 04, 2019
Last week was Lonestar ElixirConf 2019 held in Austin, Texas. The conference ran over 2 days and was the first Elixir conference I had been to.
February 01, 2019
In most cases I have found inter-process communication to be an unnecessary overhead for the work I have been doing. Although Elixir is known for this (along with Erlang), it really depends on what you’re trying to achieve and processes shouldn’t be spawned just for the fun of it. I have recently come across a scenario where I thought having a separate process be responsible for performing concurrent and asynchronous jobs would be the best way to approach the problem. In this article I will explain the problem and the solution.
December 19, 2018
When we think about what an application does, it's typical to think of how it behaves in context of its dependencies. For example, we could say a ficticious application sync's data with a third-party CRM.
November 20, 2018
When you're browsing your way through Elixir documentation or reading blog posts (like this one), there's no doubt you'll come across a GenServer. It is perhaps one of the most overused modules in the Elixir standard library, simply because it's a good teaching tool for abstractions around processes. It can be confusing though, to know when to reach for your friendly, neighbourhood GenServer.
October 30, 2018
Typically in an application with a database, you might have more records than you can fit on a page or in a single result set from a query. When you or your users want to retrieve the next page of results, two common options for paginating data include:
September 26, 2018
Protocols are a way to implement polymorphism in Elixir. We can use it to apply a function to multiple object types or structured data types, which are specific to the object itself. There are two steps; defining a protocol in the form of function(s), and one or many implementations for that protocol.
August 23, 2018
Recently, I've been writing a tonne of Elixir code, some Phoenix websites and a few other small Elixir applications. One thing that was bugging me every time I would create a new project is that I would want to add Docker to it either straight away because I knew there would be a dependency on Redis or Postgres etc, or halfway through a project and it would really slow down the speed at which I could hack something together.
July 14, 2018
Concurrency in Elixir is a big selling point for the language, but what does it really mean for the code that we write in Elixir? It all comes down to Processes. Thanks to the Erlang Virtual Machine, upon which Elixir is built, we can create process threads that aren't actual processes on your machine, but in the Erlang VM. This means that in an Elixir application we can create thousands of Erlang processes without the application skipping a beat.
July 06, 2018
Ecto is an Elixir library, which allows you to define schemas that map to database tables. It's a super light weight ORM, (Object-Relational Mapper) that allows you to define structs to represent data.
June 27, 2018
We often think about Streaming as being the way we watch multimedia content such as video/audio. We press play and the content is bufferred and starts sending data over the wire. The client receiving the data will handle those packets and show the content, while at the same time requesting more data. Streaming has allowed us to consume large media content types such as tv shows or movies over the internet.
June 06, 2018
A Queue is a collection data structure, which uses the FIFO (First In, First Out) method. This means that when you add items to a queue, often called enqueuing, the item takes its place at the end of the queue. When you dequeue an item, we remove the item from the front of the queue.
March 23, 2018
Elixir is a functional language, so it’s no surprise that one of the main building blocks of the request-response cycle is the humble Plug. A Plug will take connection struct (see Plug.Conn) and return a new struct of the same type. It is this concept that allows you to join multiple plugs together, each with their own transformation on a Conn struct.
February 06, 2018
A Supervision Tree in Elixir has quite a number of parallels to how developers using React think about a component tree. In this article I will attempt to describe parallel concepts between the two - and if you've used React and are interested in functional programming, it might prompt you to take a look at Elixir.
December 21, 2017
Technical debt is a potentially crippling disease that can take over your codebase without much warning. One day, you’re building features, the next, you struggle to untangle the mess you (or maybe your team) has created.
August 15, 2017
Before being introduced to Elixir, a functional programming language built on top of Erlang, I had no idea what pattern matching was. Hopefully, by the end of this article you will have at least a rudimentary understanding of how awesome it is.
January 06, 2017
Elixir is a functional programming language based on Erlang. I’m told it’s very similar to Ruby, with a few tweaks and improvements to the developer experience and language syntax.