Tuesday, July 14, 2026

Why Most "REST" APIs Are Lying to You (And Why HATEOAS is the Answer)

Let’s address the elephant in the room right away: Most of the APIs we call "RESTful" are actually fakes.

If you are building or consuming APIs that look like /api/users/123 or /api/orders/submit, you aren't really doing REST. You are doing RPC-over-HTTP (or what we affectionately call "REST-ish").

Why does this matter? Because you are missing the defining constraint of REST. You are missing the engine that makes the web so resilient: HATEOAS.

Don't worry if that acronym sounds intimidating. Let’s break it down into plain English.

The Website Analogy (A Lightbulb Moment)

Roy T. Fielding—the guy who literally defined REST—insists that a true REST API must act like a website. Think about your last online shopping experience.

Did you memorize the URL for the "Checkout" page? Did you hardcode the address for the "Returns" section in your brain? No. You just read the screen, saw a button that said "Proceed to Checkout," and clicked it. The website told you what you could do next.

That is the heart of HATEOAS (Hypermedia As The Engine Of Application State). The server tells the client what actions are available, rather than the client guessing or relying on an external manual.

What Exactly is "Hypertext/Hypermedia" Anyway?

Here is the beautiful thing: Hypertext doesn't mean "HTML in a browser." Roy Fielding clarified this perfectly:

“Hypertext is the simultaneous presentation of information and controls such that the information becomes the affordance through which the user obtains choices and selects actions.”

In simple terms: The data itself dictates the next steps. Machines can follow these links when they understand the data format.

To prove this isn’t just theoretical, let’s look at how this plays out in the real world, from human browsers to machine-to-machine communication.


Examples: Hypertext in Action

1. HTML (The Human Experience)

We all know this one. When you load a product page, you get the data (price, stock) and a control (the "Add to Cart" button) in the same package.

The Server Response:

<div class="product">
  <h1>Wireless Headphones</h1>
  <p>$199.99</p>
  <form action="/cart/items" method="POST">
    <input type="hidden" name="product_id" value="prod_987">
    <button type="submit">Add to Cart</button>
  </form>
</div>

Why it Works: The browser doesn't know what /cart/items is before it loads the page. It discovers that URL in the payload and presents it to you as a clickable button. The client (the browser) just understands HTML.

2. HAL JSON (Machine-to-Machine)

Now, let’s get nerdy. What if a machine (an automated bot) needs to check an order? Instead of hardcoding a URL like /orders/1024/pay, the server tells the bot what to do.

The Server Response:

{
  "order_id": "ord_1024",
  "status": "awaiting_payment",
  "_links": {
    "self": { "href": "/orders/ord_1024" },
    "payment": { "href": "/orders/ord_1024/pay" },
    "cancel": { "href": "/orders/ord_1024/cancel" }
  }
}

The Usage (The "Ah-Ha!" Moment): Instead of coding POST /orders/ord_1024/pay into the client, the bot looks for the payment link.

// The bot doesn't hardcode the URL!
const paymentUrl = order._links.payment.href;
await fetch(paymentUrl, { method: 'POST' });

The Magic: If the URL changes tomorrow to /payments/checkout?order=1024, the bot still works. No code changes required. Furthermore, if the order is already paid, the server simply removes the payment link, preventing the bot from trying to pay twice.

3. JSON-LD + Hydra (The Intelligent Automaton)

For the truly decoupled systems, we use Semantic Web standards. This tells the client how to act, not just where to go.

The Server Response:

{
  "@id": "/users/johndoe",
  "@type": "Person",
  "name": "John Doe",
  "operation": [
    {
      "@type": "ReplaceResourceOperation",
      "method": "PUT",
      "expects": "http://schema.org/Person",
      "title": "Update profile"
    }
  ]
}

Why it's Powerful: The client doesn't need an OpenAPI spec or a PDF manual. The server tells the client: "To update this, send a PUT request here, and send me a 'Person' schema." The API is literally self-documenting.


Summary: Fielding's Core Principles (Cheat Sheet)

If you want to build truly RESTful APIs, you need to adopt these three rules:

  1. The Affordance Principle: The data tells you what you can do next. If you are an admin, you see an "Edit" link. If you are a guest, that link vanishes. State transitions are driven by the payload.
  2. No Hardcoding: Your client should only hardcode the root URL (e.g., api.example.com). Every other API call is made by following the breadcrumbs (links) in the previous response.
  3. Format-Driven: The client shouldn't know your specific API rules. It only needs to know the media type standard (like application/hal+json).

Final Thoughts

Building true RESTful APIs with HATEOAS takes more initial effort than just spinning up a bunch of endpoints. However, it decouples your client and server indefinitely. It allows your API to evolve without breaking millions of mobile apps or frontend builds.

Stop hardcoding paths. Start delivering controls with your data. That is how the web was designed to work.


Sunday, July 5, 2026

Excerpt of my new upcoming book: Vertical Velocity!

How to Break Silos, Ship Faster, and Reclaim Engineering Joy 


The Resource Utilization Trap (The 100% Busy Fallacy)

In many corporate cultures, "slack time" is treated as an existential sin. Managers obsess over ensuring every developer is 100 utilized. If a backend developer finishes their tasks early, they are immediately assigned another ticket. If a QA engineer has downtime while waiting for a build, they are given manual regression scripts to run.

This philosophy—maximizing resource utilization—makes intuitive sense to someone managing a factory floor. But software engineering is not a factory floor. It is not a manufacturing line of physical, predictable goods; it is a creative network of highly variable cognitive work.

Optimizing an organization for resource utilization systematically destroys flow efficiency—the percentage of time a feature actually spends being worked on, as opposed to waiting in line.

To visualize this, think of a local highway.










  • When highway utilization is 50%, cars can move at the maximum speed limit. If one car changes lanes or taps its brakes, the system absorbs the variation. Flow efficiency is high.

  • When highway utilization reaches 90%, traffic slows to a crawl. The slightest variation—a minor lane merge—creates a cascading shockwave of brake lights that travels miles backward.

  • When highway utilization is 100%, the highway is perfectly "utilized." Every square foot of asphalt is covered by a vehicle. But the speed is 0 mph. You have built a parking lot. No value is being delivered to anyone.

In siloed organizations, software delivery functions exactly like a 100% utilized highway. Because every engineer is fully booked, there is zero buffer capacity. When a frontend developer needs a minor backend change to unblock their task, they cannot get it immediately. The backend developers are 100% booked with their own sprint commitments. The request is placed in a backlog—a queue—where it sits idle, waiting for capacity to free up.

By maximizing resource utilization, we minimize delivery speed.

The Mathematics of Congestion (Kingman’s Formula)

The highway isn't just an analogy. Queueing theory gives us the exact math. Kingman's Formula (specifically Kingman's approximation for average waiting time in a single-server queue) shows why:

E[W](ρ1ρ)(Ca2+Cs22)E[S]

Where:

  • E[W] is the average waiting time (queue delay).

  • ρ is the resource utilization level (0ρ<1).

  • Ca is the coefficient of variation for arrivals (how unpredictably work arrives).

  • Cs is the coefficient of variation for service times (how unpredictable the work is to complete).

  • E[S] is the average service time (how long it takes to actually write the code).

Source: Donald G. Reinertsen, The Principles of Product Development Flow (2009)

Look closely at the first term of this equation: ρ1ρ. This is a non-linear relationship.

If we plot the average waiting time (E[W]) against utilization (ρ), the curve is relatively flat until it crosses the 70% mark. Beyond 80%, the curve bends sharply upward. At 90% utilization, the waiting time explodes. At 99%, it approaches infinity.



In software delivery, variation (Ca and Cs) is naturally high. We rarely build the exact same feature twice, meaning we cannot precisely predict how long a task will take or when unexpected bugs will arrive.

📋 What this means: When utilization exceeds 80%, waiting time grows exponentially. The more unpredictable your work, the worse it gets.

Because our variation is high, Kingman's formula dictates that any organization operating near 90 resource utilization will experience massive, systemic queue delays. If a feature has to traverse four different horizontal teams, and each of those teams is operating at 90% utilization, the feature will spend over 90% of its lifecycle waiting in queues.

Vertical feature teams solve this math problem. By bringing all necessary disciplines into a single pod, we drastically reduce the number of handoffs. When you eliminate the handoffs, you eliminate the queues, shifting your delivery speed back down to the flat, fast end of the Kingman curve.

Wednesday, June 3, 2026

Stop Breaking Your API with Primitive Drift: A Guide to Semantic Type Modeling

If you’ve ever debugged a weird rounding error in a financial transaction or watched a perfectly good API fail because a description field suddenly allowed emojis, you’ve already met the problem this article solves.

It’s called Primitive Drift, and it’s quietly destroying your API reliability.

Let me show you how to fix it using Semantic Type Modeling —and why your E2E tests are probably making things worse.

The Problem: When Strings and Numbers Become Liabilities

Most API designs start simple. You map a business concept directly to a JSON primitive:

description:
  type: string
price:
  type: number

Seems harmless. But over time, this causes three predictable disasters:

  1. Inconsistent rules – One team limits descriptions to 250 characters. Another allows 1000. Good luck explaining that to customers.
  2. Financial fraud, literally – Floating-point numbers can’t represent cents exactly. 0.01 + 0.02 isn’t always 0.03. Attackers exploit this for fractional-cent theft.
  3. Leaky security – Each microservice reinvents XSS or SQL injection validation. Gaps appear. Compliance fails.

The solution isn’t more E2E tests. It’s a single source of truth for what your data actually means.

Semantic Type Modeling: Treat Data Like a Business, Not a Compiler

Instead of seeing string, see ItemDescription. Instead of number, see Money.

You define the rule once:

ItemDescription:
  type: string
  minLength: 10
  maxLength: 500
  pattern: '^[a-zA-Z0-9\s.,!?()"-]+$'

Then everywhere else, you just reference it:

description:
  $ref: './domain-types.yaml#/components/schemas/ItemDescription'

That’s it. One change propagates everywhere. No drift. No debate.

The Architecture: How It Stays Enforced (Without Nagging)

You don’t need a committee to enforce this. You need automation.

  • Central Domain Library – A single YAML file (domain-types.yaml) holding every canonical business type.
  • API specs – Each endpoint references types from that library using $ref.
  • CI/CD linter – Tools like Spectral block any PR that uses a raw primitive where a domain type exists.
  • API Gateway – Rejects malformed payloads at the edge, before they touch your backend.

Result: Developers get instant feedback, not a broken pipeline hours later.

Why E2E Tests Are the Wrong Hammer for This Nail

I see teams fall into the E2E Test-Driven Specification anti-pattern all the time:

“We don’t know what the API expects, so we’ll throw payloads at staging, see what breaks, and write a test to lock in that behavior.”

That’s expensive, slow, and fragile.

Bug-Driven LoopSemantic Modeling Loop
Write code → Deploy → Run slow E2E → Fail → Fix → Re-deployWrite schema → Linter flags error instantly → Fix → Done
Hours to daysSeconds

If a business rule changes (e.g., SKU format lengthens), bug-driven teams rewrite dozens of E2E tests. Semantic teams change one line in domain-types.yaml.

Shift Left: Make Validation an IDE Feature, Not a CI Surprise

Give developers local mock servers (like Prism) and OpenAPI validators. When they send a bad payload during development, they get an immediate 400 Bad Request with a precise error:

body/description must match pattern ^[a-zA-Z0-9\s.,!?()"-]+$

No waiting. No guessing. No staging environment required.

When E2E Tests Are Still Useful (And When They Aren’t)

Educate your team on the testing pyramid:

  • Contract validation → OpenAPI + linter (zero cost)
  • Integration/unit tests → business logic like discount calculation (low cost)
  • E2E tests → user journeys, multi-system flows (high cost)

If your E2E test fails because a field was an integer instead of a string, delete that test. That constraint should have been caught before the code was compiled.

But What About Cross-Field Logic? (“If A then B required”)

Developers often argue: “Sure, single-field validation works, but what about conditional rules? That needs an E2E test.”

That argument is outdated. Modern JSON Schema (OpenAPI 3.x) supports oneOf, allOf, and conditional logic natively.

Example: If payment method is CREDIT_CARD, require card details. If PAYPAL, require a redirect URL.

OrderPayload:
  type: object
  required: [orderId, paymentMethod]
  properties:
    orderId: {type: string}
    paymentMethod: {type: string, enum: [CREDIT_CARD, PAYPAL]}
  allOf:
    - if:
        properties: {paymentMethod: {const: CREDIT_CARD}}
      then:
        required: [cardDetails]
    - if:
        properties: {paymentMethod: {const: PAYPAL}}
      then:
        required: [paypalRedirectUrl]

No E2E test needed. The schema enforces it directly.

How to Roll This Out Without Causing a Rebellion

Phase 1: Audit
Look at your existing E2E suite. Flag every test that checks a simple field constraint (description too long, price negative).

Phase 2: Migrate
Move those constraints into domain-types.yaml. Add Spectral to your CI pipeline.

Phase 3: Delete
Remove those E2E tests. Watch build times drop.

Phase 4: Enforce
Make updating the OpenAPI schema a prerequisite for starting development on any feature that touches data shapes.

The Bottom Line

Semantic Type Modeling isn’t just cleaner — it’s faster, safer, and cheaper.

  • API gateways reject attacks at the perimeter
  • SDKs auto-generate correct validation code
  • Documentation stays accurate without manual updates

Stop reverse-engineering your own API. Start modeling what your business actually means.

Your future self (and your on-call rotation) will thank you.


Want the full OpenAPI examples and Spectral rules? Check out the companion repo [link].

Monday, May 11, 2026

Agile Working Agreement that Works

Let’s be honest: we’ve all been there. The Daily Stand-up that devolves into a 45-minute debugging session. The Sprint Planning that drags on so long you forget what you were planning to build. Or the dreaded "Demo Day Disaster," where the Backend team hands over an API only for the Frontend team to realize the data structures don't match.

It doesn’t have to be this way. High-performing teams don’t just "do Scrum"—they build a Working Agreement.

Think of this as your team’s constitution. It isn’t about corporate bureaucracy; it’s about protecting your "flow state," respecting each other’s time, and ensuring that "Done" actually means "Shippable." Here is a battle-tested Working Agreement designed for modern, cross-functional teams.


1. Meeting Etiquette: Principles for Productive Flow

If you want productive ceremonies, you must start with a foundation for mutual respect.

  • The 2-Minute Rule: If a deep technical debate breaks out during a Stand-up, pause it. If it lasts longer than 120 seconds, move it to the "parking lot" to be discussed by the relevant parties immediately after the meeting.
  • Camera On (Remote Teams)? Cameras are mandatory for Sprint Planning and Retrospectives - these require high emotional intelligence and engagement. For the Daily Stand-up? Optional. We trust you’re focused.
  • Punctuality is Non-Negotiable: If Stand-up starts at 10:00 AM, we start at 10:00 AM. If you are late, you owe the team a "coffee debt" or a quick lightning talk on a topic of your choice at the next Retro.
  • No "Second-Screening": Put the distractions away. If you need to answer a Slack message, 

do it before or after the ceremony. Multitasking is the enemy of a 15-minute meeting.

Ceremony Cheat Sheet:

  • Sprint Planning: 2–4 hours (Once per Sprint)

  • Daily Scrum: 15 mins (Daily)

  • Backlog Refinement: 1-2 hours (1–2x per Sprint)

  • Sprint Review: 1-2 hours (Once per Sprint)

  • Retrospective: 1-1.5 hours (Once per Sprint)

2. Refinement vs. Planning (They Are Not the Same)

Teams often fail because they try to do two different jobs at once.

  • Backlog Refinement (The "Look Ahead"): This is not about committing to work; it is about "DEEP" grooming (Detailed, Estimated, Emergent, and Prioritized). Split this into two 1-hour sessions per sprint. This gives the Product Owner time to find answers to technical blockers before the actual Planning starts.
  • Sprint Planning (The "Commitment"): The team selects items from the already refined backlog.
  • The Golden Rule: If Sprint Planning takes 4+ hours, your Refinement failed. When Refinement is done well, Planning should be a smooth "select and commit" process that takes under 2 hours.

3. The Perfect 2-Week Sprint Calendar

For those who need a visual rhythm, here is your standard cadence:

  • Day 1 (Monday AM): Sprint Planning.

  • Daily: Stand-up (same time, every day).

  • Day 4 or 5: Refinement Session #1 (Mid-sprint check).

  • Day 8 or 9: Refinement Session #2 (Finalizing the "Ready" state).

  • Last Day (Friday PM): Sprint Review (Demo) → Immediately followed by Retrospective.

Pro-Tip: Use "Hard Stops." If a Retrospective is scheduled for 60 minutes, end it at 60 minutes. Timeboxing forces the team to prioritize the most important issues rather than venting about minor ones.

4. The Holy Grail: Unified Planning for Cross-Functional Teams

This is where most cross-functional teams break down. To stop the "Silo Effect" between Backend (BE) and Frontend (FE), adopt these three habits:

The "Interface-First" Approach

Before a single line of code is written, BE and FE engineers must agree on the API Contract (the JSON structure). Once this "handshake" is defined, the FE team can build using mock data while the BE builds the logic. No one waits for anyone.

The "One-Sprint-Ahead" Design Rule

UX/UI Design is not part of the current sprint’s development; it is part of Refinement. If a story’s design isn't finalized by Planning, that story is not "Ready" and does not enter the sprint.

The "Three Amigos" Flow

During planning, use this three-phase approach for every story:

  • Phase A (The Vision): The Product Owner explains the "Why" and "What."

  • Phase B (Technical Alignment): BE and FE engineers draft the contract together and identify architectural hazards.

  • Phase C (Sub-Tasking): Break the story into specific tasks (e.g., Task 1: Define API Contract; Task 2: Build logic; Task 3: Integration testing).

5. Shared Planning vs. Split Planning

Shared Planning (The Goal)

Split Planning (The Risk)

Early Integration: Interfaces are discussed upfront.

Late Integration: Bugs are discovered on Day 9.

High Context: Everyone understands the full stack.

Black Boxes: Teams don't know what the "other side" is doing.

Shared Accountability: The team wins or loses together.

Finger-Pointing: "The API is broken" vs. "The UI is wrong."

Parallel Work: FE uses mocks to start immediately.

Linear Work: FE waits for the BE to finish.

One final tip: If the Backend team needs a deep 10-minute architecture huddle that the Frontend doesn't need to hear, use a breakout group. Huddle for 10 minutes, then "regroup" and explain the final plan to the other half.

Summary

A good Working Agreement isn't about control—it’s a shield against chaos. It protects the developers' focus, the Product Owner’s roadmap, and the customer’s need for reliable software.

Your action item for tomorrow: Share this with your team. At your next Retrospective, ask: "Which one of these rules are we breaking the most?"

Fix that one thing first. Your velocity will thank you.