¶
ADR 1: Documentation with MkDocs¶
- Status: Accepted
- Context: Need for clear, accessible, and versioned project documentation.
- Decision: Use MkDocs for documentation, hosted via GitHub Pages and automated with GitHub Actions.
- Consequences: Easy to maintain, supports Markdown, integrates with CI/CD.
ADR 2: Language and Stack Choices¶
- Status: Accepted
- Context: Selection of core technologies for frontend, backend, and database.
- Decision:
- Programming Language: TypeScript (frontend & backend), SQL (database)
- Database System: PostgreSQL
- Frameworks: React (frontend), Vite (build tool)
- Consequences: Modern, type-safe stack; strong ecosystem; scalable and maintainable.
ADR 3: Naming Conventions¶
- Status: Accepted
- Context: Need for consistent naming in code and version control.
- Decision:
- Use clear, descriptive names in code and Git.
- Commit/merge messages should include an issue tag.
- Consequences: Improved clarity, traceability, and collaboration.
ADR 4: Git Workflow¶
- Status: Accepted
- Context: Define branching and merging strategy for team collaboration.
- Decision:
- Use Git Flow: main and develop branches, with feature branches for new work.
- Branch and Commit Naming
Naming Conventions¶
Branches folgen dem Schema type/issue-beschreibung:
| Type | Wann | Beispiel |
|---|---|---|
feature |
Neue Funktionalität | feature/7-sheet-crud-api |
fix |
Bugfix | fix/14-websocket-reconnect |
chore |
Infra, Tooling, Deps, Config | chore/3-cdk-vpc-setup |
docs |
Doku, Blogs | docs/18-blog-phase-1 |
refactor |
Code-Umbau ohne Funktionsänderung | refactor/21-auth-middleware |
ci |
Pipeline-Änderungen | ci/9-deploy-workflow |
Regeln: Kleinbuchstaben, Bindestrich als Trennzeichen, keine Umlaute, Issue-Nummer voranstellen.
Commits folgen dem Conventional Commits Standard:
Typen: feat, fix, chore, docs, refactor, test, style, ci
Scopes: api, frontend, ws, cdk, auth, db
Beispiele:
feat(api): add sheet CRUD endpoints #7
fix(ws): handle reconnection after timeout #14
chore(cdk): add ElastiCache Redis stack #5
docs(blog): write phase 1 infrastructure post #18
ci(github): add deploy workflow for main branch #9
ADR 5: Comments and Code Quality¶
- Status: Accepted
- Context: Ensure code is understandable and maintainable.
- Decision:
- Write meaningful comments where necessary.
- Follow best practices for code quality and reviews.
- Consequences: Easier onboarding, fewer bugs, better maintainability.
ADR 6: Tech Stack Documentation¶
- Status: Accepted
- Context: Need to document and communicate the chosen tech stack.
- Decision:
- Maintain a clear tech stack section in the documentation.
- Consequences: Transparency for contributors and stakeholders.
ADR 7: Database Credentials via SSM Parameter Store¶
- Status: Accepted (Issue #20)
- Context:
ecs-express-stack.tsbuilt aDATABASE_URLby callingunsafeUnwrap()on the RDS-generated Secrets Manager secret. The synthesized template only held a{{resolve:secretsmanager:...}}reference, but CloudFormation resolved it at deploy time into an ordinary ECS container environment variable, where the password is readable by anyone withecs:DescribeServices. The pipeline worked around this by refusing to uploadcdk.out. - Decision:
- Store the DB username and password in SSM Parameter Store under
/cloudsheets/{env}/db/username(String) and/cloudsheets/{env}/db/password(SecureString). - Write them with a Lambda-backed custom resource, because CloudFormation cannot create
SecureStringparameters —AWS::SSM::Parametersupports onlyStringandStringList. CDK documents this onParameterType.SECURE_STRINGbut does not enforce it, so passing that type synthesizes cleanly and then fails at deploy time. The Lambda receives only the secret ARN and reads the value itself, so no plaintext ever enters the template. - Deliver them to the container with ECS-native secret injection
(
primaryContainer.secrets→valueFromthe parameter ARN), resolved once by the ECS agent at task start. The AWS SDK was deliberately not added to the backend:src/db.tsbuilds the knex pool eagerly at import, so a runtime fetch would need a start wrapper plus the same treatment for theknex migrateprocess, and would need mocking in tests. - Keep
DB_HOST/DB_PORT/DB_NAMEas plain environment variables. They are not secret, and routing them through SSM would add resources, IAM scope and per-task SSM calls for nothing. - Pass discrete connection properties to knex rather than rebuilding a URL — RDS passwords contain
@,/and:, which would silently corrupt apostgresql://string. - Consequences:
- Credentials are no longer readable from the ECS service configuration.
- Secrets Manager remains RDS's source of truth. Parameter Store is the delivery mechanism, not
the sole store, so the credentials exist in two places. Making SSM authoritative would require
Credentials.fromPassword(..., SecretValue.ssmSecure(...)), which changesMasterUsernameand replaces the RDS instance, plus an out-of-band bootstrap step. Declined for this issue. - Rotation is not synchronised. The custom resource only re-runs when its properties change, so
an out-of-band password rotation leaves the SSM copy stale and tasks will fail to connect. Deploy
with
-c dbSyncVersion=<n+1>to force a re-sync. Nothing configures rotation today. - The parameters are created outside CloudFormation, so the custom resource deletes them on stack
deletion in
devonly. Inprodthey are retained, matching the RDSRemovalPolicy.RETAIN— deleting them would leave a retained database with no stored credentials.
ADR 8: One Environment Switch for the Deployment Pipeline¶
- Status: Accepted (Issue #54)
- Context:
.github/workflows/deploy.ymldefinedCDK_ENVIRONMENT,ECR_REPOSITORYandECR_STACKas three independent literals, with a comment asking the next person to change all three together. Changing one and forgetting the others deploys dev infrastructure while pushing to — and pulling from — the prod registry, and nothing in the pipeline notices. The CDK app made the same class of mistake possible from the other side:appConfig[environment]returnedundefinedfor a typo, so-c environment=prdsynthesized a config without anenvironmentfield, silently taking the prod branch of every=== 'dev'check. - Decision:
- Keep one switch,
CDK_ENVIRONMENT, and derive everything else from it. - Put the mapping in
infra/hello-cdk/lib/environments.jsonrather than in TypeScript, because the workflow has to read it too. A TS-only table would have forced the pipeline to keep its own copy — exactly the duplication this removes.lib/environment.tsimports it for the CDK app,.github/scripts/resolve-environment.mjsreads it for the workflow. - Treat
ECR_STACK/ECR_REPOSITORYin the workflow environment as assertions, not overrides: if either is set and disagrees with the mapping, the run fails. - Instantiate only the stacks of the selected environment in
bin/hello-cdk.ts. Previously both ECR stacks were always in the app, socdk deploy --all -c environment=devcreated the prod registry as a side effect. - Reject an unknown
-c environment=value with an explicit error instead of synthesizing. - Give
prodits own stack names and its own physical names (-prodsuffix); leavedevunsuffixed. - Consequences:
- The three variables can no longer drift apart, and a dev deploy touches no prod resource.
- The naming is asymmetric.
devis unsuffixed because its stacks are already deployed under those names — renaming them would not move the resources, CloudFormation would create a second set and orphan the RDS instance, the bucket and the distribution. The ECR stacks keep their historicalEcrDevStack/EcrStacknames for the same reason, which inverts the convention (there the prod name is the plain one). - A prod deploy now creates a second set of infrastructure rather than overwriting dev,
including a second RDS instance and a second pair of NAT gateways. That is the point, but it is
not free — see
docs/deployment.md. - CI synthesizes both environments on every pull request (
synth-environmentsmatrix), so a prod regression surfaces while the pipeline still targets dev. This costs one extra job that has to wait for the frontend build, becauseFrontendStackreadsfrontend/distat synth time.