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:
> npm startdough + 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:
> npm startolives + 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.