Skip to main content

Software Engineering · 8 min. readNew

How We Engineer Software: Specification First, Decisions Second, Code Third

We write down what the software must do, decide the architecture and record why, and only then write the code. How each step works, why the order matters, and where AI coding assistants fit.

Donatas Tranauskis Founder & Systems Architect ·
Software EngineeringSpecificationArchitecture Decision Records Summarise with ChatGPT
Overview of DBETA’s digital engineering and architecture services.

The short answer

We write down what the software must do, decide the architecture and record why, and only then write the code. AI coding assistants speed up every one of those steps. A developer checks every change, and it is tested before release.

The order matters more than the tools. AI tools can now produce working code in minutes, which makes it tempting to start there. But code written before anyone has agreed what it should do tends to be rewritten, and code written without recorded decisions tends to drift until nobody is sure why it works the way it does.

This article walks through how we build software, step by step: the specification, a working prototype, architecture decisions, the structure of the code, and the tests and checks every change has to pass. Our clearest example is Bones 9, the next version of the web platform we make. It is being rebuilt as an application on Laravel, is in development and is not generally available. We work the same way on our own sites and on Bones.

Why the order matters

A change is cheapest where it is easiest to make. Changing a sentence in a specification takes minutes and affects nothing else. Changing the same rule once it has been built means changing code, tests and possibly stored data, and checking that nothing else depended on the old behaviour. Changing it after release adds users who already rely on how it worked.

So each step in our process is there to catch a different kind of mistake as early as possible:

  • The specification catches misunderstandings about what the software should do.
  • The prototype catches rules and workflows that sounded right on paper but don’t work in use.
  • Architecture decisions catch structural choices that would be expensive to reverse.
  • Tests and checks catch changes that break something that used to work.

None of this is new. It is ordinary software engineering. What has changed is that AI tools make skipping these steps very easy, because the code appears so quickly. We use the same tools, but inside this order.

Step 1: the specification

Before any screen is built, we write down what the software must do. The specification is organised by area, so each part of the product has its own section. For Bones 9 that means areas such as publishing, access, presentation and distribution, each with its own rules.

A good specification is not a wish list. For each area it states:

  • The rules: what the software must do, what it must never do, and what happens in the awkward cases.
  • Acceptance criteria: how we will know a feature is finished, written so that someone can check it.
  • What is out of scope: so that nobody builds what was never asked for.

To give an illustrative example, not one taken from a real specification: a rule might say that only people with publishing rights can make a page live, and that anyone else can submit it for review. The acceptance criterion would then say that an editor without that right sees “Submit for review” where others see “Publish”. Both are short, and both can be checked by someone who did not write them.

Writing it down forces the hard questions early: who is allowed to do this, what happens if two things conflict, what the software should do when something is missing. It also gives AI tools and reviewers the same reference. An assistant builds what it is asked for, so the asking has to be precise, and a reviewer needs something to check the result against.

Step 2: a working prototype

Next we build a clickable prototype with made-up data. It looks and behaves like the product, so people can use it rather than imagine it, and each change to it is checked in the browser. Its job is to test the rules and workflows from the specification before anything is built for real.

This is where many problems appear: edge cases, missing fields and confusing rules come up while the prototype is being used, not after launch. When the prototype shows that a rule doesn’t work, we change the specification, which is still cheap.

A prototype is not the product. It has no real data behind it and is not production code. The real build is written new, not copied from the prototype. Our engineers use the prototype as the reference for the rules, workflows and styles. This is the idea behind our AI-assisted prototyping.

Step 3: architecture decisions, written down

Some choices shape everything built afterwards: how the code is divided, what depends on what, which tools the product relies on. We record each of these in an Architecture Decision Record, or ADR.

An ADR is a short document with a simple structure. The format was described by Michael Nygard in Documenting Architecture Decisions (2011), and most teams use a version of it:

  • Context: the situation and the forces involved, including where they pull against each other.
  • Decision: what we chose to do.
  • Consequences: what follows from that choice, good and bad.

The value shows up later. Months on, when someone proposes a change, the reason for the original choice is still on record. A change that contradicts an earlier decision becomes visible, so it is either rejected or the decision is revisited openly, with a new record. Without this, decisions live in people’s memories and get reversed by accident, often by an AI assistant that had no way of knowing.

How to write a useful ADR, with a template, is a topic of its own.

Step 4: an architecture that keeps the rules separate

One of the decisions recorded for Bones 9 is its overall shape: a modular monolith on Laravel. It is one application, deployed as one piece, but divided inside into modules with clear boundaries. At the centre is a core that holds the product’s rules and does not depend on Laravel. Everything that talks to the outside world plugs into that core through defined contracts.

This pattern is known as ports and adapters, or hexagonal architecture, a name from Alistair Cockburn’s description of it. Dependencies point inward: the outer parts know about the core, but the core knows nothing about them. Automated architecture tests check that rule on every code change, so the boundary cannot be broken quietly, whether by a person or by an AI assistant. That is the point of the pattern: the business rules can be tested on their own, and the parts around them can change without the rules changing.

Why a modular monolith rather than microservices, and how ports and adapters work in Laravel in practice, are each worth a separate article.

Step 5: tests and gates

Every change has to pass automated checks before it can be merged. Each kind of check looks for a different kind of problem:

  • Unit and integration tests, per area: does each part do what the specification says, alone and together with the parts around it?
  • Architecture tests: does the code still respect the boundaries from Step 4?
  • Static analysis: are there errors that can be found without running the code?
  • Code-style checks: does it follow the same conventions as the rest of the code, so the next person can read it?
  • Browser end-to-end tests: does it work for a person clicking through the real screens?
  • Mutation testing: would the tests actually notice if the code were wrong? It makes small deliberate changes to the code and checks that a test fails each time.

These run in continuous integration (CI) on every code change, and a change that fails them does not go in. Running all of that on every change can be slow, so we put work into keeping the pipeline fast without weakening it. We wrote about how in speeding up CI on GitHub Actions.

A wider testing strategy, and mutation testing in particular, are topics of their own.

Where AI fits

AI coding assistants are part of every step. They help build prototype screens, draft code and tests, and review changes, which makes each step faster.

What they do not do is own the work. People own the specification, the architecture decisions and the final check. The structure above is what makes AI tools useful rather than risky: the specification tells the assistant what to build, the recorded decisions and architecture tests stop it from undoing earlier choices, and the tests say plainly whether its code works. A developer checks every change.

That is the difference between AI-assisted engineering and vibe coding, where code is accepted because it seems to work. We compare the two in AI-assisted engineering vs vibe coding. The same principles sit behind an engineering core for websites and building a website like a software system.

What this means if you hire us

If we build software for you, we work the same way. The work is agreed before it starts, and a working prototype can come first, so you can change your mind while it is still cheap. Every change is reviewed by a developer and tested before release.

This is how we work in AI-assisted software engineering, on new code and on code you already have. If your team wants to work this way, our workshops cover it.

The takeaway

Good software starts before the code: with a written specification, a prototype people have actually used, and decisions recorded with their reasons. AI tools make every step faster, but they don’t replace the order of the steps or the person who checks the result. If you are planning a platform, a CRM or a portal, or you have code that has become hard to change, talk to us.

In short

DBETA builds software in a fixed order: a written specification, organised by area, with rules and acceptance criteria; a clickable prototype with made-up data to test those rules and workflows; architecture decisions recorded as Architecture Decision Records with their context and consequences; code structured so that the business rules sit in a core that does not depend on outside tools, with automated architecture tests keeping that boundary; and automated checks in continuous integration before every merge, including unit, integration, architecture, browser end-to-end and mutation tests, static analysis and code-style checks. AI coding assistants help at every step, and a developer checks every change. The worked example is Bones 9, the next version of DBETA's web platform, being rebuilt as an application on Laravel; it is in development and not generally available.

Questions about this article

Changing a rule in a written specification is much cheaper than changing it in code, and cheaper again than changing it after release. A specification also gives AI tools and reviewers the same reference to build and check against.

A short document that records one architecture decision: the context, the decision and its consequences. It keeps the reason for a choice on record, so a later change that contradicts it is visible.

Yes. AI coding assistants help draft code, tests and reviews. People own the specification, the architecture decisions and the final check. A developer checks every change, and it is tested before release.