Skip to content
Apsan Works

Expanding to a Second Market Without Forking the Codebase

The second market surfaces every assumption baked into the first build. Factor market logic into configuration, and expansion becomes an adapter.

5 min read

When a product works for one market, adding a second one surfaces every assumption hardcoded into the first build. A pricing page with a hardcoded currency symbol. A data ingestion layer written for one API that silently assumes one set of fields, one coordinate system, one authority structure. An address form that validates against rules specific to one country.

The instinct at that point is to branch the codebase. Keep the first market working, start a second version for the new one. That instinct is expensive. Two codebases means two test suites to maintain, two deployment pipelines, two versions of every feature to build. Divergence accelerates. What should be one product becomes two.

The alternative is to factor market-specific logic into configuration before you need it. What changes between markets goes into a config or an adapter. What stays the same stays in the core. A second market becomes a new config file and a new adapter.

What varies between markets

For most products, a market is a combination of data source or API (different authorities, different providers, different schemas), locale and language, currency and tax rules, coordinate system or geographic convention, and legal entity structure.

Some of these vary together. A UK expansion might mean a different data source, British English, GBP, and one set of compliance requirements. Some vary independently. A second country might share a payment provider with the first but use a different data source and a different coordinate projection.

The important observation is that these are all enumerable. They are a finite set of overrideable values, and most products touch only a handful of them.

The configuration approach

The core product logic (the rules identical across markets) lives in code. Market-specific overrides live in a configuration layer the core reads.

A simple version is one config object per market: locale, currency, tax identifier format, legal entity name. The application reads from this at runtime and never hardcodes a value that belongs there.

A more involved version adds an adapter layer for the parts that cannot be captured in a plain value: ingesting from different external data sources, address validation that differs by jurisdiction, ID format rules specific to one country. Each adapter implements the same interface. The core calls the interface. Which adapter runs is determined by which market is active.

What this looks like in practice

The planning application monitor we built for Next Door Notice is built this way. The core product handles watchlists, proximity queries, alert delivery, and the map interface. Market-specific logic lives in per-market configuration: which planning authorities to ingest, what coordinate system they publish in, what the normalised application schema looks like for that jurisdiction.

When an Ireland expansion came up, it needed a new ingestion adapter for a different set of authorities, a coordinate conversion layer for the Irish national grid projection, and a new config entry for the legal entity. The core product needed none of it. The expansion was an adapter and a config file, not a branch.

The same pattern appears in any product where the data source varies by market, which is a wide category: regulatory monitoring, property data, public procurement databases, financial filings, planning systems. The business logic stays stable. The adapters absorb the variation.

The adapter layer for data sources

Data ingestion adapters are the most common place this shows up, because external APIs and public data sources vary more than almost anything else between markets.

Each adapter implements the same interface: fetch records from the source, normalise them to the internal schema, return a list of canonical items. The ingestion core calls the interface. It never knows whether the source is a REST API, a scraped HTML page, or a file download. It never knows what coordinate system the source uses. All of that lives in the adapter.

Writing a new adapter means: read the source’s documentation, map its fields to the internal schema, handle whatever encoding or projection quirks it has, write a test against a representative sample. The core does not change.

The test for whether this is working

Can someone who did not build the core add a new market by writing a new adapter and a new config entry, without touching anything in core?

If yes, the abstraction is in the right place. If no, there is market-specific logic leaking into core that needs to be extracted.

A related check: if you removed the market configuration layer and replaced it with a single hardcoded market, would any core logic need to change? If so, the boundary is not clean yet.

When it is worth building this way

If a concrete second market is on the roadmap before the first build ships, factor the market layer in from the start. The overhead is moderate and the payoff is immediate when expansion comes up.

If the second market is speculative (“we might expand someday”), build for one market and accept that expansion will involve a refactor. That refactor is smaller and more contained than a fork, and it will not need to happen until it actually needs to happen.

The decision connects to the same thinking behind multi-tenant architecture: the tenant identifier in every table is cheap to add at row one and expensive to retrofit. The market adapter layer is cheap to design in from the start and moderately expensive to extract later. Both are decisions that look like premature architecture until the moment you need them.

This is how we built Next Door Notice. See the case study, or read about our approach to custom applications.

Tell us what is slowing you down

A short conversation is usually enough to tell whether this is a build, an automation, or something you should not do at all. We will tell you which.