# AppointMe vs Clean Architecture template

> Jason Taylor's Clean Architecture template is a single-app .NET 10 solution template, not a SaaS foundation. Here's when it's the better pick, and what you'd build on it to match AppointMe.

Source page: https://appointme.dev/docs/compare/clean-architecture/

* **CA is:** A single-app solution template, not SaaS
* **Multi-tenancy:** None; you’d build it yourself
* **Shared ground:** .NET 10, EF Core 10, Aspire, React
* **Pick CA if:** One app, one customer base

## At a glance

**The Clean Architecture template (CA) gives you a well-structured single application; AppointMe gives you a multi-tenant SaaS foundation with more parts already wired.**

|                           | AppointMe                                                                                                                                           | Clean Architecture template                                                                                                                                  |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Architecture              | Modular monolith: 4 modules by bounded context, each with its own `DbContext`, schema and `.Contracts` project. Vertical slices inside each module. | Four projects by layer: Domain, Application, Infrastructure, Web. Use cases are MediatR commands and queries grouped by feature, with a Todo sample.         |
| Multi-tenancy             | One shared database with a `CompanyId` on every row, enforced in EF Core, Dapper and messages.                                                      | None.                                                                                                                                                        |
| Auth                      | OIDC with Keycloak locally and Entra External ID on Azure. JWT Bearer for the API, cookies for browser flows.                                       | ASP.NET Core Identity in the app’s database, with `MapIdentityApi`. Cookies with an SPA, bearer tokens for API only.                                         |
| Permissions               | `Permission` fields auto-discovered from code, 4 built-in roles, per-company role overrides.                                                        | `[Authorize(Roles = …, Policy = …)]` on a request, checked by a MediatR `AuthorizationBehaviour`.                                                            |
| Data access               | EF Core 10 for writes, Dapper for reads. SQL Server.                                                                                                | EF Core 10 in the Application layer, with AutoMapper projections. SQLite (default), PostgreSQL or SQL Server.                                                |
| Messaging                 | Wolverine 6: domain events and handlers on durable local queues in SQL Server. Azure Service Bus by config.                                         | Domain events published in-process through MediatR from an EF Core `SaveChanges` interceptor. No outbox or broker.                                           |
| Frontend                  | One React 19 SPA: TypeScript 6, Vite+, Tailwind 4, shadcn/ui, TanStack Query, with hooks generated by orval.                                        | Angular (default), a minimal React 19 starter in JSX, or API only. A TypeScript client generated by NSwag.                                                   |
| Local dev                 | Aspire runs SQL Server, Keycloak, Mailpit, the API and the SPA, then migrates and seeds demo data.                                                  | Aspire runs the database, the API and the SPA. SQLite needs no Docker.                                                                                       |
| Deploy                    | Bicep for Azure App Service, Azure SQL and Key Vault. GitHub Actions with OIDC on a `v*` tag.                                                       | `azd up` provisions and deploys from the Aspire model to an Azure Container Apps environment. `azd pipeline config` sets up CI.                              |
| Tests                     | 221 xUnit unit tests, with no database or HTTP host.                                                                                                | NUnit, Shouldly, Moq and Respawn: about 34 unit and functional tests (functional ones through an Aspire test host) and 6 Playwright scenarios with Reqnroll. |
| Docs                      | This site, about 50 pages.                                                                                                                          | About 20 pages at cleanarchitecture.jasontaylor.dev, plus ADRs in the repo.                                                                                  |
| License                   | MIT. A PRO add-on is planned.                                                                                                                       | MIT for the template. Its MediatR and AutoMapper versions are dual-licensed (see the Gotcha below).                                                          |
| Maintainers and community | One main maintainer (BravoDev). About 50 GitHub stars. Public since June 2026.                                                                      | Jason Taylor, with more than 80 contributors. About 20.7k stars. Public since 2019.                                                                          |
| Live demo                 | [One click](https://app.appointme.dev/api/v1/login/demo), no sign-up.                                                                               | None linked. You generate it with `dotnet new ca-sln`.                                                                                                       |

AppointMe facts are from commit `2ccbe87`. CA facts are from its `main` branch and docs (see [Sources](#sources)).

Gotcha

The template is MIT, but `Directory.Packages.props` pins MediatR 14.2.0 and AutoMapper 16.2.0. From MediatR 13 and AutoMapper 15 on, both are dual-licensed (RPL-1.5 or commercial), with a free Community license only for organizations that meet its eligibility rules. Check [mediatr.io](https://mediatr.io/) before you ship. AppointMe uses Wolverine (MIT) instead, and has no mapper.

## Where AppointMe is stronger

**To reach AppointMe’s baseline on CA, you’d build tenancy, permissions, durable messaging and an identity-provider integration yourself.**

| You’d build       | What it involves on CA                                                                                                                       | What AppointMe ships                                                                                                                       |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Tenancy           | A tenant entity and membership, per-request resolution, a filter on every tenant-scoped entity, and the tenant in raw SQL, jobs and messages | `CompanyId`, `ICurrentCompany`, named EF filters and `InvokeForCompany`. See [Multi-tenancy](/docs/concepts/multi-tenancy/).               |
| Permissions       | A permission catalog, role grants, per-tenant overrides and a resolver per request                                                           | Auto-discovered `Permission` fields, `IDefaultGrantPolicy` and per-company role overrides. See [Permissions](/docs/concepts/permissions/). |
| Durable messaging | An outbox, retries, an error queue and the tenant on each message. CA’s events run in-process inside `SaveChanges`.                          | Wolverine with durable local queues in SQL Server. See [Domain events & messaging](/docs/concepts/messaging/).                             |
| External identity | OIDC sign-in, app-managed sign-up and invitations, and a local identity provider for dev                                                     | Keycloak locally and Entra External ID on Azure, behind `IIdentityProvider`. See [Entra External ID](/docs/deploy/entra-external-id/).     |
| Module boundaries | Splitting the single `ApplicationDbContext` into one per bounded context, with schemas and contracts                                         | 4 modules with their own `DbContext`, schema and `.Contracts` project. See [Modular monolith](/docs/concepts/modular-monolith/).           |
| Local stack       | Keycloak with a realm import, a mail catcher and demo seeding in the AppHost                                                                 | Aspire runs SQL Server, Keycloak and Mailpit, migrates and seeds. See [Quick start](/docs/start/quick-start/).                             |
| Read side         | A separate query path for lists and dashboards                                                                                               | Dapper repositories with an explicit `CompanyId` predicate. See [CQRS](/docs/concepts/cqrs/).                                              |

Each row is a known pattern. The work is in how they interact: the tenant has to reach EF Core, raw SQL, background jobs and messages, and permissions depend on the tenant.

## Where the Clean Architecture template is stronger

**CA is simpler, more flexible at generation time and has a broader test setup, and for a single application it’s the better start.**

* **Simpler to learn and explain.** Four layer projects, one `DbContext` and a Todo sample, with Architecture Decision Records that explain the choices. It’s one of the most-starred .NET templates, so new hires may already know it.
* **Choices when you generate.** Angular, React or API only, and SQLite, PostgreSQL or SQL Server. AppointMe is React and SQL Server only.
* **More kinds of tests.** Functional tests run against a real database through an Aspire test host, reset with Respawn, and Playwright acceptance tests drive the UI through Reqnroll scenarios. AppointMe has more tests (221 against about 40) but only unit tests, with no database. See [Testing](/docs/testing/overview/).
* **Scaffolding.** `dotnet new ca-usecase` generates a command or query with its handler and validator. AppointMe has no item templates.
* **Fewer moving parts for one app.** ASP.NET Core Identity lives in the same database, so there’s no identity-provider container to run or Entra tenant to set up.
* **A quick Azure sandbox.** `azd up` packages, provisions and deploys from the Aspire model in one step. Its docs recommend a CI/CD pipeline for production.
* **Track record.** Maintained since 2019, with a branch for every .NET version from Core 3.1 to 9, about 20.7k stars and more than 80 contributors. AppointMe is months old with one main maintainer.

### Why not start from Clean Architecture and add tenancy later?

You can, and plenty of teams do. The risk is in the retrofit: every entity, query, job and message needs the tenant, and one missed predicate leaks another customer’s data.

AppointMe starts with the tenant in every path, including Dapper reads and Wolverine envelopes, and ships the membership model (a user is an employee of each company) that tenancy depends on.

If you may never have tenants, that structure is a cost with no payoff, and CA is the leaner choice.

## Choose Clean Architecture if… / choose AppointMe if…

**CA fits a single application or a learning project; AppointMe fits multi-tenant SaaS you’d otherwise assemble yourself.**

| Choose Clean Architecture if…                                      | Choose AppointMe if…                                            |
| ------------------------------------------------------------------ | --------------------------------------------------------------- |
| You’re building one app for one organization                       | You’re building SaaS where each customer is a tenant            |
| You’re learning Clean Architecture or teaching it                  | You want tenancy, permissions and messaging already wired       |
| You want Angular, or an API with no frontend                       | You want React and TypeScript                                   |
| You want SQLite or PostgreSQL                                      | SQL Server is fine                                              |
| You want users and sign-in in your own database                    | You want Keycloak, Entra External ID or another OIDC provider   |
| You want functional and browser tests set up from the first commit | You want a running multi-tenant app and live demo to start from |
| Fewer moving parts matters more than built-in features             | You’d rather delete example modules than write a foundation     |

## Migrating from Clean Architecture

**Porting from CA means splitting the layers into modules and replacing MediatR with Wolverine, one use case at a time.**

| Clean Architecture                          | AppointMe                                                               | Notes                                                                                                                           |
| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `Domain` entities on `BaseEntity`           | Aggregates on `AggregateRoot`, inside a module                          | Add a `CompanyId` to tenant-scoped ones. See [Multi-tenancy](/docs/concepts/multi-tenancy/#make-your-own-entity-tenant-scoped). |
| `Application/<Feature>/Commands/<UseCase>`  | A slice folder with command, handler and endpoint                       | See [Vertical slices](/docs/concepts/vertical-slices/).                                                                         |
| `IRequest<T>` and `IRequestHandler`         | A Wolverine handler with a `HandleAsync` method                         | `CompanyId` and `IPrincipal` are injected as parameters.                                                                        |
| FluentValidation validator                  | Value-object factories such as `Email.CreateOrNull`                     | They throw `ValidationException`. See [IDs & value objects](/docs/concepts/ids-and-value-objects/).                             |
| `[Authorize(Roles = …)]` on the request     | `principal.Require(...)` in the handler                                 | See [Add a permission](/docs/guides/add-a-permission/).                                                                         |
| `IApplicationDbContext`                     | One `DbContext` per module, with its own schema                         | See [Database migrations](/docs/guides/database-migrations/).                                                                   |
| AutoMapper `ProjectTo` queries              | A Dapper repository returning DTOs                                      | See [CQRS](/docs/concepts/cqrs/).                                                                                               |
| Domain events dispatched in `SaveChanges`   | `IDomainEvent` on the aggregate, handled by Wolverine on durable queues | See [Cross-module events](/docs/guides/cross-module-events/).                                                                   |
| `IEndpointGroup` classes in `Web/Endpoints` | One `IEndpoint` per slice, discovered with Scrutor                      | See [Add an endpoint](/docs/guides/add-an-endpoint/).                                                                           |
| NSwag client                                | orval-generated hooks and types                                         | See [Typed API client](/docs/frontend/api-client/).                                                                             |
| ASP.NET Core Identity users                 | Users in Keycloak or Entra External ID, plus a local `User` row         | Plan how existing users set a new password.                                                                                     |

## Sources

**Every Clean Architecture fact on this page comes from its repository, its docs and the MediatR site, accessed Nov 2026.**

| Source                                                                                                                                                                                                                                                                                                                | Used for                                                                                                        |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| [github.com/jasontaylordev/CleanArchitecture](https://github.com/jasontaylordev/CleanArchitecture)                                                                                                                                                                                                                    | README (options, stack, versions), `Directory.Packages.props`, `src/` and `tests/` tree, stars and contributors |
| [`DependencyInjection.cs`](https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Infrastructure/DependencyInjection.cs) and [`DispatchDomainEventsInterceptor.cs`](https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Infrastructure/Data/Interceptors/DispatchDomainEventsInterceptor.cs) | Identity setup and in-process domain events                                                                     |
| [`AppHost/Program.cs`](https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/AppHost/Program.cs)                                                                                                                                                                                                          | Aspire resources and the Azure Container Apps environment                                                       |
| [Deployment](https://cleanarchitecture.jasontaylor.dev/docs/getting-started/deployment/)                                                                                                                                                                                                                              | `azd` flow and its production advice                                                                            |
| [OpenAPI generated clients](https://cleanarchitecture.jasontaylor.dev/docs/features/openapi-generated-clients/)                                                                                                                                                                                                       | NSwag client generation                                                                                         |
| [Code scaffolding](https://cleanarchitecture.jasontaylor.dev/docs/getting-started/code-scaffolding/)                                                                                                                                                                                                                  | `ca-usecase`                                                                                                    |
| [Automated tests](https://cleanarchitecture.jasontaylor.dev/docs/features/automated-tests/) and the [docs sitemap](https://cleanarchitecture.jasontaylor.dev/sitemap-0.xml)                                                                                                                                           | Test layers and the Aspire test host, docs page count                                                           |
| [mediatr.io](https://mediatr.io/) and [MediatR license](https://github.com/LuckyPennySoftware/MediatR/blob/main/LICENSE.md)                                                                                                                                                                                           | MediatR and AutoMapper licensing                                                                                |

AppointMe facts are from [bravodev-hub/appointme](https://github.com/bravodev-hub/appointme) at commit `2ccbe87`.

## Next

* [How to choose a .NET SaaS template](/docs/compare/overview/) (Compare)
* [Multi-tenancy](/docs/concepts/multi-tenancy/) (Concept)
* [Build your first feature](/docs/start/first-feature/) (Tutorial)
