惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
量子位
MongoDB | Blog
MongoDB | Blog
N
Netflix TechBlog - Medium
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
A
About on SuperTechFans
腾讯CDC
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
Last Week in AI
Last Week in AI
H
Help Net Security
WordPress大学
WordPress大学
博客园 - 司徒正美
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Tailwind CSS Blog
博客园 - 【当耐特】
S
SegmentFault 最新的问题
美团技术团队
M
MIT News - Artificial intelligence
L
LangChain Blog
博客园 - 聂微东

Learn Cloud Native

Plan-based validation for coding agents on Kubernetes | Learn Cloud Native PR validation at scale and a sandbox for every pull request on Kubernetes | Learn Cloud Native 7 practical MCP policies with agentgateway | Learn Cloud Native Agentgateway rate limiting for agents | Learn Cloud Native Local development with coding agents on Kubernetes using Signadot | Learn Cloud Native cuenv: one typed file for your whole project | Learn Cloud Native Preflight: AI Code Review Before You Push Anatomy of AI Agents Accessing Google Drive from Next.js Deploying to Fly.io using Dagger and Github Top Cloud-Native & Kubernetes Certifications [2026 Guide] Rapid microservices development with Signadot How to prepare for Istio certified associate exam (ICA) Global Rate Limiting in Istio with Envoy Rate Limit Service My Journey with Istio: From Incubation to Graduation Cilium Network Policy Tutorial: Secure Kubernetes Step by Step Kubernetes Networking: How kube-proxy and iptables Work Istio ServiceEntry: DNS vs. STATIC Resolution & Endpoints Explained Apply an Istio DestinationRule Globally (Mesh-Wide) Istio Rate Limiting: Configure a Local Rate Limiter in Envoy How to expose custom ports on Istio ingress gateway Portainer Tutorial: A Web UI for Kubernetes & Containers Traefik Proxy 2.x and TLS 101 Kubernetes CLI (kubectl) tips you didn't know about Setting up SSL certificates with Istio Gateway ArgoCD Best Practices You Should Know 在 OCI Ampere A1 计算实例上运行 AI Running AI On OCI Ampere A1 Instance How to Deploy Traefik Proxy Using Flux and GitOps Principles Firebase Emulators with Next.js: Local Setup Guide
Branch by Abstraction Pattern
Peter Jausovec · 2020-08-14 · via Learn Cloud Native

In the previous article, I wrote about the strangler pattern. The strangler pattern is useful for scenarios where you can intercept the calls at the edge of your monolithic application. In this article, I'll describe a pattern you can use when calls can't be intercepted at the edge.

Scenario

Let's consider a scenario where the functionality you're trying to extract is not called directly from the outside, rather it is being called from multiple other places inside the monolith.

branch by Abstraction Pattern
branch by Abstraction Pattern

In this case, you have to modify the existing monolith, assuming can do that and have access to the code. To minimize the disruptions to existing developers and make changes incrementally you can use the branch by abstraction pattern.

Note

You can get the sample code that goes with this article from Github

Steps

There are five steps to implementing the branch by abstraction pattern:

  1. Create the abstraction
  2. Use the abstraction
  3. Implement the new service
  4. Switch the implementation
  5. Clean up

Let's look at each step in more detail.

1. Create the abstraction

1. Create the abstraction
1. Create the abstraction

The first step is to create an abstraction for the functionality you are extracting. In some cases, you might already have an interface in front of the functionality, and that is enough. If you don't however, you will have to search the code base and find all places where the functionality you're trying to extract is being called from.

As an example, let's consider we are trying to extract the functionality that sends a notification, that looks like this:

function sendNotification(email: string): Promise<string> {
  // code to send the notification
}

As part of the first step, you will create an interface that describes the functionality, something like this:

export interface Notifications {
  sendNotification(email: string): Promise<string>;
}

2. Use the abstraction with the existing implementation

2. Use the abstraction with the existing implementation
2. Use the abstraction with the existing implementation

Once you put the abstraction in place you can refactor existing clients/callers to use the new abstraction point, instead of directly calling the implementation. The nice thing here is that all these changes can be done incrementally. At this point, you haven't made any functional changes per-se, you only re-routed the calls through the abstraction.

Practically speaking at this point you will have to create an implementation for the Notifications interface you created in the previous step. Here's an example of how you'd do that:

class NotificationSystem implements Notifications {
  sendNotification(email: string): Promise<string> {
    // Existing implementation
  }
}

In addition to implementing the interface, you should also consider creating a function that returns the Notifications interface with the desired implementation. At first, you will only have a single implementation, the NotificationSystem class above, but later you will use the same function to implement a feature flag that will allow you to switch between different implementations. For now, the newNotificationSystem would look like this:

export function newNotificationSystem(): Notifications {
  return new NotificationSystem();
}

With this in place, you can search the code base for any calls to sendNotification function and replace it with newNotificationSystem().sendNotification(...).

3. Implement the new service

3. Implement the new service
3. Implement the new service

The existing functionality is now being called behind the abstraction and you can independently work on implementing the new service that will have the same functionality. Similarly, as you would do it with the strangler pattern, you can deploy the new service to production, but don't release it yet (no traffic is being sent to it). This allows you to test the new service and ensure it works as expected.

You can also spend time in this step to create the new implementation of the Notifications abstraction, that will call this new service. For example, something like this:

class ServiceNotificationSystem implements Notifications {
  async sendNotification(email: string): Promise<string> {
    const serviceUrl = process.env.NOTIFICATION_SERVICE_URL;
    if (serviceUrl === undefined) {
      throw new Error(
        `NOTIFICATION_SERVICE_URL environment variable is not set`
      );
    }
    const response = await fetch(serviceUrl, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ email }),
    });
    return response.json();
  }
}

Similarly, you can start updating the newNotificationSystem function and put the new implementation under a feature flag.

Note

Feature flags, also known as toggles or switches, allow you to switch certain functionality on or off through the use of configuration settings/environment variables, without redeploying the code.

Here's how the simple feature flag implementation might look like for our case:

export function newNotificationSystem(): Notifications {
  const useNotificationSystem = process.env.USE_NOTIFICATION_SYSTEM_SERVICE;

  // If variable is  set -> use the new implementation
  if (useNotificationSystem !== undefined) {
    console.log('Using service (new) implementation');
    return new ServiceNotificationSystem();
  }

  console.log('Using existing (old) implementation');
  return new NotificationSystem();
}

We are checking if the USE_NOTIFICATION_SYSTEM_SERVICE environment variable is set, and if it is we return the new implementation (ServiceNotificationSystem). If the environment variable is not set we return the existing (old) implementation (NotificationSystem).

4. Switch the implementation

4. Switch the implementation
4. Switch the implementation

You have deployed the new service and you have a feature flag in place that allows you to switch between implementation. You can now flip the switch (or set the environment variable in our case) to start using the new service implementation, instead of the old implementation.

You would continue to monitor the new service to make sure everything is working as expected. If you discover any issues you can revert to the previous implementation by simply removing that environment variable. The nice thing about this pattern is that you aren't locking yourself out of anything and at each step there's an 'escape hatch' you can take in case something goes wrong.

5. Clean up

5. Clean up
5. Clean up

The final step is to clean up. Since the old implementation is not being used anymore, you can completely remove it from the monolith. Don't forget to remove any feature flags as well. You could also remove the abstraction, but I think leaving it in place doesn't hurt anything.

Conclusion

The branch by abstraction pattern can be extremely valuable when you're trying to extract functionality that's deep inside the monolith and you can't intercept the calls to it at the edge of the monolith. The pattern allows for incremental changes that are reversible in case anything goes wrong.