Fourth Brother's AI Journey

Post-80s Breakthrough: From Programmer to AI's Fourth Dream

article-6-architecture-as-code-systematic-thinking-tame-ai

Architecture as Code: How to Tame AI with Systematic Thinking

๐Ÿง  The Ultimate Dilemma of AI Programming: Are You "Using AI" or "Being Used by AI"?

Most people fall into a paradox in AI programming: originally wanting AI to improve efficiency, but ending up spending a lot of time "correcting AI" and "fixing problems generated by AI."

The root cause lies in: lack of systematic architectural thinking. Without good architecture, AI is like a wild horse - capable but uncontrollable.

During Tidepool Notes development, I summarized a set of "Architecture as Code" methodology that transforms AI from an "uncontrollable genius" into an "obedient expert."

๐ŸŽฏ Core Cognition: Architecture Before Code, Rules Before Features

Misconceptions in Traditional AI Programming

โŒ Typical Wrong Workflow:

  1. Have an idea
  2. Directly ask AI to write code
  3. AI generates complex solutions
  4. Find problems, ask AI to modify
  5. More and more problems, project out of control

Where's the problem?

  • No clear architectural boundaries
  • No predefined rules
  • No systematic constraint mechanisms
  • AI is "freely expressing," deviating from actual needs

Correct Workflow for Architecture as Code

โœ… Systematic Correct Workflow:

  1. Define architectural principles and boundaries
  2. Create rule constraints and templates
  3. Let AI work within the framework
  4. Control output quality through architecture
  5. Continuously optimize the architecture itself

๐Ÿ—๏ธ First Layer: Project-Level Architecture Constraints

1. Mandatory Constraints on Technology Stack

Problem: Why does AI recommend unsuitable technology stacks?

  • AI tends to recommend "latest and hottest" technologies
  • Lacks understanding of actual project needs
  • Overconsiders "possible future expansion needs"

Solution: Lock down technology stack in Claude.md

## Mandatory Technology Stack Constraints (Unchangeable)

### Frontend Technology Stack (Locked)
- Framework: Taro 4.1.7 + React 18 (No Vue, Next.js, etc.)
- Language: TypeScript 5.4.5 (Strict mode, no any types)
- State Management: React Context + useReducer (No Redux, Zustand, etc.)
- Styling: SCSS + CSS Variables (No Tailwind, Styled Components, etc.)
- Charts: ECharts (No Chart.js, D3, etc.)

### Backend Technology Stack (Locked)
- Runtime: Node.js 18+ (No Python, Java, etc.)
- Framework: Fastify (No Express, Koa, etc.)
- Database: PostgreSQL 14+ (No MySQL, MongoDB, etc.)
- ORM: Prisma (No TypeORM, Sequelize, etc.)
- Authentication: JWT (No Session, OAuth, etc.)

### Prohibited Technologies
- โŒ Any form of microservices architecture
- โŒ Message queues (Redis Queue, RabbitMQ, etc.)
- โŒ Complex caching strategies
- โŒ Multi-database design

Effect: AI can no longer recommend "show-off" solutions, can only solve problems within given framework

2. Mandatory File Structure Specifications

Problem: Why does AI like to create complex file structures?

  • Influenced by "best practices" articles
  • Tends to over-layer and over-abstract
  • Doesn't understand actual needs of small projects

Solution: Define precise file structure templates

## Mandatory File Structure (Unbreakable)

project/
โ”œโ”€โ”€ frontend/
โ”‚ โ””โ”€โ”€ tidepool/
โ”‚ โ”œโ”€โ”€ src/
โ”‚ โ”‚ โ”œโ”€โ”€ app.tsx # App entry, not exceeding 100 lines
โ”‚ โ”‚ โ”œโ”€โ”€ context/ # Global state, each file not exceeding 200 lines
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ AppContext.tsx
โ”‚ โ”‚ โ”œโ”€โ”€ pages/ # Page components, each file not exceeding 300 lines
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ index/
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ record/
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ chat/
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ insights/
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ settings/
โ”‚ โ”‚ โ”œโ”€โ”€ components/ # Common components, each file not exceeding 200 lines
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ PageWrapper.tsx
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ CustomTabBar.tsx
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ SketchIcon.tsx
โ”‚ โ”‚ โ”œโ”€โ”€ services/ # API services, each file not exceeding 300 lines
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ api.ts
โ”‚ โ”‚ โ””โ”€โ”€ types/ # Type definitions, each file not exceeding 150 lines
โ”‚ โ”‚ โ””โ”€โ”€ index.ts
โ”‚ โ””โ”€โ”€ config/ # Configuration files, unchangeable
โ”‚ โ””โ”€โ”€ apiConfig.js
โ””โ”€โ”€ backend/
โ””โ”€โ”€ src/
โ”œโ”€โ”€ routes/ # API routes, each file not exceeding 200 lines
โ”‚ โ”œโ”€โ”€ auth.routes.ts
โ”‚ โ”œโ”€โ”€ users.routes.ts
โ”‚ โ””โ”€โ”€ sessions.routes.ts
โ”œโ”€โ”€ services/ # Business logic, each file not exceeding 400 lines
โ”‚ โ”œโ”€โ”€ auth.service.ts
โ”‚ โ”œโ”€โ”€ users.service.ts
โ”‚ โ””โ”€โ”€ llm.service.ts
โ”œโ”€โ”€ repositories/ # Data access, each file not exceeding 300 lines
โ”‚ โ”œโ”€โ”€ users.repository.ts
โ”‚ โ””โ”€โ”€ sessions.repository.ts
โ””โ”€โ”€ types/ # Type definitions, each file not exceeding 150 lines
โ”œโ”€โ”€ users.types.ts
โ””โ”€โ”€ sessions.types.ts


**Key Constraints:**
- Single file not exceeding 1500 lines (AI context window limit)
- Directory structure unchangeable
- File naming must follow specifications
- Prohibited from creating additional utils, helpers, etc. directories

3. Mandatory Rules for Dependency Management

Problem: Why does AI like to add unnecessary dependencies?

  • Tends to use "specialized libraries" to solve simple problems
  • Influenced by GitHub stars count
  • Doesn't understand dependency maintenance costs

Solution: Establish strict dependency management rules

## Mandatory Dependency Management Rules

### Prohibited Dependency Types
- โŒ Any form of utility libraries (lodash, date-fns, classnames, etc.)
- โŒ Any UI component libraries (Ant Design, Material-UI, etc.)
- โŒ Any form validation libraries (yup, zod, joi, etc.)
- โŒ Any HTTP client libraries (axios, got, etc., use native fetch)
- โŒ Any state management libraries (redux, mobx, zustand, etc.)

### Scenarios Requiring Native Implementation
```typescript
// โŒ Prohibit using lodash
import { map, filter, find } from 'lodash'

// โœ… Use native JavaScript
const filtered = users.filter(user => user.age > 18)
const found = users.find(user => user.id === targetId)

// โŒ Prohibit using date-fns
import { format, addDays } from 'date-fns'
format(new Date(), 'yyyy-MM-dd')

// โœ… Use native Date
const formatDate = (date: Date) => {
  return date.toISOString().split('T')[0]
}

// โŒ Prohibit using axios
import axios from 'axios'
const response = await axios.get('/api/users')

// โœ… Use native fetch
const response = await fetch('/api/users')
const data = await response.json()

Dependency Decision Tree

Need functionality F?
โ”œโ”€โ”€ Can it be implemented with native JavaScript APIs?
โ”‚   โ”œโ”€โ”€ Yes โ†’ Use native implementation
โ”‚   โ””โ”€โ”€ No โ†’ Check if it's a core project requirement
โ”‚       โ”œโ”€โ”€ Yes โ†’ Consider adding dependency (needs sufficient reason)
โ”‚       โ””โ”€โ”€ No โ†’ Redesign requirements to avoid dependency

๐Ÿงฉ Second Layer: Code-Level Architecture Constraints

1. Mandatory Specifications for Function Design

Problem: Why does AI write complex functions?

  • Tends to show "comprehensive consideration"
  • Overly handles edge cases
  • Likes to use complex design patterns

Solution: Triple Constraints for Function Design

// โœ… Function template: Force simplicity
export class TemplateService {
  // Constraint 1: Single function not exceeding 50 lines
  async create(data: CreateData): Promise<ReturnData> {
    // Constraint 2: Only necessary error handling
    try {
      const result = await repository.create(data)
      logger.info(`Entity created: ${result.id}`)
      return result
    } catch (error) {
      logger.exception('Create failed', error)
      throw error
    }
  }

  // Constraint 3: Single responsibility, don't do extra things
  private validate(data: CreateData): void {
    if (!data.requiredField) {
      throw new Error('Required field missing')
    }
  }
}

// โŒ Complex function that AI might generate
export class ComplexService {
  async createComplexEntity(data: ComplexInput): Promise<ComplexResult> {
    // Excessive input validation (50 lines)
    this.validateInputFormat(data)
    this.validateBusinessRules(data)
    this.validatePermissions(data)
    this.validateDependencies(data)

    // Complex retry logic (30 lines)
    const result = await this.withRetry(async () => {
      return await this.performComplexCreation(data)
    }, { retries: 3, backoff: 'exponential' })

    // Excessive logging (20 lines)
    logger.debug('Starting creation process', { dataId: data.id })
    logger.info('Validation passed', { validationResults: this.lastValidation })
    logger.info('Creation completed', { resultId: result.id })

    // Unnecessary caching handling (15 lines)
    await this.updateCache(result)
    await this.invalidateRelatedCache(result.relatedEntities)

    return result
  }
}

2. Unified Standards for Error Handling

Problem: Why does AI design complex error handling?

  • Tends to "accommodate all situations"
  • Likes to create complex error classification systems
  • Overconsiders user experience

Solution: Let it Crash + Unified Error Format

// โœ… Unified error handling template
export class StandardService {
  async operation(data: InputData): Promise<OutputData> {
    try {
      const result = await repository.operation(data)
      return result
    } catch (error) {
      // Unified format: log + throw
      logger.exception('Operation failed', error)
      throw new Error(`Operation failed: ${error.message}`)
    }
  }
}

// โŒ Complex error handling that AI might generate
export class ComplexErrorService {
  async operation(data: InputData): Promise<OutputData | null> {
    try {
      const result = await repository.operation(data)
      return result
    } catch (error) {
      // Excessive error classification
      if (error instanceof ValidationError) {
        return { error: 'VALIDATION_ERROR', details: error.details }
      } else if (error instanceof PermissionError) {
        return { error: 'PERMISSION_DENIED' }
      } else if (error instanceof NetworkError) {
        // Complex retry logic
        return await this.retryWithBackoff(() => repository.operation(data))
      } else {
        // Return null instead of throwing error
        return null
      }
    }
  }
}

3. Simplicity Principles for Class Design

Problem: Why does AI like over-engineering class structures?

  • Influenced by design pattern books
  • Tends to "prepare for future expansion"
  • Likes to use inheritance and abstraction

Solution: Prohibit over-abstraction, force composition

// โœ… Simple composition design
export class UserService {
  constructor(
    private userRepository: UserRepository,
    private logger: Logger
  ) {}

  async create(userData: CreateUserInput): Promise<User> {
    const user = await this.userRepository.create(userData)
    this.logger.info(`User created: ${user.id}`)
    return user
  }
}

// โŒ Complex inheritance structure that AI might generate
abstract class BaseService<T, CreateInput, UpdateInput> {
  protected abstract repository: Repository<T, CreateInput, UpdateInput>
  protected abstract logger: Logger
  protected abstract validator: Validator<T>

  async create(data: CreateInput): Promise<T> {
    await this.validator.validate(data)
    const result = await this.repository.create(data)
    this.logger.info(`${this.getEntityName()} created: ${result.id}`)
    return result
  }

  protected abstract getEntityName(): string
}

class UserService extends BaseService<User, CreateUserInput, UpdateInput> {
  // Complex template method pattern
}

๐Ÿ”ง Third Layer: Process-Level Architecture Constraints

1. Standardized Development Process

Problem: Why do AI programming projects easily get out of control?

  • Lack of clear development rhythm
  • No clear completion standards
  • Lack of continuous quality control

Solution: Standardized Development Process

## Mandatory Development Process

### Phase 1: Architecture Definition (Cannot be skipped)
1. Create/update Claude.md documentation
2. Define clear requirement boundaries
3. Determine technology stack constraints
4. Create file structure templates

### Phase 2: Skeleton Setup (Must be done first)
1. Create all necessary directory structures
2. Define TypeScript interfaces
3. Create basic CRUD operations
4. Set up basic error handling

### Phase 3: Feature Implementation (Strict order)
1. First implement data layer (repositories)
2. Then implement business layer (services)
3. Finally implement interface layer (routes)
4. Each layer must pass tests after completion

### Phase 4: Quality Check (Must be executed)
1. File line count check (not exceeding 1500 lines)
2. Type check (noImplicitAny: true)
3. Code style check (ESLint)
4. Unit test coverage check

### Daily Quality Gates
- [ ] All files not exceeding 1500 lines
- [ ] No any types used
- [ ] All errors correctly thrown
- [ ] Dependencies comply with constraint rules
- [ ] Functions have single responsibility

2. Mandatory Requirements for Code Review

Problem: How to ensure AI-generated code meets architectural requirements?

## Mandatory Code Review Checklist

### Architecture Consistency Check
- [ ] File structure complies with specifications
- [ ] Dependency relationships are correct
- [ ] Interface design is consistent
- [ ] Error handling is unified

### Quality Standard Check
- [ ] Function length not exceeding 50 lines
- [ ] Class has single responsibility
- [ ] No unnecessary complexity
- [ ] Follows KISS principle

### Constraint Rule Check
- [ ] No prohibited dependencies used
- [ ] No any types used
- [ ] Errors correctly thrown
- [ ] Log format is unified

### Performance and Security Check
- [ ] No unnecessary database queries
- [ ] Input validation is sufficient
- [ ] Sensitive information properly handled
- [ ] API responses are reasonable

3. Automated Mechanism for Refactoring

Solution: Automated Architecture Guardian

// scripts/archural-guard.ts
import fs from 'fs'
import path from 'path'

interface ArchitecturalRule {
  name: string
  check: (filePath: string, content: string) => boolean
  message: string
}

const rules: ArchitecturalRule[] = [
  {
    name: 'file-size-limit',
    check: (filePath, content) => {
      const lines = content.split('\n').length
      return lines <= 1500
    },
    message: 'File exceeds 1500 lines limit'
  },
  {
    name: 'no-any-types',
    check: (filePath, content) => {
      return !content.includes(': any') && !content.includes('as any')
    },
    message: 'Found usage of any type'
  },
  {
    name: 'required-imports',
    check: (filePath, content) => {
      if (filePath.endsWith('.ts')) {
        return content.includes('import traceback')
      }
      return true
    },
    message: 'Missing import traceback statement'
  },
  {
    name: 'error-handling',
    check: (filePath, content) => {
      if (filePath.includes('service')) {
        return content.includes('logger.exception')
      }
      return true
    },
    message: 'Service files must use logger.exception for error handling'
  }
]

function checkArchitecture() {
  const srcDir = './src'
  const files = getAllTypeScriptFiles(srcDir)
  let hasViolations = false

  files.forEach(file => {
    const content = fs.readFileSync(file, 'utf8')

    rules.forEach(rule => {
      if (!rule.check(file, content)) {
        console.error(`โŒ [${rule.name}] ${file}: ${rule.message}`)
        hasViolations = true
      }
    })
  })

  if (hasViolations) {
    console.error('\nโŒ Architecture check failed!')
    process.exit(1)
  } else {
    console.log('โœ… Architecture check passed!')
  }
}

checkArchitecture()

๐ŸŽฏ Practical Cases: Architecture Evolution in Tidepool Notes

Case 1: Message System Architecture Refactoring

Initial State (Result of AI Free Expression):

// Complex message system generated by AI
class MessageManager {
  // Support multiple message types
  async createTextMessage(data: TextMessageData): Promise<TextMessage>
  async createImageMessage(data: ImageMessageData): Promise<ImageMessage>
  async createVoiceMessage(data: VoiceMessageData): Promise<VoiceMessage>
  async createVideoMessage(data: VideoMessageData): Promise<VideoMessage>

  // Complex message processing flow
  async processMessage(message: Message): Promise<ProcessedMessage> {
    await this.validateMessage(message)
    await this.transformMessage(message)
    await this.enrichMessage(message)
    await this.routeMessage(message)
    await this.indexMessage(message)
    await this.cacheMessage(message)
    return this.formatMessage(message)
  }

  // Excessive error handling
  async handleError(error: Error, context: ErrorContext): Promise<ErrorResult> {
    // 50 lines of complex error classification and handling logic
  }
}

Simplification after Applying Architecture Constraints:

// Constrained message system
export class MessageService {
  constructor(
    private messageRepository: MessageRepository,
    private logger: Logger
  ) {}

  async create(data: CreateMessageData): Promise<Message> {
    try {
      const message = await this.messageRepository.create(data)
      this.logger.info(`Message created: ${message.id}`)
      return message
    } catch (error) {
      this.logger.exception('Failed to create message', error)
      throw error
    }
  }

  async findBySessionId(sessionId: string): Promise<Message[]> {
    return await this.messageRepository.findBySessionId(sessionId)
  }
}

Effects of Architecture Constraints:

  • Code lines reduced from 800 to 150 lines
  • Maintenance cost reduced by 70%
  • AI generation consistency improved by 90%
  • New feature development speed increased by 50%

Case 2: Standardization of AI Integration

AI Free Expression's Complex Solution:

// AI tends to over-engineer LLM integration
class LLMOrchestrator {
  constructor() {
    this.promptManager = new ComplexPromptManager()
    this.conversationManager = new ConversationStateManager()
    this.responseParser = new MultiFormatResponseParser()
    this.errorHandler = new RetryWithBackoffHandler()
    this.cacheManager = new MultiLevelCacheManager()
  }

  async generateResponse(input: UserInput): Promise<AIResponse> {
    // Complex multi-layer processing logic
    const context = await this.conversationManager.getContext(input)
    const prompt = await this.promptManager.buildPrompt(context, input)
    const rawResponse = await this.callLLMWithRetry(prompt)
    const parsedResponse = await this.responseParser.parse(rawResponse)
    const enrichedResponse = await this.enrichResponse(parsedResponse)
    await this.cacheManager.store(input, enrichedResponse)
    return enrichedResponse
  }
}

Standardization after Architecture Constraints:

// Standardized LLM integration
export class LLMService {
  constructor(
    private openaiClient: OpenAIClient,
    private logger: Logger
  ) {}

  async generateSuggestion(sessionData: SessionData): Promise<string> {
    try {
      const prompt = this.buildPrompt(sessionData)
      const response = await this.openaiClient.chat.completions.create({
        model: 'gpt-4',
        messages: [{ role: 'user', content: prompt }],
        temperature: 0.7,
        max_tokens: 1000
      })
      return response.choices[0].message.content
    } catch (error) {
      this.logger.exception('Failed to generate suggestion', error)
      throw error
    }
  }

  private buildPrompt(sessionData: SessionData): string {
    return `Based on user session data, provide emotional suggestions: ${JSON.stringify(sessionData)}`
  }
}

Case 3: Automation of Architecture Guardian

Implemented Automated Checks:

// Automatically executed before each commit
{
  "scripts": {
    "pre-commit": "npm run type-check && npm run lint && npm run arch-check",
    "arch-check": "ts-node scripts/architectural-guard.ts",
    "type-check": "tsc --noEmit",
    "lint": "eslint src --ext .ts"
  }
}

// Architecture checks in CI/CD pipeline
name: Architecture Check
on: [push, pull_request]
jobs:
  check-architecture:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
      - run: npm install
      - run: npm run arch-check
      - run: npm run test

๐ŸŒŸ Summary: Core Principles of Architecture as Code

Three Levels of Architectural Thinking

1. Project-Level Architecture (Top-Level Design)

  • Clear technology stack constraints
  • Standardized file structure
  • Strict dependency management rules
  • Unified development process

2. Code-Level Architecture (Middle-Level Control)

  • Triple constraints for function design
  • Unified standards for error handling
  • Simplicity principles for class design
  • Consistency of interface design

3. Process-Level Architecture (Bottom-Level Guarantee)

  • Standardized development process
  • Mandatory code reviews
  • Automated architecture guardians
  • Continuous quality control

Steps to Implement Architecture as Code

First Step: Establish Architecture Documentation

  • Create detailed Claude.md
  • Define clear constraint rules
  • Provide specific code templates
  • Create quality check checklists

Second Step: Implement Automated Guardians

  • Create architecture check scripts
  • Integrate into CI/CD processes
  • Set up code quality gates
  • Establish continuous monitoring mechanisms

Third Step: Continuously Optimize Architecture

  • Regularly review architecture effectiveness
  • Adjust rules based on actual problems
  • Optimize automated check mechanisms
  • Share architectural practice experiences

Key Success Factors

1. Clarity of Constraints

  • Every rule must be specific and executable
  • Provide positive and negative examples
  • Regularly check rule effectiveness
  • Update outdated constraints promptly

2. Automation of Tools

  • Reduce reliance on manual checks
  • Integrate into development workflow
  • Provide clear error feedback
  • Ensure fast execution of checks

3. Sustainability of Culture

  • Team consensus on architectural constraints
  • Continuous architectural thinking training
  • Regular architecture review meetings
  • Open problem feedback mechanisms

Remember: The core of AI programming is not "what to let AI do," but "what not to let AI do." Good architecture is to draw clear boundaries for AI, allowing it to create value within controllable ranges.


Next Article Preview: Prompt Engineering as Architecture: How to Make AI Understand Your Real Needs


Search "ๅ››ๅ“ฅ่ฟ˜ไผš่ŠAI" on WeChat to see how these architecture principles create value in actual projects

Search "ๆฑๅฑฟ็ฌ”่ฎฐ" on WeChat to see how these architectural principles are applied in real projects

โ† Back to Home