Skip to content
StarLive demo →
CompareChecked against main · 31 Oct 20269 min read

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

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).

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 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.
  • 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?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

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.

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.