linear-reference-architecture
Production-grade Linear integration architecture patterns. Use when designing system architecture, planning integrations, or reviewing architectural decisions. Trigger with phrases like "linear architecture", "linear system design", "linear integration patterns", "linear best practices architecture". allowed-tools: Read, Write, Edit, Grep version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>
Allowed Tools
No tools specified
Provided by Plugin
linear-pack
Claude Code skill pack for Linear (24 skills)
Installation
This skill is included in the linear-pack plugin:
/plugin install linear-pack@claude-code-plugins-plus
Click to copy
Instructions
# Linear Reference Architecture
## Overview
Production-grade architectural patterns for Linear integrations.
## Prerequisites
- Understanding of distributed systems
- Experience with cloud infrastructure
- Familiarity with event-driven architecture
## Architecture Patterns
### Pattern 1: Simple Integration
Best for: Small teams, single applications
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Linear SDK β β Cache Layer β β Webhook β β
β β (API calls) β β (In-memory) β β Handler β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββ
β Linear API β
β api.linear.app β
ββββββββββββββββββββββββ
```
```typescript
// Simple architecture implementation
// lib/simple-linear.ts
import { LinearClient } from "@linear/sdk";
const cache = new Map();
export class SimpleLinearService {
private client: LinearClient;
constructor() {
this.client = new LinearClient({
apiKey: process.env.LINEAR_API_KEY!,
});
}
async getWithCache(key: string, fetcher: () => Promise, ttl = 300): Promise {
const cached = cache.get(key);
if (cached && cached.expires > Date.now()) {
return cached.data;
}
const data = await fetcher();
cache.set(key, { data, expires: Date.now() + ttl * 1000 });
return data;
}
async getTeams() {
return this.getWithCache("teams", () => this.client.teams());
}
}
```
### Pattern 2: Service-Oriented Architecture
Best for: Medium teams, multiple applications
```
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API Gateway β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββββββ
β Issues Service β β Projects Serviceβ β Notifications Svc β
β (CRUD + sync) β β (Planning) β β (Slack, Email) β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββββββ
β β β
ββββββββββββββββββββββΌβββββββββββββββββββββ
βΌ
βββββββββββββββββββ
β Linear Gateway β
β (Rate limiting, β
β caching, auth) β
βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Linear API β
βββββββββββββββββββ
```
```typescript
// lib/linear-gateway.ts
import { LinearClient } from "@linear/sdk";
import Redis from "ioredis";
export class LinearGateway {
private client: LinearClient;
private redis: Redis;
private rateLimiter: RateLimiter;
constructor() {
this.client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY! });
this.redis = new Redis(process.env.REDIS_URL);
this.rateLimiter = new RateLimiter({
maxRequests: 1000, // Leave buffer from 1500 limit
windowMs: 60000,
});
}
async execute(operation: string, fn: () => Promise): Promise {
// Check cache first
const cacheKey = `linear:${operation}`;
const cached = await this.redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// Rate limit
await this.rateLimiter.acquire();
// Execute with metrics
const start = Date.now();
try {
const result = await fn();
// Cache result
await this.redis.setex(cacheKey, 60, JSON.stringify(result));
// Record metrics
metrics.requestDuration.observe(Date.now() - start);
return result;
} catch (error) {
metrics.errors.inc({ operation });
throw error;
}
}
}
```
### Pattern 3: Event-Driven Architecture
Best for: Large teams, real-time requirements
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Event Bus (Kafka/SQS) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β² β β
β βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Webhook Ingesterβ β Event Processor β β Notification β
β (Linear events) β β (Business logic)β β Service β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β State Store β
β (PostgreSQL) β
βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Linear Sync β
β (Outbound) β
βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Linear API β
βββββββββββββββββββ
```
```typescript
// services/webhook-ingester.ts
import { Kafka } from "kafkajs";
const kafka = new Kafka({
brokers: [process.env.KAFKA_BROKER!],
});
const producer = kafka.producer();
export async function ingestWebhook(event: LinearWebhookEvent): Promise {
// Verify signature
if (!verifySignature(event)) {
throw new Error("Invalid signature");
}
// Publish to appropriate topic
await producer.send({
topic: `linear.${event.type.toLowerCase()}`,
messages: [{
key: event.data.id,
value: JSON.stringify(event),
headers: {
action: event.action,
timestamp: event.webhookTimestamp.toString(),
},
}],
});
}
// services/event-processor.ts
const consumer = kafka.consumer({ groupId: "linear-processor" });
await consumer.subscribe({ topics: ["linear.issue", "linear.comment"] });
await consumer.run({
eachMessage: async ({ topic, message }) => {
const event = JSON.parse(message.value!.toString());
switch (topic) {
case "linear.issue":
await processIssueEvent(event);
break;
case "linear.comment":
await processCommentEvent(event);
break;
}
},
});
```
### Pattern 4: CQRS with Event Sourcing
Best for: Complex domains, audit requirements
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Command Side β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Commands β Command Handler β Event Store β Event Publisher β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Query Side β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Event Subscriber β Projector β Read Models β Query API β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
```typescript
// cqrs/event-store.ts
interface StoredEvent {
id: string;
aggregateId: string;
aggregateType: string;
eventType: string;
data: Record;
metadata: {
userId: string;
correlationId: string;
causationId: string;
timestamp: Date;
};
version: number;
}
class EventStore {
async append(aggregateId: string, events: StoredEvent[]): Promise {
await db.transaction(async (tx) => {
for (const event of events) {
await tx.insert(eventsTable).values(event);
}
});
// Publish to subscribers
for (const event of events) {
await eventBus.publish(event);
}
}
async getEvents(aggregateId: string): Promise {
return db.select().from(eventsTable)
.where(eq(eventsTable.aggregateId, aggregateId))
.orderBy(eventsTable.version);
}
}
// Projector for read model
class IssueProjector {
@Subscribe("IssueCreated")
async onIssueCreated(event: StoredEvent): Promise {
await db.insert(issueReadModel).values({
id: event.aggregateId,
...event.data,
createdAt: event.metadata.timestamp,
});
}
@Subscribe("IssueUpdated")
async onIssueUpdated(event: StoredEvent): Promise {
await db.update(issueReadModel)
.set(event.data)
.where(eq(issueReadModel.id, event.aggregateId));
}
}
```
## Project Structure
```
linear-integration/
βββ src/
β βββ api/ # REST/GraphQL API
β β βββ routes/
β β βββ middleware/
β βββ services/ # Business logic
β β βββ issue-service.ts
β β βββ project-service.ts
β β βββ sync-service.ts
β βββ infrastructure/ # External integrations
β β βββ linear/
β β β βββ client.ts
β β β βββ cache.ts
β β β βββ webhook-handler.ts
β β βββ database/
β β βββ cache/
β βββ domain/ # Domain models
β β βββ issue.ts
β β βββ project.ts
β βββ config/ # Configuration
β βββ index.ts
βββ tests/
β βββ unit/
β βββ integration/
β βββ e2e/
βββ infrastructure/ # IaC
βββ terraform/
βββ kubernetes/
```
## Resources
- [Linear API Best Practices](https://developers.linear.app/docs/graphql/best-practices)
- [Event-Driven Architecture](https://martinfowler.com/articles/201701-event-driven.html)
- [CQRS Pattern](https://martinfowler.com/bliki/CQRS.html)
## Next Steps
Configure multi-environment setup with `linear-multi-env-setup`.