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?
The developer previously published AppCore to the cloud sandbox, which updated the tenant symbol cache in the background.
Visual Studio Code automatically runs a background Git synchronization to merge the abstract syntax trees.
The AL Language extension resolves symbols for workspace dependencies directly from open workspace project folders in memory before checking .alpackages.
The compiler uses .NET reflection to query the compiled DLLs in the temporary local app data directory.
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?
Add the 'AllowCircularReferences' flag to the features array in both app.json manifests.
Set schemaUpdateMode to 'Recreate' in launch.json to bypass dependency ordering checks during build.
Merge both extensions into a single monolithic Per-Tenant Extension to avoid multi-root compilation.
Extract the shared data structures, interfaces, and event publishers into a separate foundational Core extension that both extensions depend on.
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?
The target environment must have Contoso Core installed with exactly version 3.2.0.0; any minor update (e.g., 3.2.1.0) will cause a runtime exception.
The target environment must have Contoso Core installed with version 3.2.0.0 or higher within the compatible major version branch.
Contoso Core must be compiled using runtime version 3.2.
The dependent extension can only access objects in Contoso Core that have ID numbers greater than 32000.
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?
Configure "al.packageCachePath": "./.shared-packages" in the workspace-level settings object to share a single symbol cache across all projects.
Add "includeSourceInSymbolFile": false to every project's app.json to prevent symbol files from downloading.
Delete the .alpackages folder in all 8 projects and rely solely on in-memory AST resolution without downloading symbols.
Set "target": "OnPrem" in all projects so that symbols are loaded directly from the Windows GAC.
Sections you finish are checked off in the contents.