Skip to content

Dependencies

In real applications, containers often need to work together. Some containers depend on others to perform their tasks. For this, we use dependencies and optionalDependencies.

  • dependencies are containers that must be available for another container to start. If such a dependency is missing or not working, the container won’t start.

  • optionalDependencies are containers that can be used if available, but their absence won’t prevent the container from starting.

In this section, we’ll show how to connect containers using dependencies and optionalDependencies. You’ll see how one container can use data or functionality from another.

Analogy from Cooking

Imagine you are making a pizza.

  • You need dough, sauce, and cheese — without them, you can’t make a pizza. These are your required dependencies (dependencies).
  • But olives are an optional dependency (optionalDependencies). They add extra flavor, but if you don’t have them, you’ll still have a delicious pizza.

It’s the same with containers: some things are needed to make them work, and others just make them better.

Example

import { compose, createContainer } from '@grlt-hub/app-compose';
const dough = createContainer({
id: 'dough',
domain: 'ingredients',
start: () => ({ api: { value: 'dough' } }),
});
const sauce = createContainer({
id: 'sauce',
domain: 'ingredients',
start: () => ({ api: { value: 'sauce' } }),
});
const cheese = createContainer({
id: 'cheese',
domain: 'ingredients',
start: () => ({ api: { value: 'cheese' } }),
});
const olives = createContainer({
id: 'olives',
domain: 'ingredients',
start: () => ({ api: { value: 'olives' } }),
});
const pizza = createContainer({
id: 'pizza',
domain: 'dish',
// Required dependencies: pizza cannot be made without dough, sauce, and cheese
dependencies: [dough, sauce, cheese],
// Optional dependency: olives, but pizza can be made without them
optionalDependencies: [olives],
// Start function: combines all available ingredients into a pizza
start: (api) => {
const ingredients = Object.values(api).map((x) => x.value);
console.log(`${ingredients.join(' + ')} = pizza`);
return { api: { data: 'pizza' } };
},
});
const { up } = await compose({
stages: [['prepare', [pizza]]],
});
up();

When you run this code, you should see the following output in the console:

Terminal window
> npm start
dough + sauce + cheese = pizza

Try it

Why Olives Are Missing?

In the last example, olives are not in the pizza because they are optional. We did not add them, so the pizza has no olives.

But why did dough, sauce, and cheese work even though we didn’t add them directly?
That’s because they are required dependencies. When a container depends on others, compose.up automatically includes required dependencies, even if they are not specified.

Now let’s see how to include olives.

Including Olives

To include olives, you just need to add them to compose.up.

const { up } = await compose({
stages: [['prepare', [pizza]]],
stages: [['prepare', [pizza, olives]]],
});

When you run this code, you should see the following output in the console:

Terminal window
> npm start
olives + dough + sauce + cheese = pizza

Try it

What’s Next?

So far, we’ve learned how to connect containers using dependencies and optional dependencies. This makes sure containers start when they need other containers.

But sometimes this is not enough.
Imagine you’re making a pizza. You have dough and sauce, and you usually add cheese. But today, you look in the fridge and see — there is no cheese.

This is where enable helps. It lets you choose if a container should start, like checking if you have cheese before making the pizza.