Service Dependencies
Understand service dependencies, their impact, and configuration.
This document will cover environment.{defaultServices,optionalServices} and service.requires, their differences, impact scope, and usage.
environment.{defaultServices,optionalServices}
Impact scope
| Scope | Impact |
|---|---|
| Service repo* | ✅ |
| Outside repo* | ❌ |
| dev-0* | ❌ |
This represents the default environment that will be created by lifecycle when a pull request is opened in the service repo* and does not have any impact on outside repos, dev-0, or any other static environments that use this service.
services.requires
Impact scope
| Scope | Impact |
|---|---|
| Service repo* | ✅ |
| Outside repo* | ✅ |
| dev-0* | ✅ |
services.requires has an impact across the board; hence, it is important to understand how it works and when we should use them.
Please read the info blocks below carefully.
You can think of services.requires as a hard dependency definition. For example, if you have an API service and a database, the API service will have a hard dependency on the database.
In this scenario, the database should not be defined as the default service. Instead, we should make the dependency explicitly clear by adding the database to the API’s requires block.
By doing this, we ensure that any outside repo that wants to use our API service will get the database along with it but only needs to specify the API service in their defaultServices or optionalServices.
Only services defined in lifecycle.yaml should be used in the requires
array. If a service is defined in an outside repo, use
environment.defaultServices instead.
Do not use services in the services.requires if the service itself is not
defined in the same lifecycle.yaml.
Services defined in the requires block will only be resolved 1 level down.
This is a very important nuance, which we get tripped by regularly.
Examples
To better illustrate the above statement, consider this example.
Repository A r-A has 3 services s-A, s-B, and s-C.
s-Arequiress-B.s-Brequiress-C.
As you can see, s-A has an indirect dependency on s-C through s-B.
Scenario 1: Pull Request in Service repo* ✅
When we open a pull request in r-A repo, lifecycle will deploy 3 services: s-A, s-B, and s-C.
Breakdown
- Lifecycle deploys
s-Aands-Bbecause they are defined indefaultServices. - Services defined in the
requiresblock will only be resolved one level down. - Only services defined in
lifecycle.yamlshould be used in therequiresarray. If a service is defined in an outside repo, useenvironment.defaultServicesinstead.
Scenario 2: ❌
Repository B r-B has service s-X and also defines an outside repo r-A service s-A as environment.defaultServices.
Breakdown
- Lifecycle deploys
s-Xands-Abecause they are defined indefaultServices. - Lifecycle deploys
s-Bbecause it is a 1st level dependency of a service (s-A) listed indefaultServices. - Lifecycle does not deploy
s-Csince it is not a 1st level dependency of any service listed indefaultServicesoroptionalServices.
The way this scenario manifests is lifecycle will deploy s-X, s-A, and s-B, but the build will likely fail because s-B is missing a required dependency s-C.
Solutions
There are 2 ways to address this depending on your use case.
Solution 1
Add s-B to r-B’s environment.defaultServices block in r-B.lifecycle.yaml. In effect, this will make s-C a first-level dependency.
Solution 2
Add s-C to the services.requires block of r-A in r-A.lifecycle.yaml. This will also make s-C a first-level dependency.
Choosing the Right Solution
In summary, the solution you should use depends on how you want your service to be consumed in an outside repo*.
- If you want outside repos to explicitly include
s-Aands-B, use Solution 1. - If you want outside repos to only include
s-Aand let dependencies resolve automatically, use Solution 2.
Terminology
- Service repo: The repository where
lifecycle.yamlis defined. - Outside repo: Another repository referencing it.
- dev-0: Default static environment.