All skills
asyrafhussin avatar

/clean-code-principles

@680deef

SOLID principles, design patterns, DRY, KISS, and clean code fundamentals. Use when reviewing architecture, checking code quality, refactoring, or discussing design decisions. Triggers on "review architecture", "check code quality", "SOLID principles", "design patterns", or "clean code".

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/clean-code-principles

This session only. Nothing lands on disk.

rulespattern-repository.md

≈2.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Repository Pattern

The Repository pattern abstracts data persistence, providing a collection-like interface for accessing domain objects. It separates business logic from data access, makes code testable, and allows swapping storage implementations without changing application code.

Bad Example

// ❌ Data access mixed with business logic
class OrderService {
  async createOrder(userId: string, items: CartItem[]) {
    // Business logic
    const total = items.reduce((sum, i) => sum + i.price * i.quantity, 0);

    // Direct database access - tightly coupled
    const result = await pool.query(
      `INSERT INTO orders (user_id, total, status, created_at)
       VALUES ($1, $2, $3, NOW()) RETURNING *`,
      [userId, total, 'pending']
    );

    const orderId = result.rows[0].id;

    // More SQL scattered in business logic
    for (const item of items) {
      await pool.query(
        `INSERT INTO order_items (order_id, product_id, quantity, price)
         VALUES ($1, $2, $3, $4)`,
        [orderId, item.productId, item.quantity, item.price]
      );
    }

    return result.rows[0];
  }

  async getOrdersByUser(userId: string) {
    // SQL everywhere
    const result = await pool.query(
      `SELECT o.*, json_agg(oi.*) as items
       FROM orders o
       LEFT JOIN order_items oi ON o.id = oi.order_id
       WHERE o.user_id = $1
       GROUP BY o.id`,
      [userId]
    );
    return result.rows;
  }
}

Problems:

  • Business logic mixed with SQL
  • Hard to test without real database
  • Changing database requires rewriting service
  • SQL scattered across codebase

Good Example

Define Repository Interface

// ✅ Repository interface - contract for data access
interface OrderRepository {
  find(id: string): Promise<Order | null>;
  findByUser(userId: string): Promise<Order[]>;
  findByStatus(status: OrderStatus): Promise<Order[]>;
  save(order: Order): Promise<Order>;
  delete(id: string): Promise<void>;
}

interface OrderItemRepository {
  findByOrder(orderId: string): Promise<OrderItem[]>;
  saveMany(items: OrderItem[]): Promise<OrderItem[]>;
  deleteByOrder(orderId: string): Promise<void>;
}

Implement Repository

// ✅ PostgreSQL implementation
class PostgresOrderRepository implements OrderRepository {
  constructor(private db: Pool) {}

  async find(id: string): Promise<Order | null> {
    const result = await this.db.query(
      'SELECT * FROM orders WHERE id = $1',
      [id]
    );
    return result.rows[0] ? this.mapToOrder(result.rows[0]) : null;
  }

  async findByUser(userId: string): Promise<Order[]> {
    const result = await this.db.query(
      'SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC',
      [userId]
    );
    return result.rows.map(this.mapToOrder);
  }

  async findByStatus(status: OrderStatus): Promise<Order[]> {
    const result = await this.db.query(
      'SELECT * FROM orders WHERE status = $1',
      [status]
    );
    return result.rows.map(this.mapToOrder);
  }

  async save(order: Order): Promise<Order> {
    if (order.id) {
      return this.update(order);
    }
    return this.insert(order);
  }

  private async insert(order: Order): Promise<Order> {
    const result = await this.db.query(
      `INSERT INTO orders (user_id, total, status, created_at)
       VALUES ($1, $2, $3, NOW()) RETURNING *`,
      [order.userId, order.total, order.status]
    );
    return this.mapToOrder(result.rows[0]);
  }

  private async update(order: Order): Promise<Order> {
    const result = await this.db.query(
      `UPDATE orders SET total = $1, status = $2, updated_at = NOW()
       WHERE id = $3 RETURNING *`,
      [order.total, order.status, order.id]
    );
    return this.mapToOrder(result.rows[0]);
  }

  async delete(id: string): Promise<void> {
    await this.db.query('DELETE FROM orders WHERE id = $1', [id]);
  }

  private mapToOrder(row: any): Order {
    return new Order({
      id: row.id,
      userId: row.user_id,
      total: parseFloat(row.total),
      status: row.status as OrderStatus,
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    });
  }
}

Clean Service Layer

// ✅ Service focuses on business logic only
class OrderService {
  constructor(
    private orderRepository: OrderRepository,
    private orderItemRepository: OrderItemRepository,
    private productRepository: ProductRepository,
  ) {}

  async createOrder(userId: string, items: CartItem[]): Promise<Order> {
    // Pure business logic
    const total = this.calculateTotal(items);

    const order = new Order({
      userId,
      total,
      status: OrderStatus.Pending,
    });

    // Repository handles persistence
    const savedOrder = await this.orderRepository.save(order);

    const orderItems = items.map(item => new OrderItem({
      orderId: savedOrder.id,
      productId: item.productId,
      quantity: item.quantity,
      price: item.price,
    }));

    await this.orderItemRepository.saveMany(orderItems);

    return savedOrder;
  }

  async cancelOrder(orderId: string): Promise<Order> {
    const order = await this.orderRepository.find(orderId);

    if (!order) {
      throw new OrderNotFoundException(orderId);
    }

    if (!order.canBeCancelled()) {
      throw new InvalidOrderStateException('Order cannot be cancelled');
    }

    order.cancel();
    return this.orderRepository.save(order);
  }

  private calculateTotal(items: CartItem[]): number {
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }
}

In-Memory Repository for Testing

// ✅ In-memory implementation for tests
class InMemoryOrderRepository implements OrderRepository {
  private orders: Map<string, Order> = new Map();
  private idCounter = 1;

  async find(id: string): Promise<Order | null> {
    return this.orders.get(id) ?? null;
  }

  async findByUser(userId: string): Promise<Order[]> {
    return Array.from(this.orders.values())
      .filter(order => order.userId === userId);
  }

  async findByStatus(status: OrderStatus): Promise<Order[]> {
    return Array.from(this.orders.values())
      .filter(order => order.status === status);
  }

  async save(order: Order): Promise<Order> {
    if (!order.id) {
      order.id = String(this.idCounter++);
    }
    this.orders.set(order.id, order);
    return order;
  }

  async delete(id: string): Promise<void> {
    this.orders.delete(id);
  }

  // Test helper methods
  clear(): void {
    this.orders.clear();
  }

  seed(orders: Order[]): void {
    orders.forEach(order => this.orders.set(order.id, order));
  }
}

Easy Testing

// ✅ Unit tests without database
describe('OrderService', () => {
  let orderService: OrderService;
  let orderRepository: InMemoryOrderRepository;
  let orderItemRepository: InMemoryOrderItemRepository;

  beforeEach(() => {
    orderRepository = new InMemoryOrderRepository();
    orderItemRepository = new InMemoryOrderItemRepository();
    orderService = new OrderService(
      orderRepository,
      orderItemRepository,
      new InMemoryProductRepository(),
    );
  });

  it('creates order with calculated total', async () => {
    const items = [
      { productId: '1', quantity: 2, price: 10 },
      { productId: '2', quantity: 1, price: 25 },
    ];

    const order = await orderService.createOrder('user-1', items);

    expect(order.total).toBe(45);
    expect(order.status).toBe(OrderStatus.Pending);
  });

  it('cancels pending order', async () => {
    orderRepository.seed([
      new Order({ id: '1', userId: 'user-1', status: OrderStatus.Pending }),
    ]);

    const order = await orderService.cancelOrder('1');

    expect(order.status).toBe(OrderStatus.Cancelled);
  });

  it('throws when cancelling shipped order', async () => {
    orderRepository.seed([
      new Order({ id: '1', userId: 'user-1', status: OrderStatus.Shipped }),
    ]);

    await expect(orderService.cancelOrder('1'))
      .rejects.toThrow(InvalidOrderStateException);
  });
});

Generic Repository Base

// ✅ Generic base for common operations
interface Repository<T, ID> {
  find(id: ID): Promise<T | null>;
  findAll(): Promise<T[]>;
  save(entity: T): Promise<T>;
  delete(id: ID): Promise<void>;
  exists(id: ID): Promise<boolean>;
}

abstract class BasePostgresRepository<T, ID> implements Repository<T, ID> {
  constructor(
    protected db: Pool,
    protected tableName: string,
  ) {}

  async find(id: ID): Promise<T | null> {
    const result = await this.db.query(
      `SELECT * FROM ${this.tableName} WHERE id = $1`,
      [id]
    );
    return result.rows[0] ? this.mapToEntity(result.rows[0]) : null;
  }

  async exists(id: ID): Promise<boolean> {
    const result = await this.db.query(
      `SELECT 1 FROM ${this.tableName} WHERE id = $1`,
      [id]
    );
    return result.rows.length > 0;
  }

  protected abstract mapToEntity(row: any): T;
}

Why

  • Separation of concerns
  • Business logic free of persistence details
  • Easy to test with in-memory implementations
  • Swap databases without changing services
  • Centralized query logic
  • Consistent data access patterns
  • Single place to add caching, logging

Source: SKILL.md on GitHub

1 warning16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill contains language-agnostic guidelines, examples, and reference material for clean code and SOLID principles. It consists purely of static documentation and contains no executable code or security risks.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    5/29 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 680deef. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
metadata
{
  "author": "AsyrafHussin",
  "version": "1.0.2"
}

README badge

README badge for asyrafhussin/agent-skills/clean-code-principles