Application Workspace Architecture
Rule
For greenfield multi-crate applications, start each cohesive capability as one crate with private modules, extract crates only for meaningful dependency, contract, compilation, or ownership boundaries, keep crate dependencies acyclic, and let application crates assemble concrete implementations.
Activation
Apply when designing a new application workspace or deliberately restructuring an application with multiple business capabilities. Skip single-crate applications, standalone libraries, and maintenance work that does not include architecture changes.
Why
Crates enforce dependency and compilation boundaries but turn internal collaboration into cross-crate APIs. Modules preserve finer visibility and let cohesive code change together with less ceremony.
Do
- Treat components, adapters, applications, and narrow foundation capabilities as useful roles, not mandatory top-level directories.
- Begin a component as one crate, organize it with private modules, and expose only its intended surface.
- Extract an API crate when consumers need contracts without implementation dependencies, a separate contract prevents a bad dependency edge, or the contract is independently useful.
- Extract an adapter crate for a substantial external integration or an independently useful implementation.
- Add a facade crate only when several real component crates need one intended consumer entry point; applications still select external adapters and own process setup.
- Put contracts with the code that owns the abstraction: caller-facing services belong to the component surface, while outbound ports belong with the business logic that consumes them.
- Let components use another component’s intended public surface when the dependency graph remains acyclic; never reach into another component’s implementation internals.
- Keep business logic independent of database, HTTP, cloud SDK, and process setup. Adapters depend on the contracts they implement; applications select adapters and wire the system.
- Keep foundation crates lower-level than components and give each one a narrow responsibility; they do not depend on components, adapters, or applications. Domain types stay with the component that owns their meaning.
- Use component-prefixed crate names such as
accounts-apiandaccounts-postgres; names must remain clear without directory context. - Follow Cargo policy for workspace mechanics and module policy inside each crate.
Avoid
- Do not create
api,core, and facade crates for every component by default. - Do not create broad
common,shared,utils,helpers, orcommon-typescrates. - Do not extract crates merely for directory organization or speculative future reuse.
- Do not let a technology-only adapter crate become the coupling point for unrelated components.
- Do not use a facade crate only to re-export every implementation detail.
Example
Start with the smallest graph that expresses the real boundaries:
crates/
accounts/
accounts-postgres/
server/
Here A -> B means A depends on B:
server -> accounts
server -> accounts-postgres
accounts-postgres -> accounts
accounts owns its business logic and repository contract, accounts-postgres implements that contract, and server is the composition root. Split an accounts-api crate only when a real consumer or dependency boundary needs it.
Exceptions
- Preserve an existing workspace architecture unless changing it is part of the task.
- Let one infrastructure crate serve multiple components when the integration is cohesive and its shared ownership is intentional.
- Use a different crate taxonomy for tooling, test support, proc macros, protocols, migrations, or other roles that do not fit application components.