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.
- 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
Section titled “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, 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).
Where AppointMe is stronger
Section titled “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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| Read side | A separate query path for lists and dashboards | Dapper repositories with an explicit CompanyId predicate. See 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
Section titled “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
DbContextand 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.
- Scaffolding.
dotnet new ca-usecasegenerates 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 uppackages, 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?2 min · optional
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…
Section titled “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
Section titled “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. |
Application/<Feature>/Commands/<UseCase> |
A slice folder with command, handler and endpoint | See 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. |
[Authorize(Roles = …)] on the request |
principal.Require(...) in the handler |
See Add a permission. |
IApplicationDbContext |
One DbContext per module, with its own schema |
See Database migrations. |
AutoMapper ProjectTo queries |
A Dapper repository returning DTOs | See CQRS. |
Domain events dispatched in SaveChanges |
IDomainEvent on the aggregate, handled by Wolverine on durable queues |
See Cross-module events. |
IEndpointGroup classes in Web/Endpoints |
One IEndpoint per slice, discovered with Scrutor |
See Add an endpoint. |
| NSwag client | orval-generated hooks and types | See Typed 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
Section titled “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 | README (options, stack, versions), Directory.Packages.props, src/ and tests/ tree, stars and contributors |
DependencyInjection.cs and DispatchDomainEventsInterceptor.cs |
Identity setup and in-process domain events |
AppHost/Program.cs |
Aspire resources and the Azure Container Apps environment |
| Deployment | azd flow and its production advice |
| OpenAPI generated clients | NSwag client generation |
| Code scaffolding | ca-usecase |
| Automated tests and the docs sitemap | Test layers and the Aspire test host, docs page count |
| mediatr.io and MediatR license | MediatR and AutoMapper licensing |
AppointMe facts are from bravodev-hub/appointme at commit 2ccbe87.