# 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](https://www.archunit.org/) 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.

1.  **Install the Salesforce CLI Plugin:**
    ```bash
    sf plugins install sf-archguard
    ```

2.  **Initialize Your Project Configuration:**
    Generate a starter `archguard.yml` configuration file in your project root:
    ```bash
    sf archguard init
    ```
    This command intelligently scans your project's `sfdx-project.json` and existing metadata to suggest initial layers and packages.

3.  **Enforce Your Architecture:**
    Run SF-ArchGuard to check for violations against your defined rules:
    ```bash
    sf archguard enforce
    ```
    Violations will be reported directly in your console. For CI/CD integration, you can generate JSON or JUnit reports (see [CI Integration](#ci-integration)).

### Example `archguard.yml` (Generated by `sf archguard init`):

```yaml
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 `service` layer package cannot depend on an `integration` layer 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: shared
```

**JSON 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:

```text
force-app/
  main/
    default/
      billing/
      payments/
      crm/
      notifications/
      core-domain/
      common/
```

Example intent for those folders:

-   `billing` contains invoice and receivables logic
-   `payments` contains payment gateway integrations
-   `crm` contains customer service workflows
-   `notifications` contains email, SMS, or platform event delivery
-   `core-domain` contains reusable business entities and domain logic
-   `common` contains 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()` instantiation
-   `instanceof` checks and cast expressions
-   SOQL `FROM Object__c` references
-   Trigger `on SObject` declarations

**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__c` patterns)

## 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**:
```bash
npm install -g sf-archguard
sf-archguard --help
```

**As a library** (for programmatic use — see [Programmatic API](#programmatic-api)):
```bash
npm install sf-archguard
```

## CI Integration

Use the JUnit output format to surface violations in any CI pipeline:

**GitHub Actions:**
```yaml
- 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 Violations
```

**Fail 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](./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](./CODE_OF_CONDUCT.md) to ensure a welcoming and inclusive environment for everyone.

## License

MIT