Docs
References

References

LikeC4 uses the lexical scope, with hoisting, almost like in JavaScript.

Scope

To understand references, we need to understand scopes first.
Example:

references.c4
model {
  service service1 {
    component api
    component frontend
  }
}

Every element is unique in the model, so we can add a relationship referencing them, like:

references.c4
model {
  service service1 {
    component api
    component frontend
  }
  frontend -> api
}

But if we add service2 with another api:

references.c4
model {
  service service1 {
    component api
    component frontend
  }
  service service2 {
    component api
  }
 
  frontend -> api // ⛔️ Error: 'api' not found
}

The reference is ambiguous, as there are two api components in the model.

Every element creates a new scope inside {...}, so api is unique inside service1 and service2, but not in the scope of model.

We can resolve by moving the relationship to the scope of service2:

references.c4
model {
  service service1 {
    component api
    component frontend
  }
  service service2 {
    component api
 
    frontend -> api // ✅ This is OK,
                    //    'api' is unique in 'service2'
                    //    'frontend' is unique in 'model'
  }
}

Hoisting

Hoisting is a mechanism that moves the reference to the top of the scope.

In LikeC4, the element, besides being hoisted in its scope, also "bubbles" to the upper scopes, if it stays unique.

We may reference something that is not yet defined but will be hoisted later.
The relationship on line 8 references graphql defined below on line 15:

hoisting.c4
model {
 
  service service1 {
    component api
    component frontend
 
    frontend -> api // ✅ This is OK, references to 'api' from 'service1'
    frontend -> graphql // ✅ This is OK, references to unique 'graphql'
  }
 
  frontend -> api // ⛔️ Error: 'api' is ambiguous
 
  service service2 {
    component api
    component graphql
 
    frontend -> api  // ✅ This is OK, references to 'api' from 'service2'
  }
 
}

Lines 7 and 17 are the same: frontend -> api
But they reference to different elements:
- Line 7 to service1.api
- Line 17 to service2.api

Fully qualified names

Top-level elements (placed directly in the model block) are available globally.
To reference nested elements we use their fully qualified names (FQN).

Example:

references.c4
model {
  service service1 {
    component api
    component frontend
  }
  service service2 {
    component api
  }
 
  frontend -> api // ⛔️ Error: 'api' not found
  frontend -> service1.api // ✅ This is OK
  frontend -> service2.api // ✅ This is OK
}

Or even:

references.c4
model {
  service service1 {
    component api
    component frontend {
      -> service2.api
    }
  }
  service service2 {
    component api
  }
}

Some parts may be omitted, if FQN stays unique:

references.c4
model {
  service service {
    component backend1  {
      component api
    }
    component backend2  {
      component api
      component graphql
    }
  }
 
  frontend -> service.backend1.api // ✅ Non-ambiguous fully qualified name
 
  frontend -> backend1.api  // ✅ This is OK, 'api' is unique in 'backend1',
                            //    and 'backend1' is unique in the model
                            //    We may omit 'service'
 
  frontend -> backend2.api  // ✅ This is also OK
 
  frontend -> service.api   // ⛔️ Error: 'api' is ambiguous in 'service'
 
  frontend -> service.graphql // ✅ This is also OK, we omit 'backend2'
                              //    as 'graphql' is unique in 'service'
}
⚠️

While omitting FQN-parts makes code better looking and references shorter,
it may be error-prone when you refactor the model

Sure, only if you have same-named elements