This article discusses the architecture of Trelix v2.11.0, focusing on its unified connector interface for integrating with various external ticket and test management platforms like Jira and Linear. It highlights the design pattern of abstracting diverse API behaviors behind a common contract, ensuring maintainability and extensibility when dealing with different authentication schemes, data formats, and pagination methods. The system design emphasizes robust configuration validation and immediate, synchronous linking of fetched data into a code graph.
Read original on Dev.to #systemdesignThe core problem addressed by Trelix is the disconnect between code and "the work" tracked in external systems like Jira or Linear. To bridge this, Trelix introduces a system of connectors that integrate these external platforms directly into its code graph. This approach provides immediate context for code, linking it to the requirements, bugs, and features it implements, which is a common challenge in large-scale software development environments.
Trelix's architecture for integrating external systems is built around a single, abstract interface: the `ArtifactSource` class. This interface defines a contract that all connectors must adhere to, abstracting away the complexities and idiosyncrasies of each external API. This is a fundamental design pattern for building extensible systems that interact with multiple third-party services, allowing new integrations to be added with minimal changes to the core system.
class ArtifactSource(ABC):
@abstractmethod
def validate_config(self) -> None:
pass
@abstractmethod
def fetch(self) -> list[Artifact]:
pass
# Example of how a connector might validate its config
class JiraConnector(ArtifactSource):
def validate_config(self):
missing = [
name for name, val in (
("TRELIX_JIRA_BASE_URL", self._config.base_url),
("TRELIX_JIRA_EMAIL", self._config.email),
("TRELIX_JIRA_API_TOKEN", self._config.api_token),
("TRELIX_JIRA_PROJECT_KEY", self._config.project_key),
) if not val
]
if missing:
raise ValueError(f"JiraConnector is missing required config: {', '.join(missing)}")A key design decision is the synchronous linking of fetched artifacts into the code graph via the `sync()` method. Instead of relying on a separate batch process, artifacts are written to the database and immediately linked to relevant code entities. This ensures that the code graph is updated in near real-time, providing immediate visibility into the relationships between code and external work items. This approach simplifies the system architecture by avoiding eventual consistency issues for the core graph data, albeit at the potential cost of increased sync latency.
Architectural Trade-off: Synchronous vs. Asynchronous Linking
The choice of synchronous linking provides immediate data availability and a consistent view of the graph, which is beneficial for interactive tools. However, for systems requiring high throughput or tolerating eventual consistency, an asynchronous linking mechanism (e.g., using message queues or background jobs) might be more scalable, distributing the processing load and improving overall system responsiveness during large data imports.