Expedia Group Technology — Engineering

Unveiling the Essence of Code-Level Extensibility in System Design

Keshavpeswani
Expedia Group Technology
4 min readApr 23, 2024

--

It is the intricacies of the codebase that truly determine a system’s adaptability, extensibility and sustainability

Four people hike alongside water
Photo by Tino Rischawy on Unsplash

In the ever-evolving landscape of software engineering, where high-level designs serve as the blueprints for digital architectures, an often underestimated factor emerges as the linchpin of long-term success: code-level extensibility. Beyond the grandeur of conceptual frameworks, it is the intricacies of the codebase that truly determine a system’s adaptability and sustainability.

The significance of code-level extensibility

Imagine an intricately designed system, a testament to a well-crafted high-level design. It stands tall and seemingly impervious, a marvel of architectural vision. However, the real test arises when the need for extensions or modifications surfaces to accommodate new and unforeseen use cases. This is where the concept of code-level extensibility comes into play.

Beyond the blueprint: crafting a foundation for the future

While a high-level design provides the overarching structure, the soul of any system resides in the code that brings it to life. Code-level extensibility goes beyond theoretical constructs; it involves writing code that is modular, adaptable, and easy to maintain. It is about creating a foundation where future modifications seamlessly integrate, fostering an environment of fluid evolution.

Insights from industry experience

Based on my extensive industry experience, I’ve witnessed first-hand the considerable challenges that can arise from a lack of code-level extensibility in a system’s evolution. Regardless of the brilliance of high-level design, the adaptability to new use cases becomes a daunting task if the underlying codebase lacks extensibility. Real-world systems demand a harmonious blend of visionary design and code that can flexibly grow and adapt as required.

Let’s delve into an illustrative example: Consider the initial design diagram where Service A connects to MongoDB to insert documents.

A diagram of Service A connecting  to MongoDB

In the first iteration, we might code Service A to interact with a specific “MongoService” for MongoDB operations.

package extensibility;

import extensibility.dao.MongoService;
import org.bson.Document;

import java.util.Arrays;

public class Service {
private final MongoService mongoService;

public Service(MongoService mongoService) {
this.mongoService = mongoService;
}

public void doSomething() {
mongoService.insert("collectionName", Document.parse("{ \"key\": \"value\" }"));
mongoService.insert("collectionName", Arrays.asList(Document.parse("{ \"key2\": \"value2\" }")));
}
}

The MongoService would look like this:

package extensibility.dao;

import com.mongodb.client.MongoClient;
import com.mongodb.client.model.InsertOneModel;
import com.mongodb.client.model.WriteModel;
import org.bson.Document;

import java.util.List;
import java.util.stream.Collectors;

public class MongoService {
private MongoClient mongoClient;

public void createCollection(String collectionName) {
mongoClient.getDatabase("db").createCollection(collectionName);
}

public void insert(String collectionName, Document record) {
mongoClient.getDatabase("db").getCollection(collectionName).insertOne(record);
}

public void insert(String collectionName, List<Document> records) {
List<WriteModel<Document>> writeModels = records.stream().map(InsertOneModel::new).collect(Collectors.toList());

mongoClient.getDatabase("db").getCollection(collectionName).bulkWrite(writeModels);
}
}

This might seem expedient for a faster time to market, but it overlooks the long-term extensibility of the system.

As the system evolves, we might realize that MongoDB is not the optimal choice for our needs, and we wish to replace it with another database, say Cassandra. However, this replacement entails significant code changes in both the Service class and the addition of a Cassandra Service. Unfortunately, the initial code violates SOLID principles, leading to increased complexity and maintainability issues.

Contrast this with a more forward-thinking approach, where the initial code is designed with a generic “DbService” interface, implemented by specific database services such as MongoService. This approach provides a foundation for seamless adaptation to new databases without extensive modifications to the Service class. Adding a new Cassandra class becomes a straightforward task, adhering to SOLID principles and promoting long-term code cleanliness and extensibility.

package extensibility;

import extensibility.dao.DbService;
import extensibility.dao.MongoService;

import java.util.Arrays;
import java.util.Map;

public class Service {

private final DbService dbService;

public Service() {
this.dbService = new MongoService();
}

public void doSomething() {
dbService.insert("collectionName", Map.of("key1", "value2"));
dbService.insert("collectionName", Arrays.asList(Map.of("key2", "value2")));
}
}
package extensibility.dao; import java.util.List; import java.util.Map;

public interface DbService {
void createTable(String name);

void insert(String tableName, Map<String, String> record);

void insert(String tableName, List<Map<String, String>> records);
}
package extensibility.dao;

import com.mongodb.client.MongoClient;
import com.mongodb.client.model.InsertOneModel;
import com.mongodb.client.model.WriteModel;
import org.bson.Document;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MongoService implements DbService {
private MongoClient mongoClient;

@Override
public void createTable(String name) {
mongoClient.getDatabase("db").createCollection(name);
}

@Override
public void insert(String tableName, Map<String, String> record) {

mongoClient.getDatabase("db").getCollection(tableName).insertOne(getDocument(record));
}

@Override
public void insert(String tableName, List<Map<String, String>> records) {
mongoClient.getDatabase("db").getCollection(tableName).insertMany(records.stream().map(this::getDocument).collect(Collectors.toList()));
}

private Document getDocument(Map<String, String> record) {
return new Document(record);
}
}

While the latter approach may require some additional upfront work, it significantly reduces the effort and complexity involved in adapting to changing requirements. This becomes especially critical when these services are referenced from a library, minimizing the challenges associated with refactoring and saving valuable time and resources. In essence, investing in code-level extensibility from the outset pays dividends in terms of maintainability, adaptability, and overall system robustness over time.

A key takeaway: necessity, not just best practice

Code-level extensibility is not a mere best practice; it’s a fundamental necessity. It marks the distinction between a system that gracefully embraces change and one that grapples with the complexities of evolution. Investing in code that can withstand the test of time becomes paramount for the longevity and success of any digital architecture.

Engaging in discussion

How do you ensure code-level extensibility in your projects? Share your insights in the comments below. Together, let’s elevate our understanding of system design and code-level extensibility.

#SystemDesign #CodeExtensibility #MarketingTechnology #SoftwareEngineering #TechInsights #IndustryExperience #AdaptabilityInCode

--

--

Keshavpeswani
Expedia Group Technology

Principal Software Engineer | Mentor and Trainer in System Design and Architecture | OSS Contributor | Expert: Distributed Systems & Neural Networks