mcpField
S
software-architecture

software-architecture

github.com/bbeierle12開発9148 回インストール1 日前に更新
リポジトリ

software-architecture について

ソフトウェア開発者向けのアーキテクチャ資料です。SOLID 原則、クリーンアーキテクチャ、デザインパターン、アンチパターンを扱い、コード例で責務や依存関係、設計判断の記録方法を示します。

主な機能

  1. 01コード例付きの SOLID 原則
  2. 02クリーンアーキテクチャのレイヤー解説
  3. 03リポジトリ、ファクトリー、ストラテジー、オブザーバーパターン
  4. 04アーキテクチャ決定記録の例
  5. 05よくあるアーキテクチャのアンチパターン

活用シーン

  1. 01責務が明確なクラスやインターフェースの設計
  2. 02アプリケーション層とドメイン層の構成
  3. 03一般的なデザインパターンの選択と適用
  4. 04重要なソフトウェア設計判断の記録

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/bbeierle12/skill-mcp-claude --skill software-architecture

インストール後、Claude Code で/software-architectureを入力して読み込まれたか確認します。

同梱ファイル_meta.json

詳細

ソフトウェアアーキテクチャ

SOLID 原則

単一責任の原則(SRP)

クラスが変更される理由は 1 つだけにするべきです。

// ❌ Bad: Multiple responsibilities
class User {
  saveToDatabase() { }
  sendEmail() { }
  generateReport() { }
}

// ✅ Good: Single responsibility
class User { }
class UserRepository { save(user: User) { } }
class EmailService { send(to: string) { } }
class ReportGenerator { generate(user: User) { } }
オープン・クローズドの原則(OCP)

拡張に対して開き、変更に対して閉じるべきです。

// ❌ Bad: Requires modification for new types
function calculateArea(shape: Shape) {
  if (shape.type === 'circle') {
    return Math.PI * shape.radius ** 2;
  } else if (shape.type === 'rectangle') {
    return shape.width * shape.height;
  }
  // Need to modify for new shapes
}

// ✅ Good: Extend without modification
interface Shape {
  area(): number;
}

class Circle implements Shape {
  constructor(private radius: number) {}
  area() { return Math.PI * this.radius ** 2; }
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  area() { return this.width * this.height; }
}
リスコフの置換原則(LSP)

サブタイプは、基底型と置き換え可能でなければなりません。

// ❌ Bad: Square violates Rectangle's contract
class Rectangle {
  setWidth(w: number) { this.width = w; }
  setHeight(h: number) { this.height = h; }
}

class Square extends Rectangle {
  setWidth(w: number) { this.width = w; this.height = w; } // Violates LSP
}

// ✅ Good: Separate hierarchies
interface Shape {
  area(): number;
}

class Rectangle implements Shape { }
class Square implements Shape { }
インターフェース分離の原則(ISP)

クライアントは、使用しないインターフェースに依存するべきではありません。

// ❌ Bad: Fat interface
interface Worker {
  work(): void;
  eat(): void;
  sleep(): void;
}

// ✅ Good: Segregated interfaces
interface Workable { work(): void; }
interface Eatable { eat(): void; }
interface Sleepable { sleep(): void; }

class Human implements Workable, Eatable, Sleepable { }
class Robot implements Workable { }
依存関係逆転の原則(DIP)

具象ではなく、抽象に依存するべきです。

// ❌ Bad: High-level depends on low-level
class UserService {
  private database = new MySQLDatabase();
}

// ✅ Good: Both depend on abstraction
interface Database {
  save(data: any): Promise<void>;
  find(id: string): Promise<any>;
}

class UserService {
  constructor(private database: Database) {}
}

// Now can inject any implementation
const service = new UserService(new MySQLDatabase());
const testService = new UserService(new MockDatabase());

クリーンアーキテクチャ

┌─────────────────────────────────────────────────┐
│                  Frameworks                      │
│  (Express, React, Database Drivers)              │
├─────────────────────────────────────────────────┤
│              Interface Adapters                  │
│  (Controllers, Presenters, Gateways)            │
├─────────────────────────────────────────────────┤
│              Application Layer                   │
│  (Use Cases, Application Services)              │
├─────────────────────────────────────────────────┤
│                Domain Layer                      │
│  (Entities, Value Objects, Domain Services)     │
└─────────────────────────────────────────────────┘

Dependencies point INWARD only.
Inner layers know nothing about outer layers.
ドメイン層(最内層)
// entities/User.ts
export class User {
  constructor(
    public readonly id: string,
    public readonly email: Email,
    public name: string,
    private password: HashedPassword
  ) {}

  changeName(newName: string): void {
    if (newName.length < 2) {
      throw new DomainError('Name too short');
    }
    this.name = newName;
  }

  verifyPassword(plaintext: string): boolean {
    return this.password.verify(plaintext);
  }
}

// value-objects/Email.ts
export class Email {
  constructor(private readonly value: string) {
    if (!this.isValid(value)) {
      throw new DomainError('Invalid email');
    }
  }

  private isValid(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  toString(): string {
    return this.value;
  }
}
アプリケーション層(ユースケース)
// use-cases/CreateUser.ts
interface CreateUserInput {
  email: string;
  password: string;
  name: string;
}

interface CreateUserOutput {
  id: string;
  email: string;
  name: string;
}

export class CreateUserUseCase {
  constructor(
    private userRepository: UserRepository,
    private passwordHasher: PasswordHasher,
    private emailService: EmailService
  ) {}

  async execute(input: CreateUserInput): Promise<CreateUserOutput> {
    // Validate email uniqueness
    const existing = await this.userRepository.findByEmail(input.email);
    if (existing) {
      throw new ApplicationError('Email already exists');
    }

    // Create domain object
    const hashedPassword = await this.passwordHasher.hash(input.password);
    const user = new User(
      generateId(),
      new Email(input.email),
      input.name,
      hashedPassword
    );

    // Persist
    await this.userRepository.save(user);

    // Side effects
    await this.emailService.sendWelcome(user.email);

    return {
      id: user.id,
      email: user.email.toString(),
      name: user.name,
    };
  }
}
インターフェースアダプター
// controllers/UserController.ts
export class UserController {
  constructor(private createUserUseCase: CreateUserUseCase) {}

  async create(req: Request, res: Response): Promise<void> {
    const result = await this.createUserUseCase.execute({
      email: req.body.email,
      password: req.body.password,
      name: req.body.name,
    });

    res.status(201).json(result);
  }
}

// repositories/PrismaUserRepository.ts
export class PrismaUserRepository implements UserRepository {
  async save(user: User): Promise<void> {
    await prisma.user.create({
      data: {
        id: user.id,
        email: user.email.toString(),
        name: user.name,
      },
    });
  }

  async findByEmail(email: string): Promise<User | null> {
    const data = await prisma.user.findUnique({
      where: { email },
    });
    return data ? this.toDomain(data) : null;
  }

  private toDomain(data: PrismaUser): User {
    return new User(
      data.id,
      new Email(data.email),
      data.name,
      new HashedPassword(data.password)
    );
  }
}

デザインパターン

リポジトリパターン
interface Repository<T> {
  findById(id: string): Promise<T | null>;
  findAll(): Promise<T[]>;
  save(entity: T): Promise<void>;
  delete(id: string): Promise<void>;
}
ファクトリーパターン
interface PaymentProcessor {
  process(amount: number): Promise<void>;
}

class PaymentProcessorFactory {
  create(type: 'stripe' | 'paypal'): PaymentProcessor {
    switch (type) {
      case 'stripe': return new StripeProcessor();
      case 'paypal': return new PayPalProcessor();
    }
  }
}
ストラテジーパターン
interface PricingStrategy {
  calculate(basePrice: number): number;
}

class RegularPricing implements PricingStrategy {
  calculate(basePrice: number) { return basePrice; }
}

class PremiumPricing implements PricingStrategy {
  calculate(basePrice: number) { return basePrice * 0.9; }
}

class Order {
  constructor(private pricingStrategy: PricingStrategy) {}
  
  getTotal(basePrice: number) {
    return this.pricingStrategy.calculate(basePrice);
  }
}
オブザーバーパターン
interface Observer<T> {
  update(data: T): void;
}

class EventEmitter<T> {
  private observers: Observer<T>[] = [];

  subscribe(observer: Observer<T>): void {
    this.observers.push(observer);
  }

  notify(data: T): void {
    this.observers.forEach(o => o.update(data));
  }
}

アーキテクチャ決定記録(ADR)

重要な決定を記録します。

# ADR-001: Use PostgreSQL for Primary Database

## Status
Accepted

## Context
Need to choose a primary database for user data storage.

## Decision
Use PostgreSQL.

## Consequences
### Positive
- Strong consistency guarantees
- Rich query capabilities
- Well-supported ORMs

### Negative
- Requires more operational overhead than SQLite
- Horizontal scaling more complex than NoSQL

避けるべきアンチパターン

神オブジェクト

❌ すべてを行う 1 つのクラス

スパゲッティコード

❌ 明確な構造や流れがない

ゴールデンハンマー

❌ すべての問題に同じ解決策を使う

時期尚早な最適化

❌ 測定する前に最適化する

カーゴ・カルト・プログラミング

❌ 理由を理解せずにパターンを使う

コメント