From CocktailDev to Tendero: an AI-native ecommerce platform in .NET


From CocktailDev to Tendero: an AI-native ecommerce platform in .NET

TL;DR

  • Tendero is an AI-native ecommerce platform in .NET 11 and Angular 22 that runs on a laptop with Docker, for people and AI agents.
  • It aims at a real gap: there is no .NET reference implementation of UCP, the new protocol for agentic commerce.
  • It is built in the open with Claude Code, and every article links the exact code it describes.

In January 2023 I published The new challenge: CocktailDev. It was already my second try. Years before, I had started a book and never finished it, and CocktailDev was my plan to turn it into something I could really ship. The idea was a cocktail shaker: take all my scattered technical work, mix it, and pour out one single story instead of a shelf of unrelated experiments.

The plan was to work in the open, deliver something useful from the first day, and not wait until everything was finished. I wrote this, and I meant it:

I don’t want to write down a complete task list in advance… daily work will decide.

I still think that was right. What I am doing now is the same project with a new name, a new domain, and one thing that did not really exist when I wrote that post.

The name is Tendero — Spanish for shopkeeper. The domain is ecommerce. The new thing is AI.

What actually changed

The easiest way to see why this is a new start, and not a continuation, is the list of technologies in that 2023 post. It named MediatR, AutoMapper and Dapr, among others. It also described a move from a monolith to microservices, deployed on Azure and Kubernetes.

Three years later, MediatR and AutoMapper both have commercial licences. So does FluentAssertions v8, and Moq had a trust problem. In 2023 those libraries were a big part of what «a modern .NET project» meant. Today, a public repository has to choose them on purpose, not out of habit.

Tendero has a hard rule for this, written in the project’s constitution: every dependency must be free for commercial use under a permissive open-source licence, and the licence is checked for the exact version before it is added. That is why the CQRS dispatcher, the object mapping and the outbox are written by hand in this project. Writing your own is usually not a good idea. But the alternative is a repository that the people who clone it may not be allowed to use.

I am not embarrassed by that 2023 list. It shows something useful: a plan does not last. After three years, the intentions survived: work in the open, ship in small steps, learn in public. Every single technology choice did not. That is good to know before you write your next plan.

The other change is that the interesting problems moved. In 2023, «from monolith to microservices» was a story worth telling. In 2026 that discussion is over, and the new frontier is software that is used by other software. That is why this project is about ecommerce, and it is not a random choice.

Why agentic commerce, and why now

I work in ecommerce. That alone was a good reason to choose it: when you already know the business, you can spend your attention on the architecture instead of the business rules.

But there is a second reason, and it is the one that made me start.

Agentic commerce is still open. The Universal Commerce Protocol (UCP) launched at NRF in January 2026, backed by Shopify, Google and about two dozen retailers. It covers catalogue search, cart, identity, checkout and order management. AP2, which handles payment consent with cryptographic mandates, was donated to the FIDO Alliance in April 2026.

The official reference implementations exist only in Python and Node.js. There is no .NET one.

For a .NET architect, filling that gap is the most useful thing to do right now. It is also why Tendero looks like this, and is not just another sample shop.

The tagline comes from that: commerce for humans and agents. A shopkeeper who knows every customer by name, remembers what they bought, and suggests what really fits — for people, and for the software that acts for them.

What I want to get out of this

Three things, starting with the most honest one.

Learn, and really understand what I only half know. Reading about the outbox pattern is very different from watching your own outbox stuck at 115 messages, with none processed, while every test in the repository is green. I want the second kind of knowledge, in two roles: as a Software Architect and as an AI Architect — a job that did not even have a name when I wrote the CocktailDev post.

Share the experience, including the parts that do not make me look good. The project keeps a notebook, written while things happen, with three rules. If something cost an afternoon, it goes in. Numbers are written exactly as measured: «it got faster» means nothing six months later, but «0.769 to 0.860» still does. And it says who made the mistake — usually me.

Grow with AI, and especially with Claude Code. I don’t mean «use an assistant to type faster». I mean studying the collaboration itself: what it does well, where it is confidently wrong, and what a repository needs for the collaboration to work at all.

That last point needs more than a bullet, and I will come back to it.

What Tendero is

An AI-native ecommerce platform: .NET 11 and Aspire on the backend, two Angular apps on the front, and everything self-hostable with Docker at zero cost. It owns its own commerce core — catalogue, cart, checkout and orders — instead of using an existing platform for that. A UCP server on top of Shopify would be useless on the day Shopify adds UCP for its own merchants. So here Shopify is only a catalogue source, never the engine.

This is the honest state of the project. Solid lines run today from a fresh clone. Dashed lines are planned, and on purpose they are not declared yet.

What runs today, and what it becomes

That difference is a rule, not just a drawing style. Qdrant, a local model server and Redis are all part of the target architecture, but none of them is in the orchestrator yet, because no code uses them. Declaring them early would mean five gigabytes to download on someone’s first run, and three containers that nothing connects to. They arrive in the same pull request as the code that uses them.

One box in the diagram needs a note, because it changed the story: WebMCP already works. An AI agent running inside the shopper’s own browser tab can search the catalogue, open a product and add it to the cart, using the same Angular services as the interface. It needs no token and no mandate, because it uses the session the shopper already has. It is one of three ways for agents to use the shop, and it is the cheap one for exactly that reason. The other two — an external MCP server, and UCP with signed mandates — need an identity of their own, and they are still dashed.

Three planes, and no dependency points up

Three planes, and no dependency points up

The inbound plane is any number of catalogue sources behind one single port. Adding Shopify means one class and one registration line, with no if in any handler. The new adapter also has to pass a contract-test suite before it can be merged.

The core is five bounded contexts that share no entities: catalogue, pricing, inventory, ordering and accounts. It started with two, and the number was never the rule. The rule is that contexts share no entities and talk through events. For example, an order copies the product name and price when it is placed. It never keeps a live reference to a product that could be renamed or archived later. Everything is built as vertical slices: one folder per feature, and a slice can never reference another slice. Shared behaviour goes down into the shared kernel, or out into a port.

The exposure plane is HTTP for the two Angular apps, plus the browser tools that come with the storefront. The MCP server and UCP for external agents come later.

Every solid arrow in the diagram is a dependency, from the code that calls to the code it calls, and none of them points up. Five of them go from one part of the core to another. Search reads the two contexts it projects, and ADR 0007 explains why. Catalog and Ordering ask Inventory for stock through its ports, and Ordering runs Pricing’s engine again through a single adapter. Each of these three crossings is protected by an architecture test, and the one behind the stock saga also has ADR 0024. That is what ADRs are for: not to describe what you did, but to make exceptions expensive, so that you only make them on purpose.

The only arrow that points up is dotted, on purpose. It is an event going to the outbox worker, not a dependency: a context that raises an event never knows who reads it.

None of this depends on good intentions. It depends on tests that fail:

[Fact]
public void Domain_code_depends_on_the_shared_kernel_and_nothing_else()
{
foreach (var assembly in Solution.Contexts)
{
var forbidden = ForbiddenForDomainIn(assembly);
var result = Types.InAssembly(assembly)
.That().ResideInNamespaceEndingWith(DomainSuffix)
.ShouldNot().HaveDependencyOnAny(forbidden)
.GetResult();
Assert.True(result.IsSuccessful, Describe(assembly, result));
}
}

The forbidden list is calculated from the project file, not written by hand. If you add a package to a context, the rule includes it automatically. When one of these tests goes red, the answer is a design discussion — maybe an ADR — and never a weaker rule.

A test can still lie in a quieter way: it can run code without really checking it. A new test for code that already exists passes the first time it runs, and that is exactly when it has proved nothing. So here a test has to earn its place. The code is broken on purpose — a condition inverted, a filter removed — and the test must go red before it counts. Today this is done by hand, with one deliberate break per test. The next step is a tool that does it systematically: Stryker.NET changes the code in many small ways and reports which changes no test noticed. It will run at the end of every phase. That is why it is dashed in the stack below: the practice exists today, and the tool comes with the code of a later article.

The stack

Everything runs on a laptop, for nothing

One rule applies to all six layers: it has to run on a laptop, from a clone, for free. No cloud account, no API key, no sign-up. Open models run in a container instead of a hosted API. Elasticsearch, Postgres and Keycloak run in Docker. The goal is not to save money. The goal is that someone who is not me can read, clone and run the repository.

How a product becomes findable

This is the flow I would show first to anyone who wants to understand the project, because three separate design decisions meet here:

From an import to a search result

Importing saves products as Draft and returns. Search only reads Active products. Between those two facts there is an explicit publish step, and until someone does it, the catalogue is invisible.

I did not design that gap. I designed each half carefully, and I found the gap when the storefront returned nothing for cafetera on a machine where the search quality gate was passing at 0.860 — the baseline at that time. That story is its own article. The short version is a sentence I keep coming back to: a comment that admits a hole is not a test that fails while the hole is open.

Also look at where the response arrow is. The request finishes before anything is indexed. Every side effect in this system goes through domain events, saved to an outbox table in the same transaction as the change itself, and a worker processes them later. No handler indexes, sends emails or calls a model inside a request.

Where it actually is today

A diagram means nothing without the real state of the project:

HTTP routes shipped34
Tests green547 backend · 39 frontend · 20 browser
Catalogue100 products, es and en
Search relevance, SpanishNDCG@10 0.813 · recall@50 0.633
Search relevance, EnglishNDCG@10 0.766 · recall@50 0.556
Annotated golden-set queries44 across two cultures, 268 judgments
Architecture decision records21
Articles publishedthis is the first

The last row is in the same table as the others on purpose. The drafts exist, in publication order, and until today none of them was published. The public repository gets the code an article describes on the day that article is published, not before. Deciding when to start publishing is my choice, and on purpose it is not «the first phase is finished, so publish now» — that kind of pressure is exactly what turned the original book into a shelf of unfinished work.

If NDCG@10 is new to you: it is a score from 0 to 1 for the order of the first ten results, compared with an ideal order that a person defined in advance. The repository has a glossary for this term and for the rest of the vocabulary in this article.

Two numbers in the table need a warning. The Spanish and English scores look worse than the one I mentioned earlier in this article, but they are not worse. The catalogue grew from 6 products to 100, and the golden set from 30 judgments to 268. NDCG over six documents and NDCG over a hundred are different measurements. The lower recall is the honest part: the new judgments include products a shopper would happily accept — a gym trainer for «running shoes», a duffel bag for «backpack» — that a lexical search cannot find, because they do not contain the searched words. Recall now shows that gap instead of hiding it, and that gap is what hybrid search will have to close.

What is coming, and in what order

The foundation is built: a catalogue with variants, a localized taxonomy, prices and promotions with combination rules, real-time inventory, cart, checkout, payments and returns, accounts behind a real identity provider, and the first of the three agent surfaces.

What is left is the part the project exists for. Hybrid search with embeddings, measured against the baseline above. The MCP server and the UCP manifest. A knowledge layer where the AI writes claims with a source and a confidence, and a person approves them. The transactional half of UCP, with AP2 mandates. A backoffice copilot that proposes commands instead of text. And multimodal search.

The next articles are already drafted or outlined, in a fixed order. The next one is One port, N catalogs.

Three rules you can hold every future article to

The features will change as I learn — that is the lesson of the CocktailDev list. These three rules will not change, and each one is written in the project’s constitution, not in a slide:

  • Every AI feature has a non-AI path, and that path is tested. If the model server or the vector database is down, the shop still searches with normal lexical search. The test that proves it comes with the feature, not a comment saying it should work.
  • AI proposes, people approve, the system applies. A model never changes a product or an order directly. It writes a claim or a proposed command, with its source and its confidence. Only a person’s approval changes anything, and that approval is recorded in the audit log, not in a chat.
  • The architecture is complete; the data is laboratory size. A hundred products, two warehouses and three roles — but real promotion rules, a real saga with compensation and a real identity provider. Work that only matters at large scale is postponed, with its measured numbers written down, and never silently skipped.

And one convention for the whole series: every article ends with How we know it works. It shows the test that failed first, or the deliberate break that proves the claim; the check that protects it on every pull request; and, once the project uses Stryker.NET, the mutation score of the code in the article. Architecture you only read about is just a diagram. Architecture with a red test behind it is something you can check.

Building with Claude Code: the part I think will be most useful to you

This is where I want to take the series beyond architecture.

Everything above is built together with Claude Code, and that way of working is worth writing about. Not the marketing version, but the real one, where:

  • I proposed a feature flag to hide infrastructure that nothing used yet, and I was asked why do we need a flag? I had no answer. A flag controls behaviour that exists and can be turned on. Nothing used Qdrant, so there was nothing to turn on. The right answer was to remove it and add it back in the pull request that uses it.
  • The assistant told me my IDE would not compile because the preview SDK was not on my PATH. That was true for my terminal and false for the IDE, which finds the SDK on its own — and the log said so, one minute of reading away. A plausible cause is not a diagnosis.
  • We both read «81 of 115 outbox messages processed» and decided the fix had worked. In fact, those 81 had been processed hours earlier by a forgotten process connected to a database container that no longer existed. The number was real. The explanation was wrong.

That is how this way of working really feels, and I have not seen much written about it that is neither hype nor dismissal.

So here is the concrete plan, and it is the part I am most excited about.

As the project grows, the repeated work becomes skills: packaged instructions for the tasks this repository does again and again. Creating a new vertical slice with its tests and its port. Running the relevance evaluation and reading the report. Writing an ADR in the project’s format. Drafting an article from a finished feature branch and the notebook.

Those four skills are in the project plan, and none of them exists yet. When they do, each one gets an article: what went into it, what it got wrong the first time, and how much time it really saved.

Then comes the step I think is really worth it: packaging them as a plugin, with real installation instructions for Claude Code, and then porting the same skills to Codex, Cursor and the other tools people use. A skill that only works in one tool is a personal shortcut. A plugin that installs anywhere is something you can give to someone else.

That is the difference between writing about a workflow and shipping one. Apart from the UCP server, it is the most original thing this series can offer.

The same objective as CocktailDev

Three years later, the cocktail shaker still works as an image: scattered work, one story, shared in public while it is still being built, not after it is finished and tidy.

The difference is that now I have the two things that project was missing. A domain with a real open problem. And a collaborator that lets one person, working in the evenings, build something with architecture tests, a measured quality gate and twenty-one decision records — while still being wrong often enough to keep the writing honest.

The repository is public and MIT licensed, and the code exactly as this article describes it is tagged blog/00-from-cocktaildev-to-tendero. It runs with one command:

dotnet run --project src/AppHost

How to follow along. Every article has a tag like that one, and its code reaches the public repository on the same day as the article — never before, so you never read about something you cannot run. Clone the tag, run the command, and keep the glossary open for new terms.

How we know it works

  • The test that fails: make any type in a *.Domain namespace depend on something its project uses, other than the shared kernel, and Domain_code_depends_on_the_shared_kernel_and_nothing_else goes red.
  • The check on every pull request: the build treats warnings as errors, runs the architecture rules and the 547 backend tests, and runs the NDCG@10 relevance gate against a real Elasticsearch.
  • The mutation score: not yet. Stryker.NET comes in a later phase, and this line will be filled in from the first article whose tag includes it.

Next article: One port, N catalogs — why every commerce platform ends up with a folder full of importers, and what one interface and a contract-test suite cost instead.

Tendero — commerce for humans and agents.

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.