sf-archguard
v0.3.0
Published
ArchUnit-style architecture enforcement for Salesforce SFDX projects
Maintainers
Readme
SF-ArchGuard: Architecture Enforcement for Salesforce SFDX Projects
The Problem: Uncontrolled Complexity in Salesforce Development
As Salesforce organizations grow, so does the complexity of their codebase. Multiple teams, diverse functionalities, and continuous development often lead to:
- Dependency Drift: Modules and packages unintentionally start relying on each other, breaking intended architectural boundaries.
- Slow, Risky Deployments: Tightly coupled components force larger deployments, increasing risk and slowing down release cycles.
- Eroding Maintainability: A lack of clear architectural guardrails makes it harder to understand, test, and evolve the application.
This results in a cycle of technical debt, hindering agility and team productivity.
The Solution: Enforce Architectural Rules with SF-ArchGuard
SF-ArchGuard provides architecture enforcement for Salesforce SFDX projects, similar to how ArchUnit works for Java. It helps you define clear architectural layers and package boundaries, then automatically catches illegal cross-package and cross-layer dependencies in your Apex classes, triggers, and custom object relationships.
Key Benefits:
- Preserve Architectural Integrity: Automatically detect and prevent unintended dependencies.
- Enable Faster, Safer Deployments: Maintain smaller, more independent deployment units.
- Boost Developer Productivity: Clearly defined boundaries reduce confusion and support multi-stream development.
- Automate Compliance: Integrate into your CI/CD pipeline to enforce rules continuously.
Quickstart: Get Started in 3 Steps
SF-ArchGuard is designed for seamless integration with your Salesforce DX workflow.
Install the Salesforce CLI Plugin:
sf plugins install sf-archguardInitialize Your Project Configuration: Generate a starter
archguard.ymlconfiguration file in your project root:sf archguard initThis command intelligently scans your project's
sfdx-project.jsonand existing metadata to suggest initial layers and packages.Enforce Your Architecture: Run SF-ArchGuard to check for violations against your defined rules:
sf archguard enforceViolations will be reported directly in your console. For CI/CD integration, you can generate JSON or JUnit reports (see CI Integration).
Example archguard.yml (Generated by sf archguard init):
layers:
- name: integration
dependsOn: [service, shared]
- name: service
dependsOn: [shared]
- name: shared
dependsOn: []
packages:
billing:
path: force-app/main/default/billing
layer: service
payments:
path: force-app/main/default/payments
layer: integration
dependsOn: [billing] # explicit cross-package exception
common:
path: force-app/main/default/common
layer: shared
rules:
enforcePackageBoundaries: true
enforceObjectBoundaries: true
exclude:
- "**/*Test.cls"
- "**/*Mock.cls"What SF-ArchGuard Checks
SF-ArchGuard focuses exclusively on structural and architectural rules. It does not duplicate PMD, ESLint, or other linting tools — there are no code style, complexity, or security checks here.
- Layer dependency direction — e.g., a
servicelayer package cannot depend on anintegrationlayer package if not explicitly allowed. - Package boundaries — Apex classes can only reference classes from the same package, an allowed layer, or an explicitly declared dependency.
- Object boundaries — Custom object lookups and master-detail relationships respect the same package/layer rules.
Violation Output
Every violation explains what broke and why it's disallowed. Run with --verbose to also get a concrete fix suggestion and the list of allowed targets.
Default output — one-line "why" under each violation:
✗ layer-dependency: 1 error (8 edges checked)
[ERROR] force-app/main/default/billing/objects/Invoice__c/Invoice__c.object-meta.xml
Invoice__c → Payment__c (billing → payments)
Layer "service" may only depend on: shared, or "service" itself (lateral).Verbose output (--verbose) — adds Fix: and Allowed targets::
✗ layer-dependency: 1 error (8 edges checked)
[ERROR] force-app/main/default/billing/objects/Invoice__c/Invoice__c.object-meta.xml
Layer violation: "Invoice__c" (billing, layer: service) depends on "Payment__c" (payments, layer: integration). Layer "service" is not allowed to depend on layer "integration".
billing/Invoice__c → payments/Payment__c
Why: Layer "service" may only depend on: shared, or "service" itself (lateral).
Fix: Either move "Payment__c" into an allowed layer, or add "integration" to layers[service].dependsOn in archguard.yml.
Allowed targets: sharedJSON and JUnit reports always include explanation, suggestion, and allowedAlternatives fields on every violation, so CI pipelines surface the full context without needing a flag.
When To Use It
SF-ArchGuard is most useful when a Salesforce codebase is split into multiple logical modules that should be developed and deployed separately, while still living in the same repository and SFDX project.
Typical cases:
- Large orgs with distinct domains such as billing, payments, CRM, or notifications.
- Teams trying to keep deployments fast by shipping only the affected module.
- Projects getting close to Salesforce deployment scale limits, including the 10,000-component limit for a single deployment.
- Repositories where architectural boundaries are defined up front, but tend to erode over time during normal feature work.
Without automated boundary checks, one module can quietly start depending on another in ways that make separate deployment harder. That usually shows up later as slow deployments, unexpected metadata coupling, or an inability to deploy a large package cleanly because too many components now need to move together.
Example Project Structure
One common setup is a single repo with subfolders representing modules that should remain independently deployable:
force-app/
main/
default/
billing/
payments/
crm/
notifications/
core-domain/
common/Example intent for those folders:
billingcontains invoice and receivables logicpaymentscontains payment gateway integrationscrmcontains customer service workflowsnotificationscontains email, SMS, or platform event deliverycore-domaincontains reusable business entities and domain logiccommoncontains shared technical utilities
In that structure, payments may be allowed to depend on billing, and both may use common, but common should not depend on payments. A CRM module also should not reach into billing internals just because both folders sit under the same SFDX source tree.
SF-ArchGuard makes those boundaries executable during development, before dependency drift turns into deployment coupling.
When the codebase is split well and those boundaries are actively governed, teams usually get practical delivery benefits as well:
- Easier packaging and clearer ownership of what belongs in each package.
- Easier multi-stream development, because teams can work in parallel with less accidental overlap.
- More scratch-org-friendly development, since smaller and cleaner module scopes are easier to push and validate.
- More deployment-friendly release flows, because the deployable unit stays smaller and more predictable.
How Dependency Rules Work
Dependencies are allowed based on two mechanisms working together:
Layer rules define the general direction. A layer can only depend on layers listed in its dependsOn. Same-layer references are always allowed (lateral dependencies).
integration --> service --> domain --> shared
\ \
`-> shared `-> shared `-> (nothing else)Package-level dependsOn adds specific exceptions. If payments declares dependsOn: [billing], then classes in the payments package can reference billing classes regardless of layer rules.
Same-package references are always allowed — classes within a single package can freely reference each other.
This is especially useful when packages map to deployment units. Keeping dependencies limited to approved layers and explicitly declared package links helps preserve smaller deployment scopes and reduces the risk that unrelated modules must be deployed together.
What Gets Analyzed
Apex classes and triggers — the parser detects:
extends/implements(inheritance)- Type usage in declarations (
BillingService svc = ...) - Generic type parameters (
List<InvoiceWrapper>) - Static method calls (
BillingService.createInvoice()) new ClassName()instantiationinstanceofchecks and cast expressions- SOQL
FROM Object__creferences - Trigger
on SObjectdeclarations
Custom objects and fields — the parser reads .object-meta.xml and .field-meta.xml to detect:
- Lookup relationships (
referenceTo) - Master-Detail relationships (
referenceTo) - Formula field object references (
Object__c.Field__cpatterns)
Command Reference
sf archguard enforce
| Flag | Short | Description | Default |
|---|---|---|---|
| --project-dir <path> | -p | SFDX project root directory | Current directory |
| --config <path> | -c | Path to archguard.yml | Auto-detected in project root |
| --format <format> | -f | Output: console, json, or junit | console |
| --output <path> | -o | Write report to file (json/junit) | stdout |
| --verbose | -v | Add Fix: suggestion and Allowed targets: list to each violation | false |
| --[no-]fail-on-violation | | Exit code 1 on violations | true |
Other Installation Options
As a standalone global CLI:
npm install -g sf-archguard
sf-archguard --helpAs a library (for programmatic use — see Programmatic API):
npm install sf-archguardCI Integration
Use the JUnit output format to surface violations in any CI pipeline:
GitHub Actions:
- name: Enforce architecture
run: sf archguard enforce --format junit --output archguard-report.xml
- name: Publish test results
uses: mikepenz/action-junit-report@v4
if: always()
with:
report_paths: archguard-report.xml
check_name: Architecture ViolationsFail the build on violations (default --fail-on-violation is true; disable with --no-fail-on-violation for report-only mode).
Coming Soon: A dedicated public GitHub Action is in development. It will provide a turnkey setup without needing to write bash scripts, and will natively annotate your pull requests with architectural violations inline!
What This Tool Does NOT Do
SF-ArchGuard is intentionally narrow in scope. The following are handled by other tools and should not be duplicated here:
- Code style and formatting (use Prettier, PMD)
- Cyclomatic complexity and method length (use PMD)
- Security scanning (use PMD security rules, Checkmarx)
- SOQL injection detection (use PMD)
- Naming conventions (use PMD)
- Governor limit analysis (use Salesforce Scanner)
Community and Contributing
We welcome contributions from the community! Whether it's reporting a bug, proposing a feature, or submitting a pull request, your input helps make sf-archguard better.
- Report Bugs & Request Features: Please use our GitHub Issue Tracker. We have templates for bug reports and feature requests to make it easier for you to provide the necessary information.
- Contribute Code: Read our CONTRIBUTING.md guide for details on our development process and the steps for submitting pull requests.
- Code of Conduct: Please review and adhere to our Code of Conduct to ensure a welcoming and inclusive environment for everyone.
License
MIT
