8.6 KiB
Relation Types for C4 Code Elements
This guide provides comprehensive documentation of relationship types between code elements at the C4 Code level.
Overview
Relations describe how code elements interact with each other. Each relation has:
- target: The code element being related to
- type: The nature of the relationship
- description: Active voice description of the relationship
Code Flow Relations
calls
Definition: Invokes another function or method
When to use: When a function/method directly calls another
Examples:
function processOrder(order: Order) {
validateOrder(order); // calls validateOrder
calculateTotal(order); // calls calculateTotal
}
JSON:
{
"target": "validate-order",
"type": "calls",
"description": "Calls validateOrder to validate order structure"
}
awaits
Definition: Awaits an async function
When to use: When async function awaits another async function
Examples:
async function getUser(id: string) {
const user = await userRepository.findById(id); // awaits findById
return user;
}
JSON:
{
"target": "user-repository-find-by-id",
"type": "awaits",
"description": "Awaits UserRepository.findById for user lookup"
}
returns
Definition: Returns a specific type
When to use: When documenting what type a function returns
Examples:
function createUser(data: UserInput): User {
return new User(data); // returns User
}
JSON:
{
"target": "user-class",
"type": "returns",
"description": "Returns User instance with provided data"
}
throws
Definition: Throws an exception type
When to use: When function explicitly throws an exception
Examples:
function validateAge(age: number) {
if (age < 0) {
throw new ValidationError('Age cannot be negative'); // throws ValidationError
}
}
JSON:
{
"target": "validation-error-class",
"type": "throws",
"description": "Throws ValidationError when age is negative"
}
Structural Relations
inherits
Definition: Extends another class
When to use: When a class extends a parent class
Examples:
class AdminUser extends User { // inherits from User
permissions: string[];
}
JSON:
{
"target": "user-class",
"type": "inherits",
"description": "Inherits from User base class"
}
implements
Definition: Implements an interface
When to use: When a class implements an interface contract
Examples:
class UserService implements IUserService { // implements IUserService
getUser(id: string): User { ... }
}
JSON:
{
"target": "i-user-service-interface",
"type": "implements",
"description": "Implements IUserService interface contract"
}
declares
Definition: Declares a type or interface
When to use: When defining the shape of data used elsewhere
Examples:
interface UserInput { // declares shape for createUser
name: string;
email: string;
}
function createUser(input: UserInput) { ... }
JSON:
{
"target": "create-user-function",
"type": "declares",
"description": "Declares the input shape for createUser function"
}
uses-type
Definition: Uses a type in signature or body
When to use: When function uses a type for parameters, return, or internally
Examples:
function processPayment(payment: Payment): PaymentResult {
// Uses Payment type for input
// Uses PaymentResult type for output
}
JSON:
{
"target": "payment-type",
"type": "uses-type",
"description": "Uses Payment type for input parameter"
}
Data Relations
creates
Definition: Instantiates a class
When to use: When function creates new instance of a class
Examples:
function createOrder(items: Item[]): Order {
return new Order(items); // creates Order instance
}
JSON:
{
"target": "order-class",
"type": "creates",
"description": "Creates new Order instance with provided items"
}
mutates
Definition: Modifies state or data
When to use: When function modifies object state
Examples:
function updateUser(user: User, data: Partial<User>) {
Object.assign(user, data); // mutates user object
}
JSON:
{
"target": "user-class",
"type": "mutates",
"description": "Mutates User instance with provided data"
}
reads
Definition: Reads from data source
When to use: When function reads data without modifying
Examples:
function getUserName(user: User): string {
return user.name; // reads from User
}
JSON:
{
"target": "user-class",
"type": "reads",
"description": "Reads name property from User instance"
}
Dependency Relations
imports
Definition: Imports from another module
When to use: When code imports from another file/module
Examples:
import { UserService } from './services/user'; // imports UserService
JSON:
{
"target": "user-service-class",
"type": "imports",
"description": "Imports UserService from services module"
}
depends-on
Definition: General dependency relationship
When to use: When there's a dependency that doesn't fit other categories
Examples:
class OrderService {
constructor(
private userService: UserService, // depends-on UserService
private paymentService: PaymentService // depends-on PaymentService
) {}
}
JSON:
{
"target": "user-service-class",
"type": "depends-on",
"description": "Depends on UserService for user operations"
}
overrides
Definition: Overrides parent class method
When to use: When method overrides a parent method
Examples:
class AdminUser extends User {
override toString(): string { // overrides User.toString
return `Admin: ${this.name}`;
}
}
JSON:
{
"target": "user-class-to-string",
"type": "overrides",
"description": "Overrides User.toString to include admin prefix"
}
Relation Type Summary
| Type | Category | Description |
|---|---|---|
calls |
Code Flow | Invokes function/method |
awaits |
Code Flow | Awaits async function |
returns |
Code Flow | Returns specific type |
throws |
Code Flow | Throws exception |
inherits |
Structural | Extends class |
implements |
Structural | Implements interface |
declares |
Structural | Declares type/interface |
uses-type |
Structural | Uses type in signature |
creates |
Data | Instantiates class |
mutates |
Data | Modifies state |
reads |
Data | Reads data |
imports |
Dependency | Imports from module |
depends-on |
Dependency | General dependency |
overrides |
Dependency | Overrides parent method |
Complete Example
{
"relations": [
{
"target": "base-service-class",
"type": "inherits",
"description": "Inherits common service functionality from BaseService"
},
{
"target": "i-user-service-interface",
"type": "implements",
"description": "Implements IUserService interface contract"
},
{
"target": "user-repository",
"type": "depends-on",
"description": "Depends on UserRepository for data persistence"
},
{
"target": "user-repository-find-by-id",
"type": "awaits",
"description": "Awaits UserRepository.findById for user lookup"
},
{
"target": "user-class",
"type": "returns",
"description": "Returns User instance to caller"
},
{
"target": "user-not-found-error",
"type": "throws",
"description": "Throws UserNotFoundError when user doesn't exist"
}
]
}
Best Practices
✅ DO:
- Use active voice in descriptions
- Be specific about what is being called/used
- Document all significant relationships
- Use the most specific relation type
❌ DON'T:
- Use passive voice ("is called by")
- Skip obvious but important relations
- Use generic descriptions
- Mix relation types inappropriately
Relation Direction
All relations are outbound from the source element:
- Source element → calls → Target element
- Source element → inherits → Target element
- Source element → depends-on → Target element
Do NOT document reverse relations (e.g., "is called by"). The target element's incoming relations can be derived from the source elements' outbound relations.