Architecture Decision Records for the CloudDesk multi-tenant SaaS backend.
| Field | Value |
|---|---|
| Project | CloudDesk Multi-Tenant SaaS Backend |
| Environment | dev |
| AWS Region | us-east-1 |
| Infrastructure as Code | AWS SAM |
| Runtime | Python 3.13 |
| Database | Amazon RDS for PostgreSQL |
| Status | Active |
This document records the major engineering decisions made during CloudDesk.
The goal is not only to document what was chosen, but also:
- why it was chosen;
- which alternatives were considered;
- what trade-offs were accepted;
- what conditions would justify revisiting the decision.
Each decision follows this structure:
- Status
- Context
- Decision
- Rationale
- Alternatives considered
- Consequences
- Revisit when
Accepted.
CloudDesk must support multiple customer organizations while using one backend platform.
The system must allow:
- one user to belong to multiple tenants;
- one tenant to contain multiple users;
- different roles per tenant;
- strict prevention of cross-tenant access.
Use a shared application and shared PostgreSQL database with logical tenant isolation enforced through the tenant_users membership model and application authorization.
This is the simplest architecture that satisfies the current business requirements.
It avoids creating separate infrastructure or databases for every tenant while still allowing CloudDesk to enforce tenant-specific access.
Rejected because it would:
- increase provisioning complexity;
- increase cost;
- complicate migrations;
- complicate monitoring;
- be unnecessary for the current scale.
Rejected because it would:
- complicate query management;
- increase migration complexity;
- add operational overhead without solving a demonstrated requirement.
Positive:
- lower infrastructure cost;
- simpler deployment;
- easier centralized reporting;
- simpler schema management.
Negative:
- tenant isolation depends on consistent authorization and query discipline;
- a coding error could create a cross-tenant exposure risk.
- regulatory requirements demand stronger physical separation;
- enterprise tenants require dedicated infrastructure;
- tenant size or performance isolation becomes a problem;
- contractual requirements demand separate databases.
Accepted.
CloudDesk must store:
- users;
- tenants;
- many-to-many memberships;
- tenant roles;
- membership status;
- timestamps;
- transactional tenant creation.
Use Amazon RDS for PostgreSQL.
PostgreSQL is a strong fit because CloudDesk depends on:
- relational joins;
- transactions;
- foreign keys;
- uniqueness constraints;
- many-to-many relationships;
- role and membership queries;
- strong consistency.
Tenant creation and owner membership must be committed together.
Rejected because the current data model is relational and transaction-heavy.
DynamoDB could support the workload, but it would require more complex access-pattern design and denormalization without a demonstrated scaling need.
Rejected for the current stage because:
- the workload does not justify its additional cost and operational scope;
- standard RDS PostgreSQL satisfies the requirements.
Positive:
- strong relational integrity;
- simple membership queries;
- transactional operations;
- mature SQL ecosystem.
Negative:
- database connections become the primary scaling constraint;
- RDS introduces continuous baseline cost;
- Lambda concurrency must be monitored against connection capacity.
- workload growth justifies Aurora;
- read scale requires replicas;
- availability requirements justify Multi-AZ changes;
- connection pressure requires RDS Proxy.
Accepted.
Amazon Cognito manages authentication, but CloudDesk also needs application-specific user data.
Store Cognito identities in Cognito and maintain a separate users record in PostgreSQL.
The Cognito subject maps the identity to the CloudDesk user.
Authentication and application data serve different purposes.
Cognito should manage:
- credentials;
- account confirmation;
- token issuance;
- identity claims.
PostgreSQL should manage:
- application user ID;
- tenant membership;
- tenant roles;
- user status;
- application profile fields.
Rejected because Cognito is not a relational application database and does not model tenant memberships well.
Rejected because it would mix provisioning with request handling and create more runtime complexity.
Positive:
- clear separation of responsibilities;
- relational application data remains in PostgreSQL;
- Cognito can be replaced more easily in the future.
Negative:
- identity synchronization must be handled;
- provisioning failures can leave a confirmed Cognito user without an application record.
- a different identity provider is introduced;
- federation becomes a requirement;
- provisioning needs compensation or retry workflows.
Accepted.
CloudDesk requires every confirmed Cognito user to have a PostgreSQL application-user record.
Use the Cognito Post Confirmation trigger to invoke a user-provisioning Lambda.
The event occurs at the correct lifecycle point: after the user confirms the account.
It avoids performing synchronization during every protected API request.
Rejected because:
- it mixes reads with provisioning;
- it introduces additional runtime branching;
- it makes user-state behavior less predictable.
Rejected because:
- it delays user availability;
- it adds unnecessary operational complexity.
Positive:
- user records are created early;
- API request flow remains simpler;
- provisioning is event-driven.
Negative:
- Post Confirmation failures must be investigated;
- retry and compensation behavior is limited.
- asynchronous retry is required;
- external user sources are introduced;
- invitation flows need more complex provisioning.
Accepted.
CloudDesk needs a public HTTPS API with JWT authorization and Lambda integration.
Use Amazon API Gateway HTTP API instead of REST API.
HTTP API provides:
- Lambda integrations;
- JWT authorizers;
- lower operational complexity;
- lower cost than REST API;
- sufficient routing for the current project.
Rejected because the project does not currently require:
- usage plans;
- advanced request transformations;
- API keys;
- REST API-specific integrations.
Rejected because the application is Lambda-first and HTTP API provides a better fit.
Positive:
- simpler API layer;
- lower cost;
- native JWT authorizer.
Negative:
- fewer advanced API-management capabilities.
- usage plans are required;
- complex request transformation is needed;
- API key management becomes a requirement.
Accepted.
CloudDesk consists of event-driven API operations with variable demand.
Implement each API operation as a focused AWS Lambda function.
Lambda provides:
- automatic scaling;
- no server management;
- usage-based compute;
- direct integration with API Gateway;
- strong fit for stateless request handlers.
Rejected because it would require server management, patching, scaling, and continuous compute cost.
Rejected because the application does not require long-running containers.
Rejected because it would add major operational complexity without solving a current problem.
Positive:
- low operational overhead;
- independent handlers;
- automatic scaling;
- straightforward SAM deployment.
Negative:
- cold starts;
- database connection pressure;
- distributed logs;
- runtime package constraints.
- long-running workloads appear;
- persistent connections are required;
- workload economics favor containers;
- Lambda limits become restrictive.
Accepted.
CloudDesk exposes multiple operations across users, tenants, and memberships.
Use focused Lambda functions such as:
create_tenant;list_tenants;get_tenant;add_member;update_member;remove_member.
This keeps handlers:
- small;
- independently deployable;
- easier to test;
- aligned with least-privilege IAM;
- easy to troubleshoot.
Rejected because it would:
- centralize too much logic;
- make testing and permissions broader;
- increase deployment blast radius.
Positive:
- clearer responsibility;
- smaller handler scope;
- simpler logs and alarms per function.
Negative:
- more functions to deploy and monitor;
- repeated configuration in the SAM template.
- function count becomes difficult to manage;
- route groups share significant runtime behavior;
- a framework-based Lambda monolith provides measurable value.
Accepted.
Multiple Lambda functions need the same authentication, authorization, database, secret, response, serialization, and observability logic.
Store reusable application modules in:
backend/layers/shared/python/shared/
and third-party dependencies directly under:
backend/layers/shared/python/
The layer reduces duplication and centralizes security-sensitive behavior.
Rejected because it would create duplication and inconsistent behavior.
Rejected because the project would have repeated dependencies and larger artifacts.
Positive:
- centralized logic;
- easier security fixes;
- consistent responses and authorization;
- smaller function folders.
Negative:
- all functions depend on layer compatibility;
- local Windows testing must avoid importing Linux binaries before local packages;
- layer versioning must be managed.
- deployment coupling becomes a problem;
- functions require conflicting dependency versions;
- packaging tools provide a better approach.
Accepted.
Every tenant-scoped handler must enforce consistent membership and role rules.
Use reusable helpers:
require_membership()
require_admin()
require_owner()Authorization logic is security-critical and should not be duplicated across handlers.
Rejected because duplicated checks can drift and create vulnerabilities.
Rejected because tenant roles are application data, not AWS identities.
Positive:
- consistent access rules;
- easier tests;
- easier reviews;
- simpler handlers.
Negative:
- authorization helpers become a critical shared dependency.
- policy complexity justifies a policy engine;
- fine-grained resource permissions expand significantly.
Accepted.
CloudDesk uses a shared PostgreSQL schema.
Enforce tenant isolation by verifying the current user's active membership before tenant operations.
The current project scale and complexity do not justify a separate policy engine or PostgreSQL row-level security.
Deferred because:
- it adds database-policy complexity;
- the project already enforces roles in the application;
- it is not required for the current milestone.
Positive:
- clear application behavior;
- easier handler-level testing.
Negative:
- every query must remain tenant-aware;
- application mistakes remain a risk.
- defense-in-depth requirements increase;
- the query layer expands;
- a production security review recommends database-enforced isolation.
Accepted.
CloudDesk should preserve membership history.
Set membership status to inactive rather than deleting the row.
Soft deletion supports:
- audit history;
- recovery;
- accidental-deletion protection;
- future reactivation.
Rejected because it permanently removes useful membership history.
Positive:
- historical data preserved;
- easier future auditing.
Negative:
- queries must filter by active status;
- reactivation behavior must eventually be defined.
- legal retention rules require physical deletion;
- data lifecycle policies are introduced.
Accepted.
Removing or demoting the only tenant owner would leave the tenant without administrative control.
The standard membership API cannot:
- assign
owner; - demote the current owner;
- remove the current owner;
- allow owner self-removal.
Ownership changes require a dedicated, transactional workflow.
Rejected because it could create ownerless tenants or ambiguous authority.
Positive:
- protects tenant continuity;
- prevents accidental lockout.
Negative:
- ownership transfer is not currently supported.
- a dedicated ownership-transfer workflow is implemented.
Accepted.
Lambda requires PostgreSQL credentials.
Store credentials in AWS Secrets Manager and pass only the secret ARN to the application.
This avoids hardcoded credentials and supports future rotation.
Rejected because credentials would be directly visible in configuration and deployment history.
Not selected because Secrets Manager is purpose-built for secrets and future rotation.
Positive:
- no database password in source control;
- centralized secret management;
- future rotation support.
Negative:
- recurring cost;
- runtime dependency;
- caching and rotation behavior must be considered.
- rotation is enabled;
- secret architecture changes;
- organization-wide secret-management standards are introduced.
Accepted.
VPC-connected Lambda functions need to retrieve database credentials without public internet access.
Use a Secrets Manager interface endpoint.
This provides private service access without adding a NAT Gateway solely for secret retrieval.
Rejected because it would add a larger recurring cost and broader outbound connectivity.
Rejected because the Lambda functions are designed for private dependency access.
Positive:
- private secret retrieval;
- avoids NAT Gateway;
- explicit endpoint security group.
Negative:
- interface endpoint has recurring cost;
- additional network resources.
- multiple private workloads require broad outbound internet access;
- a centralized egress design is introduced.
Accepted.
CloudDesk is primarily a serverless AWS application.
Use AWS SAM and CloudFormation for Infrastructure as Code.
SAM provides direct support for:
- Lambda;
- API Gateway;
- events;
- layers;
- IAM policies;
- CloudFormation outputs.
Not added because maintaining the same application in two IaC tools would create unnecessary complexity.
Rejected because it is not repeatable and does not support reliable CI/CD.
Positive:
- serverless-native templates;
- CloudFormation rollback;
- repeatable deployment;
- GitHub Actions integration.
Negative:
- CloudFormation errors can be verbose;
- some existing resource relationships require careful parameterization.
- the portfolio needs a Terraform-specific project;
- CloudDesk expands beyond SAM's comfortable scope;
- organization standards require Terraform.
Accepted.
CloudDesk requires automated quality checks and deployment.
Use:
.github/workflows/ci.yml
.github/workflows/deploy.yml
GitHub Actions integrates directly with the repository and supports OIDC authentication to AWS.
Positive:
- automated validation;
- deployment only after successful CI;
- visible workflow history;
- no separate CI platform.
Negative:
- workflow permissions and triggers must be maintained;
- deployment depends on GitHub availability.
- organization-wide CI moves to another platform;
- deployment requirements require a specialized release system.
Accepted.
CI/CD requires AWS credentials.
Use GitHub OIDC and AWS STS AssumeRoleWithWebIdentity.
OIDC provides short-lived credentials and eliminates long-lived AWS keys in GitHub.
Rejected because static credentials create a larger security risk and require rotation.
Positive:
- no long-lived AWS keys;
- trust restricted by repository and branch;
- temporary credentials.
Negative:
- trust policies are sensitive to exact OIDC subject claims;
- immutable GitHub subject configuration required troubleshooting.
- GitHub identity configuration changes;
- separate deployment roles are created for staging and production.
Accepted.
CloudDesk must not deploy code that has not passed validation.
Use one workflow for CI and another triggered after successful CI on main.
This creates a clear gate between validation and deployment.
The deployment workflow checks out the exact commit SHA validated by CI.
Positive:
- deployment is blocked by test or build failures;
- exact validated commit is deployed;
- responsibilities remain clear.
Negative:
- workflow chaining adds configuration complexity.
- release environments require approvals;
- reusable workflows simplify the design.
Accepted.
The project needs automated code quality and testing.
Use:
- Black for formatting;
- isort for import ordering;
- Ruff for linting;
- pytest for tests;
- pytest-cov for coverage.
These tools provide fast, widely understood Python quality gates.
Positive:
- consistent code;
- fewer review debates;
- fast feedback;
- measurable coverage.
Negative:
- tooling configuration must exclude vendored layer dependencies.
Accepted.
CloudDesk needs logs, metrics, alarms, notifications, and dashboards.
Use CloudWatch and SNS.
CloudDesk is AWS-native, and CloudWatch already provides the required service metrics.
Rejected because they would add infrastructure and maintenance without solving a current monitoring gap.
Rejected because the project does not require its additional features or cost.
Positive:
- native integration;
- no additional platform;
- simple deployment.
Negative:
- dashboard flexibility is more limited than specialized platforms;
- custom application metrics are not yet implemented.
- multi-cloud monitoring is required;
- advanced visualization becomes necessary;
- observability requirements exceed CloudWatch.
Accepted.
Lambda automatically created some log groups before CloudFormation attempted to manage them.
This caused AlreadyExists failures.
Deploy the stack first, then apply 30-day retention to existing CloudDesk Lambda log groups in the deployment workflow.
This avoids conflicts with automatically created log groups.
Rejected for the current stack because existing groups caused deployment failure.
Positive:
- deployment succeeds;
- logs do not remain indefinitely.
Negative:
- never-invoked functions may not yet have log groups;
- retention may require later reconciliation.
- all function log groups can be managed predictably;
- a dedicated retention-reconciliation job is introduced.
Accepted.
Tenant and membership mutations are high-value operational events.
Instrument:
- tenant creation;
- member addition;
- role update;
- member removal.
These operations are important for troubleshooting and future auditing.
Positive:
- better incident investigation;
- request and tenant context;
- consistent operation outcomes.
Negative:
- not every handler is instrumented yet;
- logs are not a complete audit store.
- all handlers require instrumentation;
- a dedicated audit-event system is introduced.
Accepted.
Lambda concurrency can create database connection pressure.
Use direct Lambda-to-RDS connections with connection reuse for the current workload.
There is no demonstrated connection-exhaustion problem yet.
Deferred because it adds cost and infrastructure complexity.
Positive:
- simpler architecture;
- lower cost.
Negative:
- direct connections remain a scaling risk.
- concurrency grows;
- connection exhaustion appears;
- failover connection handling needs improvement.
Accepted.
The project goal is to solve the current backend requirements without overengineering.
Do not add Docker, ECS, EKS, or Kubernetes.
The workload is well suited to Lambda.
Adding containers would not improve the current architecture.
- long-running processes appear;
- workload packaging requires containers;
- runtime limits become a problem.
Accepted with restriction.
The team needs a way to verify Lambda, Secrets Manager, VPC networking, and PostgreSQL together.
Keep /database-test during development.
It provides a direct deployment-verification signal.
Positive:
- fast networking and database diagnostics.
Negative:
- it may expose unnecessary database metadata;
- it is not suitable as a public production endpoint.
- production hardening begins;
- a safer internal health-check design is implemented.
Accepted.
Authenticated API responses should reduce browser-related exposure and caching.
Add:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Cache-Control: no-store
These headers are low-cost defensive controls.
Positive:
- prevents content sniffing;
- prevents framing;
- reduces referrer leakage;
- prevents response caching.
Negative:
- request-ID propagation is still incomplete.
Deferred.
CloudDesk is a development portfolio environment.
Do not add WAF or route-specific throttling during the current milestone.
The project does not yet have public production traffic or a measured abuse problem.
Positive:
- avoids unnecessary cost and configuration.
Negative:
- public production hardening remains incomplete.
- public traffic is introduced;
- threat modeling identifies abuse risks;
- production launch begins.
Accepted temporarily.
The initial schema must be applied to PostgreSQL.
Run the initial SQL migration manually from an environment with database access.
The project has one initial migration and does not yet require a migration orchestration system.
Positive:
- simple;
- transparent;
- no additional tool.
Negative:
- not ideal for multiple environments;
- no automated rollback;
- deployment and schema changes are separate.
- additional migrations are added;
- staging and production environments exist;
- deployment approvals and rollback procedures are defined.
Accepted.
CloudDesk is a portfolio project under active development.
Deploy the current implementation as dev in us-east-1.
This avoids the cost and complexity of duplicate environments before the application is stable.
Positive:
- lower cost;
- simpler learning environment.
Negative:
- does not demonstrate full environment separation;
- production release controls are not present.
- staging tests are required;
- production launch is considered;
- environment-specific IAM and data separation are implemented.
CloudDesk deliberately prioritizes:
- secure defaults;
- managed AWS services;
- clear tenant isolation;
- automation;
- maintainability;
- operational visibility;
- cost-conscious simplicity.
The project intentionally avoids technologies that do not solve a current problem.
The architecture should evolve only when new requirements, measurements, or risks justify the change.