How Merge uses Fern to ship SDKs

··

9 min read

Cover Image for How Merge uses Fern to ship SDKs

Merge provides Unified APIs that allow B2B organizations to offer categories of integrations, whether that’s CRM, file storage, HRIS, ATS, among others. This approach to integration allows organizations to dramatically simplify their integration development. Moreover, by leveraging Merge’s maintenance support and management tooling, organizations can offer more reliable and high-performing integrations.

Without Merge, developers often need to write redundant code to support competitive but semantically similar integrations (e.g., CRM integrations like Salesforce, HubSpot, and Pipedrive). Merge is the difference between building one integration or fifty.

However, by replacing native integrations, Merge is also functionally replacing any associated native SDKs. A Merge customer uses a Merge SDK, not a Salesforce SDK. Given this, it follows that Merge needs a very good SDK—something that is developer friendly, well documented, and consistent across all languages. The issue is that Merge is, at heart, an API company, not an SDK company—and building an in-house SDK is time-consuming, error-prone, and ultimately distracting.

Originally, the company trusted OpenAPI’s SDK Generator, which resulted in a fragmented, error-prone, and limited SDK. Then, Merge switched to a Fern-based SDK workflow. With Fern, Merge is able to leverage advanced code automation to create a robust, well-documented SDK that appeals to the modern developer. Let’s explore those explicit advantages.

Some detailed history

Originally, Merge was maintaining two different types of SDKs.

The first was a language-specific, vertical-specific SDK (e.g., Go SDK for CRM integrations) dated to Merge’s early days. The second was a language-specific, consolidated SDK for all integrations (e.g., Node SDK for all integrations) that emerged later.

While consolidating SDKs was a step in the right direction, Merge was struggling with SDK management, especially with its lean engineering team philosophy. In practice, Merge needed to create and maintain multiple SDKs in parallel while focusing most of its engineering efforts on its core API product.

The typical solution to this problem is to turn to code generation. At first, Merge extended OpenAPI’s Generator to accomplish this. Unfortunately, OpenAPI’s exported code was often verbose and confusing, requiring many hours of engineering maintenance per library per week. And with Merge’s support for Node, Go, Ruby, Java, C#, and Python, this problem was becoming a big problem.

Once Merge determined it needed a more robust solution, it evaluated a few SDK vendors, eventually choosing Fern because of the idiomatic code generated and seamless automation in the CI/CD pipeline.

Implementing Fern

While Fern is rather easy to implement, Merge moved especially fast at installing and deploying Fern. Less than four weeks passed between the initial sales call and the first Fern-based SDKs going live.

Two things made this possible. First was Merge’s immediate buy-in after evaluating the Fern product; rolling out Fern became an immediate high priority for them. The second was Fern’s onboarding strategy, in which a mock GitHub organization filled with mock repositories is created to vet the workflow. Because Fern can be installed in various ways, this helps a customer explore and determine the best strategy for them.

Eventually, Merge decided on a system that would allow Fern to automate code generation and testing but would still require a human to merge (pun intended) the final pull request before deploying code to production.

How Merge’s Fern workflow works

Merge created a private config repository to commit changes to its OpenAPI specification and Fern configuration. This repository also controls settings for the SDK exports.

Whenever a developer makes a pull request into this private config repository, Fern’s bot will scan the change and validate the API definition.

After the commit is made to the main branch, a GitHub action triggers Fern to generate a new SDK. Fern’s bot will then make a pull request into each of the language-specific production-facing SDK libraries (e.g., Merge-Node-Client).

This final step allows a human to audit the final changes to the SDK, which is particularly helpful if changes need to be timed with a press release or documentation refresh.

Fern’s immediate impact

From the start, Fern enabled Merge to score many benefits for themselves and their end users.

First, it allowed Merge to pause development on consolidating its SDKs because Fern’s generated SDK would already be consolidated across all languages.

Second, it created a single source of truth where a private Merge GitHub repository seeded and documented all of Merge’s SDKs.

In a nutshell, Merge gained better and more consistent SDK code generation without losing any control in the process.

Additional impacts

While Merge’s primary objective in adopting Fern was to bring order to its SDK development, there were a series of second-order benefits that came with the change.

A cleaner SDK experience

Fern’s SDK exports result in a cleaner, more readable SDK that mirrors Merge’s API prowess. The best way to demonstrate this is to use a before-and-after comparison of both SDKs, side by side.

Previously, using an ATS (applicant tracking system) integration from Merge’s old SDK would’ve required an independent install of the Merge ATS SDK:

npm add @mergeapi/merge-ats-node

Then, afterward, calling the ATS SDK:

import { CandidatesApi, HttpBearerAuth } from "@mergeapi/merge-ats-node";

const auth = new HttpBearerAuth();
auth.accessToken = "YOUR_API_KEY";

// Substitute AccountDetailsApi with the API you're trying to use
const candidatesApi = new CandidatesApi();
candidatesApi.setDefaultAuthentication(auth);

const response = await candidatesApi.candidatesList(
  "YOUR_ACCOUNT_TOKEN",
  undefined,
  undefined,
  true,
  undefined);
const responseBody = response.body;

The code is clunky and lengthy.

Now compare that with Fern’s analog export, which requires no siloed SDK install:

import { MergeClient, Merge } from "@mergeapi/merge-node-client";

const merge = new MergeClient({
  apiKey: "YOUR_API_KEY",
  accountToken: "YOUR_ACCOUNT_TOKEN",
});

const response = await merge.ats.candidates.list({
  includeDeletedData: true,
})

Merge’s Fern-based SDK is far more concise. Additionally, all of the types are neatly organized within the Merge namespace.

This cleaner, hierarchal SDK results in happier developers who spend less time buried in documentation and more time building.

Documentation

Today, developers tend to peer through SDK source code to understand what is possible with it. Doing so helps developers both to understand an SDK’s underlying structure and audit an SDK’s code quality.

Because of this, Fern makes it easy to inject documentation directly into SDK code as a comment. This allows Merge’s users to quickly process an SDK’s design, especially if they use an IDE like VS Code. Additionally, this comes with little effort from Merge engineers because the documentation they ingest into the OpenAPI document is also used for general documentation.

The best way to understand how beneficial this is to end users is to run through an example. In Merge’s Python SDK, there is a class that returns any tags associated with an ATS solution. The class is declared at the following structured path:

merge-python-client / src / merge / resources / ats / resources / tags / [client.py](http://client.py)``

After the class’s strongly-typed list, there is an embedded comment that explains exactly what each of the inputs does:

The best part is that engineers don’t even need to open this file to read the comment; if they use an IDE like VS Code, the comment will appear in the function’s hover-over definition.

Backward compatibility

Fast-evolving startups like Merge go through a lot of changes in short periods of time, and this can make backward compatibility a challenge. A common problem is that installations of previous versions of an SDK may fail due to newly-introduced enums—which, when fatal, can lead to applications failing in production.

Fern’s bot safeguards against this by detecting incompatible API changes, such as an object path parameter referencing a non-existent schema. Fern also safeguards against future enums by making the SDK forward-compatible, preventing it from hard-failing if it encounters an unfamiliar value. Additionally, on Fern, changes can be attributed to specific language/framework versions (e.g., Node 14.0) to prevent failures.

The end benefit: fewer breaking changes and fewer customer-losing incidents.

Support for unions

One of OpenAPI Generator’s biggest problems is its poor support for unions. Unions are necessary whenever an SDK class is expecting either of two (or more) types as instantiation inputs. While OpenAPI does support unions, it relies on very long and verbose definitions to accommodate them.

Previously, Merge’s SDK did not support unions. For instance, in its HRIS-specific SDK, a call to BankInfoList wouldn’t be able to support unions in the return object.

for (BankInfo bankInfo of result.getResults()) {
  UUID id = bankInfo.getEmployee(); // what if its an expandable object?
}

This changed with Fern--one of Fern's cornerstone design principles was its native support for polymorphism. Now, Merge's Fern-based SDK has support for unions:

for (BankInfo bankInfo of response.getResults().get()) {
    BankInfoEmployee employee = bankInfo.getEmployee();
  // visit the case of it being either an id or an expandable employee
    employee.visit(new EmployeeVisitor());
  // or just visit inline
  if (employee.get() instanceof String) {
    System.out.println("Received employee id", id);
  }
}

class EmployeeVisitor implements new BankInfoEmployee.Visitor<Void> {
    void visit(String id) {
            System.out.println("Received employee id", id);
            return null;
    } 

    void visit(Employee employee) {
            System.out.println("Received employee", employee);
            return null;
    } 
}

Support for async and sync code

For some languages, supporting both asynchronous and synchronous requests isn’t necessary. For example, Node is a non-blocking (async) framework, and therefore, requests are typically async. The same could be said about Go.

However, Python developers often want to support both sync and async API requests, depending on the context. For instance, if an API might take nontrivial time to respond, an async request may be preferred to avoid locking up a thread.

Before Fern, Merge was only able to support synchronous requests. Today, Merge supports both. Merge’s Python SDK automatically generates two Python classes—Merge and AsyncMerge. And given Fern’s code automation, these classes are automatically maintained.

Fern’s live code snippets in Merge’s documentation

Fern is a company that develops two complementary products: (i) an SDK code generation tool and (ii) a documentation tool that competes with products like ReadMe and Stoplight.

Merge strictly uses the former and for good reason; because Merge is a bespoke developer tool, it has specialized documentation to support its “API of APIs” hierarchy. As a result, Merge is one of the few companies that opted for an in-house documentation system.

However, with the recommendation from Merge, Fern built a middle-ground solution that enables Merge’s developers to query Fern’s API to inject dynamic code examples into Merge’s documentation.

This enables Merge’s users to see up-to-date code examples in their language of choice that mirror the latest SDK build.

The result is better, more up-to-date documentation for Merge users and less maintenance work for Merge’s documentation team. You can explore the live code snippets today in Merge’s documentation!

The classic build versus buy

A common challenge for development teams is build versus buy. In this scenario, Merge decided to buy a product (Fern) instead of building a bespoke SDK flow. Hypothetically, Merge could’ve built a bespoke flow that refurbishes OpenAPI-generated SDKs. However, it would be costly, error-prone, and distracting.

Closing thoughts

Merge is a master of APIs and chose Fern to generate its SDKs to provide a better, cleaner, and more consistent experience for Merge users. Fern made the lives of Merge’s engineers easier, allowing them to focus more on API development than picking apart poorly generated SDKs. Additionally, Fern created a better documentation experience for developers, better support for polymorphic code conventions, a cleaner SDK experience, and minimized backward compatibility issues.

In the words of Gil Feig, Merge’s CTO, “Since adopting Fern, customers have migrated rapidly to our new SDKs and have been blown away by the quality.” In short, Merge focused on its flagship work of consolidating APIs, passing on the corollary SDK work to a dedicated SDK tool, Fern.