Back to Blog

Contract-Driven Development and Specmatic — Why Contract-Driven, Why I Chose Specmatic Over Pact, and What I Learned by Trying It

Knowledge

In the previous post, I talked about the background to launching NASEBANAL API Specs, and what I see beyond it: my expectation for the OpenAPI spec as a shared guardrail across in-house, outsourced, and agent development. I also promised to "cover it in detail in the next post" — the Contract-Driven Development (CDD) work I've put at the center of that evaluation, and in particular my experiments with Specmatic. This post is that follow-up.

Before I get into it, I'd like to touch on why I'm investing my time here — the motivation behind it, and a somewhat larger vision.


What I Want to Achieve with NASEBANAL Evolution

As I've shared in previous posts, my most recent work has been NASEBANAL Growth, a set of apps that support personal growth, which I've built and released. It grew out of my own past experience and what I want right now; there are still features I'd like to expand, and I'm genuinely attached to the concept. But separately from that, there's a piece of development I want to take on with a bigger business in view. That is NASEBANAL Evolution, and what I'm planning with that solution is reshaping the software development process for the AI era.

In system development — and in Japan especially — the dominant mindset has long been "leave it to the specialists": entrust software development to firms that do software for a living, and keep your own resources focused on your core business. There's an economic rationality to that, too. But when that external dependence goes too far, a new problem surfaces. Across many industries today, business models and the way revenue is generated are being redefined by software (software-defined). Even manufacturing — where Japan has competed through the craftsmanship of its hardware — is shifting its center of gravity toward subscription-style, usage-based service businesses billed monthly or annually. This has let users hold down their initial investment and commit to a full-scale investment only once they've felt the value of adoption. And alongside that servicization, software increasingly layers further value on top of hardware performance. In this way, the source of an industry's added value is drawn toward a new layer — from hardware to software. When that happens, a company that has outsourced its software wholesale risks letting go of the very source of its competitiveness without realizing it — and that is the problem awareness that "going too far" brings.

So while the need to engage with software is increasingly recognized at many companies, plenty of them still can't quite commit to "in-house development" — taking on the development and maintenance with internal resources. This isn't a black-and-white question but a matter of balance — how much should be handled in-house — and precisely because the outcome of that decision is hard to foresee, companies hesitate to commit. That, I feel, is the reality.

Through my time working at Pivotal (later acquired by VMware), a company dedicated to supporting system modernization, I've felt the difference in temperature between the US and Japan around exactly this debate. In the US, there are many published cases of operating companies driving their own modernization of system development, whereas in Japan at the time, the published cases I saw centered on a handful of web-service providers or SIers, and my impression was that the move toward operating companies themselves leading genuine, full-scale software modernization was still yet to come.

So here is my hypothesis. The significance of in-house development is increasingly felt at many companies, yet they haven't been able to draw a realistic roadmap for where to land in order to optimize return on investment — and that, I think, is the big challenge. That debate hasn't been an easy strategic call to make, partly because the expected impact is hard to quantify; but by combining the recent arrival of AI with the mechanism of Contract-Driven Development that I'll describe below, I believe we can take a step closer to making it real.


What Contract-Driven Development Is, and Where It Meets NASEBANAL Evolution

Put simply, CDD is a mechanism that defines the integration points between pieces of software as a Contract, and verifies whether the called side (the Provider) can deliver the output expected by the calling side (the Consumer). You could also see it as one area of Spec-Driven Development, which is drawing attention in today's AI coding as well.

Trace the idea back to its origins and you arrive at Consumer-Driven Contracts, a pattern proposed by Ian Robinson on Martin Fowler's site in 2006. When services integrate, rather than the provider unilaterally deciding the spec, the consumer's expectations are made explicit as a "contract," and by satisfying it you can evolve the service safely — that's the idea. Later, in the 2010s as microservices spread widely, the technique of automatically verifying these contracts as tests (contract testing) drew attention, and Pact emerged as its representative OSS (around 2013). More recently, a spec-first approach that treats an existing API specification such as OpenAPI as the contract itself has also appeared, and Specmatic — the subject of this post — sits in that lineage.

As the main players offering solutions that implement CDD, SmartBear's Pact and Specmatic come to mind. I'll cover a comparison of the two in the next section, but the main features these tools provide are roughly as follows:

  • Based on the contract file, the calling side (Consumer) is given a mock of the API, so it can run simulated tests of API integration (unit tests) in the Consumer's pipeline
  • Based on the contract file, the API-providing side (Provider) can run simulated tests (unit tests) using calls that follow the contract

In system development, quality is generally assured through a flow of unit testing, integration testing, system testing, and acceptance testing. In today's web-service development, however, it's normal to develop the frontend and backend separately, and in that case it's quite hard to put integration tests on a CI pipeline. The common practice is to merge a Pull Request into the main branch only once it passes the CI pipeline, but an integration test only succeeds once both Consumer and Provider are present in the correct state — so while each is still being developed on its own, there's simply no way to make it pass.

This is where CDD tooling comes into its own. With CDD, mocks and simulated drivers are provided, so what you'd normally confirm in an integration test can be broken down into simulated unit tests and folded into the CI pipeline. In other words, you get to turn a faster feedback loop.

That's the basic concept of CDD. So how does it relate to the NASEBANAL Evolution I described earlier?

When thinking about in-house development, what matters, I believe, is discerning the right place to land — which parts of the software to cover yourself, and which to hand to external resources. What I'd propose is this division: build the frontend that users touch directly in-house, and outsource the backend (API) it calls as needed.

  • The frontend is where users operate directly, so it's a domain where turning the feedback loop quickly — to respond to their requests — pays off
  • The frontend doesn't directly hold the data persistence (the database), so its implementation architecture tends to stay relatively simple and approachable (and is amenable to AI coding)
  • It's realistically hard to lock down the frontend's requirements completely before development; more often than not, after the first version ships, it's expected to undergo continuous improvement in response to user feedback — whereas the backend it calls lends itself more readily to Spec-Driven Development

Valuing what's close to the user and handling it with your own hands — this perspective has something in common with the approach of manufacturing in general, doesn't it? And it's precisely in conveying the spec to an outsourced backend that I think CDD can be put to good use.

Beyond the in-house-development context, I also think it can be applied to coordinating specs and assuring quality between vendors in multi-vendor development.

The significance of this division of labor becomes clearer when contrasted with the typical architecture of the past.

Legacy-style enterprise architecture: each system is a self-contained silo of Frontend, Backend, and DB, with systems integrating mostly through nightly batch jobs that pass data DB-to-DB

The figure above shows a common legacy-style enterprise system layout. Each system is built as a vertically self-contained stack of Frontend, Backend, and DB, standing side by side as independent "silos." In this shape, functionality is fragmented along system boundaries and hard to leverage as a whole, and integration between systems tends to rely on rigid interfaces — passing data directly between databases, like the nightly batch jobs connecting the DBs in the figure.

On top of that, such systems are often built wholesale by a single vendor, turning their internals into a "black box" the ordering side can't see into. When that's the case, even fixing a single external integration means going back to the vendor and commissioning the change under a separate, one-off contract each time — which adds lead time and cost. This accumulation of "case-by-case contracts" becomes a major drag on nimbleness in the face of change — on flexibility, or agility.

What I want to aim for, by contrast, is the To-Be in the next figure.

To-Be architecture: the ordering side keeps a grip on the in-house scope framed in red (the frontends plus the shared OpenAPI Spec), using that Spec as the contract that drives quality assurance, with the backends/DBs hanging beneath the Spec

Here, frontend and backend are separated as distinct systems — the vertically self-contained silos are sliced into a group of frontends and a group of backends/DBs — and a single shared OpenAPI Spec is run along the boundary between them. That Spec becomes the contract both the ordering and contracting sides refer to, and the ordering side can keep a grip on the overall architecture's spec through it. Because integration happens via APIs rather than direct database hand-offs, functionality can be combined far more flexibly, and with that Spec as the axis, quality assurance can be applied — this is the backbone of the integration that CDD underpins.

The quality assurance drawn on the right of the figure isn't confined to a single tool. With NASEBANAL Evolution, I'm envisioning an API quality-assurance capability that combines multiple open-source tools, going beyond the contract testing with Specmatic covered in this post. Concretely, on top of contract testing with Specmatic, I want to bundle verification at each layer — fuzz testing driven from the OpenAPI spec with Schemathesis, End-to-End testing with Playwright, and load testing with Locust. All of these are widely used open-source tools, and by combining them with the OpenAPI spec as the starting point, I aim for a mechanism that runs a multi-faceted quality assurance coherently.


Why Specmatic — A Comparison with Pact

The first CDD solution that comes to mind is probably Pact. I actually tried it myself.

To summarize how Pact works briefly: taking the API provider as the Provider and the consumer as the Consumer, the contract information (the Pact file) is generated from the Consumer's implementation and stored in a repository, and the Provider side runs tests against that contract to determine whether it satisfies the Consumer's expectations.

The Pact development flow: the consumer's tests generate the contract (pact file), which is published to the Pact Repository; the Provider then verifies against the real backend — and an API Spec has to be maintained separately on the side

In practice, the flow goes like this: the pact file generated by running the Consumer's (frontend's) tests is published to a Pact Repository (Broker), and the Provider (backend) pulls it and verifies by replaying the recorded requests against the real backend. What gave me pause here is that, separately from the contract (the pact), you also tend to want an OpenAPI spec for documentation and type generation. As step 4 in the figure shows, the two aren't linked automatically, so you end up keeping them in sync by hand — in effect, maintaining the contract twice.

When I tried to apply this to my own division-of-labor approach to development, the key point was that Pact is a Consumer-Driven (consumer-originated) mechanism. In Pact, the contract (the Pact file) is generated from the Consumer's test runs, and is established independently on a per-pair basis between a Consumer and a Provider. Put the other way around, the contract is no more than a snapshot of "the range the Consumer actually called," and it is not the kind of thing you can hand to an outside vendor as an independent spec before the Provider is even built.

In other words, where Pact truly shines is in verifying that a Provider exposing an API properly satisfies the demands of many Consumers, whereas the approach under discussion here is about a Consumer clearing, one by one, the behavior of the multiple Providers it calls — and I felt that's where each solution's strengths and weaknesses show.

Consumer-Driven: one Provider satisfies all its Consumers

As the figure above shows, in a structure where many Consumers depend on a single Provider — when you want to verify that "this Provider satisfies all of its Consumers' demands" — Pact demonstrates its strength.

What I wanted at NASEBANAL this time, on the other hand, was the reverse. Keep the Consumers — the ordering side, that's us — few and build them in-house, and outsource the multiple Providers they call. And I want to guarantee, one by one, that "from a given Consumer's point of view, each Provider behaves as specified" — the structure in the figure below.

Spec-First: one Consumer verifies all its Providers

What fundamentally separates the two is the origin of the contract. Whereas Pact's contract is generated from the Consumer's implementation (consumer-derived), what I wanted was a spec-first contract — an independent spec written ahead of the implementation that you can hand to an outside vendor.

This is the perspective from which I became interested in Specmatic. Specmatic is a contract-definition mechanism that treats an OpenAPI spec directly as the contract, a spec agreed upon per API that a Provider offers. Because you can fix the contract ahead of implementation (spec-first), you can hand the spec to an outside vendor even before any Provider exists. When outsourcing, you just give the OpenAPI spec to the contractor and make that the contract. The frontend can proceed using the mocks (stubs) Specmatic generates, and the contractor can assure quality with the test calls (Example information) Specmatic provides. What's more, now that AI coding has advanced, you can also expect to auto-generate part of the backend through Spec-Driven Development that takes the OpenAPI spec as input — which brings into view a best-mix between development using offshore resources and in-house development leveraging AI.

Compared with Pact, Specmatic's advantage starts with the simplicity of this setup. There's no Pact Repository (Broker) to set up and operate as a separate place to store the contract — you can use the OpenAPI spec you already have directly as the contract. You avoid the redundant information management of "maintaining an API Spec and a contract in parallel" that we saw in the Pact figure earlier; instead, a single OpenAPI spec serves as the single source of truth, which both Consumer and Provider reference and verify against. That consolidation is a quiet but real operational difference.

This "ease of standing up stubs" also pays off when building a Consumer's CI/CD. A Pact mock only responds within the range of the interactions you hand-wrote. So when you try to run an app against multiple Providers in a single Consumer's CI, you end up having to prepare separate stubs with the likes of Nock or WireMock for any API beyond the calls you described as contracts (there's also `pact-stub-server`, which replays the Pact file as a stub, but even that doesn't go beyond the "recorded interactions"). Specmatic, by contrast, can auto-generate a whole stub from each Provider's OpenAPI spec, so you can spin up spec-compliant fakes for every Provider you call with little effort. The fact that it makes the Consumer-side pipeline easy to build was one of the reasons Specmatic appealed to me this time.

Below is the actual code that boots the Specmatic mock, called before running NASEBANAL Recorder's (the frontend's) unit-test scenarios. As you can see, simply handing it the OpenAPI spec file maps a Docker container process onto the host name (here, localhost) and port number you specify.

export async function setup(): Promise<void> {
  const spec = specPath();
  const specDir = resolve(spec, '..');
  const specFile = spec.slice(specDir.length + 1);

  removeContainer(); // drop a leftover from an interrupted run

  execFileSync(
    'docker',
    [
      'run', '--rm', '-d',
      '--name', CONTAINER,
      '-p', `${HOST_PORT}:9000`,
      '-v', `${specDir}:/spec:ro`,
      SPECMATIC_IMAGE,
      'stub', `/spec/${specFile}`, '--port', '9000',
    ],
    { stdio: 'inherit' },
  );

  // The stub serves bare path templates (no /api/v1 base path), and /health is
  // public (security: [] in the spec), so it answers 200 without a token.
  await waitForStub(`http://localhost:${HOST_PORT}/health`);
}

As noted earlier, when you develop the frontend and backend separately, it's hard to put End-to-End integration tests on a CI/CD pipeline; the result is that integration testing happens only after both implementations are in place, spec misunderstandings come to light, and rework kicks in — which tends to stretch out the lead time to release. With CDD via Specmatic, you can confirm spec compliance at the unit level using the OpenAPI spec as the contract, so you can crush many of those misunderstandings well before the integration test. End-to-End integration testing is still ultimately necessary, but by the time you reach it the residual bugs are fewer, and the accuracy of detection can be expected to improve as well.


What I Tried and Learned

Against this backdrop, I actually ran CDD — Specmatic in particular. The pipeline below is what I assembled by running NASEBANAL's OpenAPI spec, backend, and frontend through a full loop, so that a contract update propagates across every layer.

NASEBANAL's CDD release/bump pipeline (propagating from the API Spec to Backend and Frontend)

Concretely, it goes around in the following steps:

  1. Split the OpenAPI spec into a separate project and manage it as an NPM package. Along with that, publish a reference page based on that OpenAPI spec as the contract. Change the spec and merge, and a new version gets published.
  2. When the new spec is merged, Mend Renovate automatically opens a Bump PR on the Provider (backend) side.
  3. On the backend, add the implementation that satisfies the new spec, and merge.
  4. Triggered by that merge, repository_dispatch sends an update signal to the spec repository.
  5. On the spec repository side, update the published status (API Doc) for the new version.
  6. Next, Renovate automatically opens a Bump PR on the Frontend (consumer) side.
  7. On the frontend, do the follow-up implementation and merge. With this, the spec, backend, and frontend are all happily lined up on the new version.

Going around this loop, I came away with a few lessons.

  • Manage the spec file in its own independent repository, and make bot-driven propagation (à la Renovate) the default. Making the contract independent was the starting point for everything in CDD.
  • Clear version management of the spec is a prerequisite. For breaking changes in particular, strictly stick to a policy of bumping the version like /v2 and running it in parallel as a separate implementation. The discipline of not slipping a backward-incompatible change in "quietly" is what underpins the pipeline's reliability.
  • Drift happens even in AI-authored specs. I've been auto-developing the frontend and backend simultaneously with AI, and having AI write the spec too. Even so, there was an error in the spec's response definition (specifically, a drift where a field could be null but that wasn't reflected in the spec), and Specmatic's contract test caught it. It was the moment I felt that even when a human can't track every detail, the contract works as a guardrail.
  • The Consumer isn't only the frontend. This time I also released NASEBANAL CLI and NASEBANAL SDK — the official clients for operating the NASEBANAL API, corresponding to Google's gcloud (CLI) and the per-language Google Cloud client SDKs. By positioning them likewise as Consumers, I was able to build an environment where, through CDD, I can verify that each client stays compatible with the API (its OpenAPI spec). Even when I ship a new SDK or CLI, I can cleanly confirm its conformance against the contract.

What I Want to Take On Next

Looking again from the Consumer angle, as I touched on above, besides the frontend there are also the SDK and the CLI. As a next step, I'd like to start on mobile app development as well.

A single API spec (the contract) shared by multiple Consumers — Web App, Mobile App, SDK, and CLI

This is where the contract-centered structure of CDD pays off. As the figure above shows, a single API (the Provider) can be served by multiple Consumers — a web app, a mobile app, the NASEBANAL SDK, the NASEBANAL CLI, and more. Rather than each one verifying the API's behavior on its own, they all share the same OpenAPI spec as the contract — which is exactly why, however many Consumers you add, the guardrail that guarantees conformance stays a single one. You don't have to redo the spec alignment every time you add a new client; you just follow the contract. That, I think, will be a major lever as we diversify the apps.

Going forward, I want to put this OpenAPI spec at the core and diversify the apps, weaving in not just offshore but a best-mix that includes AI agents — the optimal division of labor between humans and AI, and the cost optimization of it, is surely going to be the discussion ahead.

This drifts a little from the message of this post, but in NASEBANAL Growth I discussed tokenization (the handling of identifying information). If you use a mobile app or CLI, publish it as open source, and manage IDs and names in a local file, it becomes harder for the server side to grasp what information is at hand. This also opens up the possibility of a design where information is managed on the client side.

Building on CDD, I want to keep refining the Consumer, and beyond that I'm planning, as NASEBANAL Evolution, a spec- and quality-management platform that bundles the spec conveyance and quality assurance I've described here into a single mechanism. I hope to report back again with the next update.