2.3: Multi-Root Workspaces & Project Dependencies
Key Takeaways
- Visual Studio Code multi-root workspaces (.code-workspace) allow developers to manage, edit, and compile multiple interdependent AL extension projects within a single unified IDE window.
- The AL Language extension resolves symbols across open workspace folders directly in memory before querying the .alpackages folder, providing real-time cross-project IntelliSense without manual symbol downloads.
- Extension dependencies are declared in app.json using id, name, publisher, and version, establishing a minimum version constraint (>= declared version) and determining build order.
- Circular dependencies between AL extensions are strictly prohibited and fail the build; they must be resolved by refactoring shared models and interfaces into a lower-tier Core extension.
- Workspace-level settings such as al.packageCachePath centralize symbol storage to a single shared directory, eliminating redundant multi-gigabyte symbol downloads across projects.
2.3: Multi-Root Workspaces & Project Dependencies
Enterprise Business Central solutions rarely consist of a single monolithic extension. Instead, professional development architectures partition complex business domains into modular, layered applications—such as core frameworks, domain-specific modules, country localizations, and integration connectors. The MB-820 exam tests your mastery of Visual Studio Code multi-root workspaces, project dependency resolution, circular reference elimination, and symbol cache optimization across multi-app architectures.
1. Multi-Root Workspace Architecture (.code-workspace)
A Visual Studio Code multi-root workspace enables developers to open and work on multiple distinct AL extension project folders simultaneously within a single IDE window. Each folder maintains its own independent app.json, launch.json, and source code tree, while sharing unified workspace settings.
{
"folders": [
{
"name": "01 - Contoso Core Library",
"path": "./ContosoCore"
},
{
"name": "02 - Contoso Logistics Engine",
"path": "./ContosoLogistics"
},
{
"name": "03 - Contoso Shipping Connector",
"path": "./ContosoShipping"
}
],
"settings": {
"al.packageCachePath": "./.shared-packages",
"al.enableCodeActions": true,
"al.incrementalBuild": true,
"al.codeAnalyzers": [
"${CodeCop}",
"${AppSourceCop}"
],
"al.ruleSetPath": "./ruleset.json"
}
}
In-Memory Abstract Syntax Tree (AST) Symbol Resolution
The primary benefit of multi-root workspaces is real-time in-memory symbol resolution:
- In a single-project setup, whenever an upstream dependency is modified, developers must recompile the upstream project (
Ctrl+Shift+B), copy its generated.apppackage into the dependent project's.alpackagesfolder, and reload the window. - In a multi-root workspace, the AL Language extension inspects all open workspace folders. When Project B depends on Project A, the language server constructs an in-memory Abstract Syntax Tree (AST) of Project A directly from its raw source code.
- Instant Cross-Project IntelliSense: If a developer adds a new public procedure or table field in
ContosoCore, that procedure or field is immediately visible inContosoLogisticsandContosoShippingvia IntelliSense without compiling, packaging, or downloading symbols.
2. Project Dependencies & Dependency Graphs
Upstream dependencies are formally declared within the dependencies array of the downstream project's app.json:
{
"id": "3d4e5f6a-7b8c-9d0e-1f2a-3b4c5d6e7f8a",
"name": "Contoso Logistics Engine",
"publisher": "Contoso",
"version": "2.0.0.0",
"dependencies": [
{
"id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"name": "Contoso Core Library",
"publisher": "Contoso",
"version": "1.5.0.0"
}
]
}
Key Dependency Rules for the MB-820 Exam
- Exact Identification: A dependency entry requires four exact attributes:
id(GUID),name,publisher, andversion. Theidis the primary matching key. - Minimum Version Semantics: The
versionspecified in the dependency declaration represents the minimum compatible version. The target runtime environment must have a version installed that is greater than or equal to (>=) the declared version (e.g.,1.5.0.0or higher). - Directed Acyclic Graph (DAG) Compilation Order: The AL compiler builds a dependency graph across all workspace folders. Compilation occurs strictly from the bottom up:
- Level 0: Foundation / Core libraries (no dependencies).
- Level 1: Domain modules (depend on Level 0).
- Level 2: Composite integrations / UI customizations (depend on Level 1 and Level 0).
- Breaking Changes Propagation: If a developer modifies a public method signature (e.g., adding a mandatory parameter) or removes a table field in a Level 0 project, all downstream projects will immediately display compiler errors in the Problems pane until updated.
3. Circular Dependencies & Decoupling Strategies
A circular dependency occurs when Extension A depends on Extension B, while Extension B simultaneously depends on Extension A (either directly or transitively through an intermediary Extension C).
FATAL ARCHITECTURAL DEFECT (Circular Dependency):
+-----------------------+ +-----------------------+
| Extension A | -----------> | Extension B |
| (Contoso Logistics) | <----------- | (Contoso Billing) |
+-----------------------+ +-----------------------+
REFACTORED CLEAN ARCHITECTURE (Common Kernel / Interface Pattern):
+-----------------------+ +-----------------------+
| Extension A | | Extension B |
| (Contoso Logistics) | | (Contoso Billing) |
+-----------------------+ +-----------------------+
\ /
\ /
v v
+------------------------------+
| Extension C |
| (Contoso Core Interfaces) |
| - Shared Tables & Enums |
| - Business Event Publishers |
+------------------------------+
Circular Dependency Detection
The AL compiler strictly forbids circular references. If a circular loop exists in the dependency graph, symbol resolution fails and the compiler reports that a circular dependency was detected between the listed extensions.
Architectural Remediation Patterns
When two modules require mutual interaction, developers must decouple them using one of two proven design patterns:
- Shared Kernel / Common Core Pattern:
- Extract the shared tables, enums, interfaces, and integration event publishers into a third foundational extension (e.g.,
Contoso Core Interfaces). - Configure both Extension A and Extension B to depend downward on the Core extension.
- Extract the shared tables, enums, interfaces, and integration event publishers into a third foundational extension (e.g.,
- Event-Driven Decoupling (Publish/Subscribe):
- Instead of Extension A directly invoking a codeunit in Extension B, Extension A declares and raises an Integration Event Publisher.
- Extension B subscribes to Extension A's event using
[EventSubscriber]. - Extension A remains completely unaware of Extension B, eliminating the dependency from A to B.
- Interface Polymorphism:
- Declare an AL
interfacein the Core extension. - Implement the interface within concrete codeunits in Extension A and Extension B.
- Use extensible enums to dynamically instantiate the desired implementation at runtime without direct project coupling.
- Declare an AL
4. Centralized Package Cache Optimization (al.packageCachePath)
In enterprise multi-app workspaces containing multiple projects, each project by default maintains its own local .alpackages folder. Downloading identical multi-gigabyte symbol files (Microsoft_Base Application.app, Microsoft_System.app) for 5 to 10 distinct folders wastes gigabytes of disk space and saturates network bandwidth.
{
"settings": {
"al.packageCachePath": "c:/AL/SharedSymbols"
}
}
Benefits of Centralized Package Caching:
- Single Download: When
AL: Download Symbolsis executed in any project, symbols are downloaded to the centralized folder and immediately shared across all workspace projects. - Disk Footprint Reduction: Reduces workspace disk consumption from ~15 GB down to ~3 GB.
- Fast CI/CD Builds: Build scripts can pre-populate the shared symbol directory once prior to executing parallel project builds.
- Unified Cache Invalidation: Executing
AL: Clear Object Cacheflushes the single shared cache, preventing subtle version drift between projects.
A developer opens a multi-root workspace in Visual Studio Code containing two AL projects: 'AppCore' and 'AppExtension'. 'AppExtension' declares a dependency on 'AppCore' in its app.json. The developer adds a new global procedure to a codeunit in 'AppCore'. Without running 'AL: Package' or downloading symbols, the developer types the procedure name inside 'AppExtension' and IntelliSense immediately suggests it. Why does this occur?
During a code review, an AL architect identifies that 'Extension Sales' declares a dependency on 'Extension Inventory', while 'Extension Inventory' simultaneously declares a dependency on 'Extension Sales' in its app.json. When compiling the workspace, the AL compiler reports a circular dependency between the two extensions. What architectural pattern must be implemented to resolve this error?
In an AL extension's app.json manifest, the dependencies array contains the following entry: "dependencies": [ { "id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "name": "Contoso Core", "publisher": "Contoso", "version": "3.2.0.0" } ] What requirement does this dependency specification impose during compilation and deployment?
An enterprise development team maintains a multi-root workspace containing 8 interrelated AL extensions. Each developer experiences slow build times and high disk usage because each project downloads redundant 2 GB Base Application symbol files into its local .alpackages folder. How should the team optimize this configuration in the .code-workspace file?