# Langflow Development Guide (Example) > **This is an EXAMPLE file.** Use at your own risk. > It is provided as a reference template for development standards and coding conventions. > Adapt it to your project's needs before adopting. No guarantees are made about its completeness or suitability for any specific use case. > Language-agnostic. Framework-agnostic. --- ## Table of Contents 1. [Core Philosophy](#1-core-philosophy) 2. [Design Principles](#2-design-principles) 3. [Code Quality](#3-code-quality) 4. [Architecture](#4-architecture) 5. [File Structure](#5-file-structure) 6. [Error Handling](#6-error-handling) 7. [Security](#7-security) 8. [Observability](#8-observability) 9. [Testing](#9-testing) 10. [Code Review](#10-code-review) 11. [Documentation](#11-documentation) 12. [Pre-Delivery Checklist](#12-pre-delivery-checklist) --- ## 1. Core Philosophy ### Trade-off Priority (when conflicts arise) 1. **Correctness** — Code does what it should 2. **Simplicity and readability** — Code is easy to understand 3. **Testability** — Code is easy to test 4. **Performance** — Code is fast enough 5. **Abstraction and reuse** — Code is DRY ### Ground Rules - Read and understand existing code before modifying it. - Follow the project's existing patterns and conventions. - If a requirement is ambiguous, ask before writing code. - Prefer incremental delivery: core logic first, then edge cases, then refinements. - Do not overengineer. Build for today's requirements, not hypothetical future ones. --- ## 2. Design Principles ### SOLID | Principle | Rule | Common Mistake | |-----------|------|----------------| | **SRP** — Single Responsibility | Each class/function/file has ONE reason to change. If you need "and" or "or" to describe it, split it. | Interpreting SRP as "one function per class." SRP means one *axis of change*. | | **OCP** — Open/Closed | Add new behavior by writing new code, not modifying existing code. Use polymorphism or strategy patterns where change is expected. | Over-engineering with premature abstractions. Apply OCP where you have *evidence* of changing requirements. | | **LSP** — Liskov Substitution | Subclasses must honor the contract of their parent. Prefer composition over inheritance when "is-a" is not strict. | Overriding a method to throw `NotImplementedError` or do nothing. | | **ISP** — Interface Segregation | Define small, role-specific interfaces. Clients depend only on methods they use. | Creating one "service" interface with 15+ methods. | | **DIP** — Dependency Inversion | Depend on abstractions at module boundaries, not concrete implementations. Domain logic must never import from infrastructure. | Confusing DIP with "just use dependency injection." DIP is about inverting the *direction of source-code dependency*. | ### DRY — Don't Repeat Yourself - Extract shared logic when the *exact same business rule* is duplicated in 3+ places (Rule of Three). - Single source of truth for configuration, constants, and schema definitions. - **Prefer duplication over wrong abstraction.** Two pieces of code that look similar but serve different business purposes are NOT duplication — merging them creates accidental coupling. - "Wrong abstraction" means: premature generalization, unclear purpose, or coupling unrelated concerns. ### KISS — Keep It Simple - Choose the simplest implementation that satisfies current requirements. - Prefer standard library solutions over custom implementations. - A plain function call beats metaprogramming. A dictionary beats a class when all you need is data grouping. - Do not add design patterns, abstractions, or frameworks "just in case." ### YAGNI — You Aren't Gonna Need It - Implement features only when there is a concrete, current requirement. - Do not build generic/extensible frameworks before you have at least two concrete use cases. - Delete speculative code and unused feature flags regularly. - Three similar lines of code is better than a premature abstraction. --- ## 3. Code Quality ### Naming - Use clear, meaningful, intention-revealing names. The name should answer *why* it exists and *what* it does. - Functions use verbs: `get`, `create`, `update`, `delete`, `validate`, `format`, `parse`. - Booleans use prefixes: `is`, `has`, `can`, `should`. - No abbreviations unless universally understood (`id`, `url`, `api`). - No generic names: `data`, `result`, `obj`, `thing`, `temp`, `misc`, `utils`. - No names with "and", "or", "then" — that signals multiple responsibilities. ### Strong Typing - Use strong typing everywhere. Avoid `any`, `object`, `dynamic`, `Object`. - Use typed parameters and return types for all public functions. - Never cast to `any` just to make something compile. ### Immutability - Default to immutable. Use `const`, `readonly`, `final`, `frozen`, `tuple`, `frozenset`. - Return new objects from transformation functions instead of mutating inputs. - Never expose mutable internal collections. Return copies or read-only views. - Mutable local variables inside a function are fine — mutable *shared state* is the danger. ### Early Returns and Guard Clauses - Validate preconditions at the top of functions and return/throw early. - Reduce nesting by inverting conditions and returning early. - Keep the "happy path" at the lowest indentation level. ### No Magic Values - Extract repeated numbers and strings to named constants. - Use descriptive variable names instead of inline literals. ### Comments - Do not comment obvious code. Prefer self-explanatory code through good naming. - Comments explain **WHY**, never **WHAT**. - No commented-out code — use version control. - No TODO comments without ticket references. ### Functions - Keep functions short with a single level of abstraction. - One function does one thing. If it does two things, split it. - Do not use boolean parameters that switch behavior — split into two named functions. - Eliminate dead code and unused imports on every change. --- ## 4. Architecture ### Separation of Concerns - Separate domain, application, and infrastructure concerns. - Domain/business logic must have zero imports from frameworks, databases, or HTTP layers. - Keep side effects (I/O, logging, metrics) at the edges. Business logic should be pure. - Use DTOs or value objects at layer boundaries — never pass ORM models or HTTP request objects into business logic. ### Layer Rules | Layer | CAN | CANNOT | |-------|-----|--------| | **Handler/Controller** | Receive input, delegate to service, return output | Contain business logic, call DB directly | | **Service/Orchestrator** | Coordinate operations, apply business rules | Know about HTTP/transport, execute SQL directly | | **Repository/Data Access** | Execute queries, map data | Make business decisions, call external APIs | | **Helper** | Transform data, validate, format | Have side effects, do I/O, maintain state | | **External Client** | Communicate with external services | Contain business logic, access database | ### Dependency Injection - Inject dependencies through constructors or method parameters. Make all dependencies explicit. - Inject I/O boundaries (database, HTTP clients, filesystem, clock) so they are swappable in tests. - Keep the composition root at the application entry point, separate from business logic. - If a class needs more than ~4 injected dependencies, it is doing too much — split it. - Only inject things that have *side effects* or *vary between environments*. Do not inject pure utility functions. ### DDD (When Justified) - Apply DDD concepts only if the domain complexity clearly justifies it. - Keep domain logic independent from frameworks and infrastructure. - Use Entities, Value Objects, and Aggregates only when they add real value. - Model errors and invariants as part of the domain. --- ## 5. File Structure ### Limits Per File (Production Code) | Metric | Guideline | |--------|-----------| | Lines of code (excluding imports, types, docs) | **~500 lines** (up to ~530 OK; 600+ is a red flag) | | Functions with DIFFERENT responsibilities | **5 functions max** | | Functions with SAME responsibility (same prefix) | **10 functions max** | | Main classes per file | **1 class** | | Small related classes (exceptions, DTOs, enums) | **5 classes** (if all same type) | ### Single Responsibility Per File Every file MUST have **one reason to exist** and **one reason to change**. **The Test:** Can you describe this file's purpose in ONE sentence WITHOUT using "and" or "or"? ### Separation by Responsibility Functions MUST be grouped by responsibility category. **Functions with DIFFERENT prefixes MUST NOT coexist in the same file.** | Responsibility | Function Prefixes | Separate File | |----------------|-------------------|---------------| | **Types/Models** | Type definitions, interfaces, classes without logic | `{feature}_types` | | **Constants** | `MAX_*`, `DEFAULT_*`, enums | `{feature}_constants` | | **Validation** | `validate*`, `check*`, `is_valid*` | `validation` | | **Formatting** | `format*`, `build*`, `serialize*`, `to_*` | `formatting` | | **Parsing** | `parse*`, `extract*`, `from_*` | `parsing` | | **External calls** | `fetch*`, `send*`, `call*`, `request*` | `{service}_client` | | **Data access** | `save*`, `load*`, `find*`, `delete*`, `query*` | `{feature}_repository` | | **Orchestration** | Main entry points, coordination | `{feature}_service` | | **Handlers** | Endpoints, controllers, views | `{feature}_handler` | ### Avoid Over-Engineering - Do NOT create a separate file for 1-2 trivial functions with less than 20 lines total. - Private helpers (`_func`) stay in the file that uses them. - One-liner utilities are not extracted to separate files. - Split when you have clear, reusable responsibilities. Keep together when separation adds complexity without benefit. ### File Naming - **NEVER** use generic names: `utils`, `helpers`, `misc`, `common`, `shared` as standalone files. - Follow the project's existing naming convention. ### Module Structure ``` feature/ ├── {feature}_service # Orchestration ├── {feature}_types # Type definitions ├── {feature}_constants # Constants and enums ├── helpers/ │ ├── validation # ONLY validation functions │ ├── formatting # ONLY formatting functions │ └── parsing # ONLY parsing functions ├── services/ │ └── {external}_client # ONLY external API communication ├── repositories/ │ └── {feature}_repository # ONLY data persistence └── handlers/ └── {feature}_handler # ONLY request handling ``` --- ## 6. Error Handling - Handle expected errors explicitly. No silent failures. - Do not use generic exceptions (`Exception`, `Error`, `object`). Use domain-relevant error types. - Return or throw errors with meaningful context (what failed, what input caused it, how to fix it). - Errors are part of the API contract. - Validate inputs at system boundaries. Fail fast on invalid data. - Distinguish between recoverable errors and fatal exceptions. - Never silently coerce or fix invalid input — reject with a clear message. ```python # BAD try: result = do_something() except: pass # GOOD try: result = do_something() except ValidationError as e: logger.warning("Validation failed", extra={"error": str(e), "field": e.field}) raise DomainError(f"Invalid input: {e.field}") from e ``` --- ## 7. Security - Sanitize and validate all user and external inputs at the boundary. - Never trust data from outside the system boundary. - Use allowlists, not denylists. Reject by default, accept only known-good patterns. - Use schema validation libraries (Pydantic, zod, JSON Schema) — do not hand-roll validation for complex structures. - Keep secrets out of code. Use environment variables or secret managers. - No hardcoded API keys, tokens, or passwords. - SQL queries use parameterized statements — no string concatenation. - Do not expose internal details in error messages to end users. - Validate on the server side always — client-side validation is a UX convenience, not a security measure. - Use fake/anonymized data in tests — never real user data. --- ## 8. Observability ### Logging - Use structured logging (key-value / JSON), not formatted strings. - Log at key decision points and boundaries, not inside tight loops. - Include: operation name, relevant IDs, outcome (success/failure), duration if relevant. - Use consistent field names across the entire codebase. ### Log Levels | Level | When to Use | |-------|-------------| | **ERROR** | Something is broken and needs human attention | | **WARN** | Degraded but self-recoverable | | **INFO** | Significant business events | | **DEBUG** | Diagnostic detail, off in production | ### PII in Logs — ZERO TOLERANCE - **NEVER** log: email addresses, user names, phone numbers, physical addresses, tokens, passwords. - **Approved identifiers**: `auth_id`, `user_id`, `internal_id`. - No `print()` / `console.log()` with user data — these go to production logs. --- ## 9. Testing > **Test code is production code.** It receives the same care, review, and quality standards. ### Core Principles - Write unit tests for all core logic. - Follow Arrange-Act-Assert (AAA) structure. ONE act per test, ONE logical assertion per test. - Tests MUST be independent, deterministic, and not depend on execution order. - Mock or fake all external dependencies (DB, APIs, filesystem, time, randomness). - Name tests clearly: `should_[expected]_when_[condition]`. ### Tests MUST Also Challenge the Code — Not Only Confirm It **Happy path tests are the foundation** — they validate the code works under normal conditions. Always start with these. **But happy path tests ALONE are not enough.** You MUST also write adversarial tests that actively try to break the code and find defects: - Unexpected input types: `None`, `""`, `[]`, `{}`, `0`, `-1` - Boundary values: max int, max length, exactly at the limit, one past the limit - Malformed data: missing fields, extra fields, wrong types, invalid formats - Error states: what happens when dependencies fail? - What should NOT happen: verify that forbidden states are correctly rejected - Error messages and types: not just that it fails, but *how* it fails **Write tests based on REQUIREMENTS/SPEC, not on what the source code currently does.** This is how you catch bugs where the code diverges from expected behavior. **When a test fails:** first ask if the CODE is wrong, not the test. Do NOT silently change a failing assertion to match the current code without understanding WHY. ### Test File Rules | Metric | Guideline | |--------|-----------| | Lines per file | **~1000 lines** guideline — above this, consider splitting, but not required if covering a single module | | Tests per file | No hard limit — split only when covering **unrelated behaviors** | | Setup (Arrange) | **~20 lines max** per test (extract to helpers/factories if exceeded) | **Split test files based on LOGICAL SEPARATION, not arbitrary line counts.** One file per module/service is perfectly fine, even at 800+ lines. ### Coverage - **Target: 80%. Minimum acceptable: 75%.** Below 75% the task is not complete. - Focus on **branch coverage** (both sides of `if/else`, all `catch` blocks), not just line coverage. - High coverage with no assertions is worthless. Every test MUST have at least one meaningful assertion. - Coverage must be **run and shown** at the end for ALL created tests (backend AND frontend). ```bash # Python pytest tests/your_tests.py --cov=src/module_under_test --cov-report=term-missing --cov-branch -v # JavaScript/TypeScript (Jest) npx jest tests/your_tests.test.ts --coverage --collectCoverageFrom="src/module/**/*.{ts,tsx}" # JavaScript/TypeScript (Vitest) npx vitest run tests/your_tests.test.ts --coverage ``` ### All Created Tests MUST Pass - Every test you create or modify MUST pass. Zero failures. Zero exceptions. - Never disable, skip, or delete a test to hide a failure. - Never leave a test "to fix later" — fix it NOW. - If coverage is below 75%: write more tests, re-run, repeat until the minimum is met. ### What NOT to Test - Simple getters, setters, trivial mappers — not worth testing. - Implementation details (method call order, internal state) — test behavior instead. - Do not inflate coverage with meaningless assertions. ### Anti-Patterns (Forbidden) | Pattern | Problem | |---------|---------| | **The Liar** | Test passes but doesn't verify the behavior it claims to test | | **The Mirror** | Test reads the source code and asserts exactly what the code does — finds zero bugs | | **The Giant** | 50+ lines of setup, multiple acts, dozens of assertions — should be 5+ separate tests | | **The Mockery** | So many mocks that the test only tests the mock setup | | **The Inspector** | Coupled to implementation details, breaks on any refactor | | **The Chain Gang** | Tests depend on execution order or share mutable state | | **The Flaky** | Sometimes passes, sometimes fails with no code changes | --- ## 10. Code Review ### Priority (blockers first) 1. **Security & PII** — No PII in logs, no hardcoded secrets, input validation 2. **DRY** — No duplicate types, classes, functions, or logic 3. **File Structure** — Limits respected, responsibilities separated 4. **Architecture** — Single responsibility, proper layer separation 5. **Code Quality** — SOLID, strong typing, error handling 6. **Testing** — Both happy path AND adversarial tests, coverage met 7. **Observability** — Structured logging, no PII ### Review Questions for Tests 1. "Are there BOTH happy path AND adversarial tests?" 2. "Would these tests catch a regression if someone broke the logic?" 3. "Are there edge cases or failure modes that aren't being tested?" 4. "If I remove a line of business logic, will at least one test fail?" ### Legacy Code - Do NOT prolong bad patterns — even if surrounding code is bad, write good code. - Do NOT copy-paste from legacy code without reviewing quality. - Isolate new code from legacy where possible. --- ## 11. Documentation ### When to Document - Generate feature documentation after implementation is complete. - Documentation lives alongside code in the repository (Markdown). - Use ubiquitous language — same terms in docs, code, and communication. ### Documentation Levels (C4 Model) | Level | Audience | Content | |-------|----------|---------| | **Context (L1)** | Product / Stakeholders | System in its environment | | **Container (L2)** | Both | Applications, databases, queues | | **Component (L3)** | Engineering | Internal service details | ### Required Sections for Feature Docs 1. **Overview** — Summary, business context, bounded context 2. **Ubiquitous Language Glossary** — Domain terms with code references 3. **Domain Model** — Aggregates, entities, value objects, events 4. **Behavior Specifications** — Gherkin scenarios (happy path, edge cases, errors) 5. **Architecture Decision Records** — Context, decision, consequences 6. **Technical Specification** — Dependencies, API contracts, error codes 7. **Observability** — Metrics, logs, dashboards 8. **Deployment & Rollback** — Feature flags, migrations, rollback plan --- ## 12. Pre-Delivery Checklist **BEFORE delivering ANY code, verify ALL items.** ### Critical (Blockers) - [ ] No PII in any logs, prints, or webhook messages - [ ] No secrets or credentials in code - [ ] No duplicate types, classes, or logic (DRY) - [ ] No file exceeds ~500 lines (production code) or ~1000 lines (test code) - [ ] No mixed responsibility prefixes in same file - [ ] All user inputs validated at system boundaries ### Important (Must Fix) - [ ] Each file/function has single responsibility - [ ] Proper error handling (no silent failures, meaningful errors) - [ ] Strong typing (no `any`, `object`, `dynamic`) - [ ] Types in dedicated types file, constants in dedicated constants file - [ ] Domain logic independent from frameworks/infrastructure ### Testing (Mandatory) - [ ] Unit tests for all core logic - [ ] Both happy path AND adversarial tests exist - [ ] All created/modified tests pass — zero failures - [ ] Coverage report ran and output shown (backend AND frontend) - [ ] Coverage >= 75% minimum (target 80%) - [ ] No test anti-patterns (Liar, Mirror, Giant, Mockery, Inspector) ### Quality (Should Fix) - [ ] Structured logging at key decision points - [ ] Comments explain WHY, not WHAT - [ ] No over-engineering (no files with 1-2 trivial functions) - [ ] No legacy bad patterns prolonged ### Pre-Commit - [ ] Linter ran on all changed files — zero errors - [ ] Formatter ran on all changed files — zero diffs - [ ] Type checker ran (if applicable) — zero errors --- > **This guide applies to every line of code in the Langflow project.** > **When in doubt, choose simplicity. When trade-offs arise, follow the priority order in Section 1.** > **Build for correctness first. Optimize later. Test always.**