Skip to content

Manage Dependencies

In this guide, we’ll look at how to manage dependencies between containers using the dependsOn and optionalDependsOn options. Defining dependencies allows you to control the order in which containers start, ensuring that necessary data or functionality is available when each container initializes.

We’ll explore:

  • dependsOn: Strict dependencies that must be initialized before the container starts.
  • optionalDependsOn: Flexible dependencies that are used if available but are not required.
  • how to access dependencies

By configuring these dependencies, you can create modular and interdependent components that execute in the correct order and adapt based on available data.

Strict Dependency (dependsOn)

When a container has a strict dependency defined by dependsOn, it relies on the successful initialization of that dependency to start. Here’s how dependsOn affects container statuses:

  • If the dependency is done: The container will proceed to start, as all strict dependencies have completed successfully.
  • If the dependency is fail: If a container’s dependency is in the fail status, the dependent container will also fail. This strict relationship ensures that any error in a required dependency is propagated to dependent containers.
    • Example: If Container A depends on Container B via dependsOn and Container B has a fail status, then Container A will also be marked as fail.
  • If the dependency is off: If the dependency is in the off status (disabled), then the dependent container will also be marked as off. This prevents the container from running if a required dependency is explicitly turned off.
    • Example: If Container C depends on Container D via dependsOn and Container D is off, then Container C will also be off.

Optional Dependency (optionalDependsOn)

The optionalDependsOn option allows you to define dependencies that are not strictly required for the container to start. This means the container will attempt to start regardless of the final status of these dependencies. Here’s how optionalDependsOn affects container statuses:

  • If the dependency is done: The container will start and use any data or resources provided by the optionalDependsOn dependency.
    • Example: If Container E optionally depends on Container F, and Container F is done, then Container E will start and can access Container F’s data.
  • If the dependency is fail: The container will still attempt to start even if the optional dependency fails. This flexibility allows the container to function independently without failing due to non-critical errors.
    • Example: If Container G optionally depends on Container H, and Container H has a fail status, then Container G will still try to start without data from Container H.
  • If the dependency is off: Similar to a fail status, the container will still attempt to start without the optional dependency. This enables the container to run without being affected by dependencies that are explicitly turned off.
    • Example: If Container I optionally depends on Container J, and Container J is off, then Container I will start as usual.

Using optionalDependsOn provides flexibility, allowing containers to run even when some dependencies are unavailable. This approach is useful for non-critical dependencies, where the container can perform its primary function regardless of the state of these optional dependencies.

Accessing Dependencies

type StartFn = (dependsOn, optionalDependsOn) => Promise<StartResult> | StartResult;
type EnableFn = (dependsOn, optionalDependsOn) => Promise<boolean> | boolean;

In both the start and enable functions, you can access dependencies and optional dependencies through the function parameters:

  • Strict Dependencies (dependsOn) are accessible as the first parameter in start and enable. This provides the APIs of all required dependencies that have been resolved.
  • Optional Dependencies (optionalDependsOn) are available as the second parameter in start and enable. These are accessible if the optional dependencies are resolved, but the container can still start if they are unavailable.

By using these parameters, you can conditionally access dependency data as needed, allowing your containers to respond flexibly to the availability of required and optional dependencies.

Examples

Example 1: Using dependsOn for Strict Dependencies

In this example, we set up two containers, where one (accountContainer) depends on the other (userContainer). The accountContainer will not start until the userContainer has successfully completed.

import { createContainer, compose } from '@grlt-hub/app-compose';
const userContainer = createContainer({
id: 'user',
start: () => {
console.log('User data loaded');
return { api: { userId: 'user123' } };
},
});
const accountContainer = createContainer({
id: 'account',
dependsOn: [userContainer],
start: (depsApi) => {
console.log('Account loaded for user:', depsApi.user.userId);
return { api: {} };
},
});
await compose.up([userContainer, accountContainer]);

Expected Result

In this example, accountContainer can access data from userContainer, as it waits for userContainer to complete:

Terminal window
User data loaded
Account loaded for user: user123

Example 2: Using optionalDependsOn for Flexible Dependencies

In this example, notificationContainer has an optional dependency on userContainer. If userContainer completes successfully, notificationContainer can access user data. If userContainer does not complete or is turned off, notificationContainer will still start but without user data.

const notificationContainer = createContainer({
id: 'notification',
optionalDependsOn: [userContainer],
start: (_, optionalDepsApi) => {
if (optionalDepsApi.user) {
console.log('Notifications loaded for user:', optionalDepsApi.user.userId);
} else {
console.log('Notifications loaded without user context');
}
return { api: {} };
},
});
await compose.up([userContainer, notificationContainer]);

Expected Result

If userContainer completes successfully, notificationContainer will access userId. If userContainer is unavailable, notificationContainer will start independently:

Terminal window
User data loaded
Notifications loaded for user: user123

or, if userContainer is unavailable:

Terminal window
Notifications loaded without user context

Example 3: Using Both dependsOn and optionalDependsOn

In this example, dashboardContainer depends on settingsContainer as a strict dependency and on userContainer as an optional dependency. It will only start after settingsContainer completes but can use userContainer if available.

const settingsContainer = createContainer({
id: 'settings',
start: () => {
console.log('Settings loaded');
return { api: { theme: 'dark' } };
},
});
const dashboardContainer = createContainer({
id: 'dashboard',
dependsOn: [settingsContainer],
optionalDependsOn: [userContainer],
start: (depsApi, optionalDepsApi) => {
console.log('Dashboard started with settings:', depsApi.settings.theme);
if (optionalDepsApi.user) {
console.log('Dashboard has user context:', optionalDepsApi.user.userId);
}
return { api: {} };
},
});
await compose.up([settingsContainer, userContainer, dashboardContainer]);

Expected Result

If both settingsContainer and userContainer complete successfully, dashboardContainer will receive data from both:

Terminal window
Settings loaded
User data loaded
Dashboard started with settings: dark
Dashboard has user context: user123

If userContainer is unavailable, dashboardContainer will still start with settingsContainer:

Terminal window
Settings loaded
Dashboard started with settings: dark

Summary

In this guide, we explored how to use dependsOn and optionalDependsOn to manage container dependencies. Key takeaways include:

  • Strict Dependencies (dependsOn): Containers with strict dependencies only start once all required dependencies have reached a done status. If any strict dependency is off, the container will also be set to off. If a strict dependency fails, the container will inherit the fail status.
  • Optional Dependencies (optionalDependsOn): Containers with optional dependencies attempt to start regardless of the status of those dependencies. If optional dependencies are available, the container can access their data; if not, it will still function independently.
  • Accessing Dependencies: In start and enable, dependencies and optional dependencies are accessible as the first and second parameters, respectively. This allows containers to flexibly retrieve data based on both strict and optional dependencies.

By configuring strict and optional dependencies, you can control the order and conditions under which containers start, creating modular and interdependent components. This setup ensures that containers have access to the necessary resources and data, improving both flexibility and reliability.