Skip to content

Feature/2 add single project structure#11

Merged
cstacklab merged 18 commits into
mainfrom
feature/2-add-single-project-structure
Jun 23, 2026
Merged

Feature/2 add single project structure#11
cstacklab merged 18 commits into
mainfrom
feature/2-add-single-project-structure

Conversation

@cstacklab

Copy link
Copy Markdown
Owner

No description provided.

cstacklab added 18 commits June 17, 2026 20:01
Move the existing multi-project template into a self-contained layered/ folder
in preparation for a second 'modular' variant. Repo-level files (README, LICENSE,
CONTRIBUTING, docs/, .github) stay at the root and are shared across variants.

Behavior preserving: layered/ builds, unit tests pass, the template packs and
instantiates with database/migrations intact. The shared-database hoist to root
and the .github CI rework are intentionally deferred to later steps.
Second dotnet new variant 'clean-api-modular' alongside layered. Collapses
Domain/Application/Infrastructure into one CleanApiStarter.Api project organized
by folders (Domain/, Infrastructure/, Common/, Features/), reusing the AspNetCore
and Configuration platform projects plus AppHost. Namespaces are CleanApiStarter.Api.*.

Boundaries are enforced by NsDepCop (config.nsdepcop + WarningsAsErrors=NSDEPCOP01):
Domain may not reference Features/Infrastructure, Features may not reference
Infrastructure. Verified that an illegal Domain->Infrastructure reference fails
the build with NSDEPCOP01.

Both variants given distinct template identities/package ids: clean-api-layered
and clean-api-modular. Verified: modular builds, unit + integration tests pass,
and the template packs, instantiates, and the generated project builds.

Deferred to step 3: shared database/ at repo root (modular currently has its own
copy), .github CI matrix, CONTRIBUTING refresh, and a modular-specific README.
- Move database/ to the repo root as the single source of truth; each variant
  gets a git-ignored copy via scripts/sync-database.sh (CI and install scripts
  run it before building/packing). Removes the duplicated, drift-prone copies.
- Rework CI as a matrix over [layered, modular]: build.yml, template.yml (packs +
  instantiates each variant by shortName/package), codeql.yml, release.yml
  (publishes both NuGet packages). dependabot.yml now watches both variant dirs.
- Ship a per-variant generated-project build.yml (restores CI that generated
  projects lost when the repo .github moved to the root).
- Fix per-variant install-template.sh for the new package ids + sync step.
- Refresh CONTRIBUTING.md for the two-variant workflow; give modular its own
  README; update the root README (modular is available, database sync documented).

Verified: both variants build, and both pack -> install -> instantiate -> the
generated project builds, with migrations and a build workflow shipped.
Capture the architecture's evolution: seven-project split -> dual layered+modular
variants -> a single modular solution of three projects with analyzer-enforced
boundaries. Insights synthesized as general industry knowledge.
…uration into Api

Per ADR-003, reduce the modular variant toward three projects:
- Rename CleanApiStarter.AspNetCore -> CleanApiStarter.AspNetCoreDefaults (folder,
  csproj, assembly, namespace).
- Fold CleanApiStarter.Configuration into the Api project (CleanApiStarter.Api.
  Configuration), removing the separate project.
- Break the resulting reference cycle by keeping AspNetCoreDefaults
  application-agnostic: the JWT bearer token-validation options are now bound from
  AppSettings by the composition root (Api) via AddJwtBearerOptions, instead of
  inside the defaults project.

Modular now has three source projects (Api, AspNetCoreDefaults, AppHost). Build,
unit tests, and the Testcontainers integration test (which exercises JWT auth)
all pass.
Consolidate to a single modular solution at the repository root, replacing the
two-variant layout:
- Delete the layered/ variant; move the modular solution (Api, AspNetCoreDefaults,
  AppHost + tests) to the root.
- database/ at the root is referenced directly; remove the per-variant sync
  mechanism (scripts/sync-database.sh) and its gitignore entry.
- Revert CI from a two-variant matrix to single-solution workflows; dependabot
  watches one nuget directory again.
- Single template again: identity/shortName CleanApiStarter.Template /
  clean-api-starter, PackageId CleanApiStarter.Template. Exclude adr/, docs/,
  CONTRIBUTING from packed output.
- Refresh README and CONTRIBUTING for the single-solution structure.

Verified: build, format check, unit + integration tests pass; template packs,
instantiates, and the generated three-project solution builds with migrations and
CI workflows shipped.
- Delete docs/architecture/ardalis-clean-architecture-vsa-transcript.md.
- Strip source attribution (author/transcript references) from
  clean-architecture-and-vertical-slices.md and reframe its now-stale two-variant
  wording for the single-solution structure.
- Add an 'Architecture in brief' summary near the top of README.md that distills
  the three-decisions framing and links to the full architecture doc and the ADRs.
Config files under Solution Items now sit in nested .config/, .github/workflows/,
and .template.config/ folders instead of a flat list. Fold the virtual src/Common
group into src/ (the projects are siblings on disk) and normalize tests paths to
forward slashes.
Folders-only step toward proper vertical slices: move the task contracts
(ProjectTaskDto, Create/Update task validators) into Features/Projects/Tasks with a
matching CleanApiStarter.Api.Features.Projects.Tasks namespace. Auth stays a flat
capability folder. Handlers and endpoints are untouched.
…points/ folder

Move the Auth/Projects (V1) and Projects (V2) endpoint groups and the dev Google
login page into their feature folders with matching namespaces; remove the separate
Endpoints/ folder. Endpoint groups are still discovered by reflection and continue
to call ProjectService / IAuthService (handlers unchanged). Drop the now-unused
Endpoints global using; refresh the README layout.

Verified: build, format, unit and integration tests pass.
Retire the monolithic ProjectService, IProjectService, and the result enums. Each
operation is now a self-contained slice co-locating its request record, validator,
endpoint mapping, and handler that talks directly to IProjectRepository / IAuthService:

  Auth/        SignInWithGoogle, GetCurrentUser
  Projects/    CreateProject, GetProjects, GetProject, DeleteProject
  Projects/Tasks/  CreateTask, GetTasks, GetTask, UpdateTask, CompleteTask, DeleteTask
  Projects/V2/ GetProjects

Thin per-feature IEndpointGroup classes (Auth, Projects, V2.Projects) just wire the
route group and delegate to each slice's Map. Handlers return TypedResults directly,
removing the DeleteProjectResult / ProjectTaskMutationResult indirection. Mapping
moves to ProjectDto.From / ProjectTaskDto.From; a shared IUser.RequireId() guards the
user id. IAuthService.SignInWithGoogleAsync now takes the id token string, dropping
GoogleSignInDto. NsDepCop's Features-not-Infrastructure rule still holds (slices use
interfaces; impls stay in Infrastructure).

Unit test now covers the CompleteTask slice handler (asserts 409 + no save);
Application.UnitTests gains a Microsoft.AspNetCore.App framework reference to assert
on typed results. Build, format, unit + integration tests, and template
pack/instantiate/build all pass.
Features is now one self-contained file per operation (endpoint + request +
validator + handler); fix the composition-root bullet (no Endpoints folder).
Task-oriented recipes (add an endpoint/feature/migration) with copy-pasteable slice
templates, instead of a descriptive auto-loaded AGENTS.md. Not an auto-load filename,
so it costs no idle context tokens. Linked from CONTRIBUTING.
Add Claude Code project skills:
- /add-endpoint  — add an API operation as a vertical slice (path-scoped to Features/)
- /add-migration — add a schema change + keep EF config in sync (path-scoped to
  database + persistence)

Each is auto-loaded when relevant (paths frontmatter) or invokable directly. The
skills ship inside generated projects; .claude/settings.local.json is git-ignored
and excluded from the packed template. Remove the plain SKILLS.md doc (superseded);
CONTRIBUTING now points at the skills.
The AddApplication extension did nothing but register validators after the slice
refactor, so inline that single call into Program.cs and delete the file. Rename
the infrastructure DI holder from the generic DependencyInjection to
InfrastructureServiceCollectionExtensions (method AddInfrastructure unchanged).
Rename InfrastructureServiceCollectionExtensions -> InfrastructureExtensions and the
generic AspNetCoreDefaults 'Extensions' partial -> AspNetCoreDefaultsExtensions
(incl. the typeof reference in MapDefaultEndpoints). Extension method names are
unchanged. Now every holder is <Area>Extensions: Infrastructure, AspNetCoreDefaults,
WebApplication, OpenApiDocumentation, OptionsRegistration, User.
- CleanApiStarter.Application.UnitTests -> CleanApiStarter.UnitTests
- CleanApiStarter.Api.IntegrationTests  -> CleanApiStarter.IntegrationTests
- CleanApiStarter.Tests (shared)        -> CleanApiStarter.TestUtilities

No more layer qualifier now that the app is a single project (matches the
type-based convention: UnitTests / IntegrationTests / shared utilities). Updates
csproj names, namespaces, project references, the .slnx, README, and CONTRIBUTING.
The JWT issuer/audience string literals are unchanged.
@cstacklab cstacklab merged commit 8fe5f77 into main Jun 23, 2026
4 checks passed
@cstacklab cstacklab deleted the feature/2-add-single-project-structure branch June 23, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant