Skip to content

Instantly share code, notes, and snippets.

@gunzip
Created February 23, 2026 17:16
Show Gist options
  • Select an option

  • Save gunzip/297b40747a416fea5109f5bdf822f8e6 to your computer and use it in GitHub Desktop.

Select an option

Save gunzip/297b40747a416fea5109f5bdf822f8e6 to your computer and use it in GitHub Desktop.
name description
dx-principles
DevEx Principles: Fundamental guidelines for coding agents based on analysis of DevEx team code reviews and comments. These principles represent the core values and patterns that drive architecture decisions and code quality in the PagoPA DX initiative.

DevEx Fundamental Principles

Overview

This skill document captures the fundamental coding and architecture principles that emerge from the DevEx team's code review patterns and comments. These principles guide the creation of high-quality, maintainable, and secure code within the PagoPA DX initiative.

Analysis Basis: Mining of 43 substantive comments from 6 core team members (gunzip, lucacavallaro, mamu0, Krusty93, christian-calabrese, kin0992) across PRs and issue discussions in the dx repository.


1. ⚙️ Configuration Philosophy: Arguments Over Environment Variables

Principle: For single-run inputs in scripts and GitHub Actions, prefer explicit arguments/parameters over environment variables.

Rationale:

  • Environment variables are suitable for configuration that persists across multiple runs
  • Arguments make intent explicit and single-run purpose clear
  • Arguments are more discoverable and self-documenting

Application:

  • GitHub Actions: Use action inputs instead of requiring environment variables to be set before invocation
  • Shell scripts: Define explicit parameters rather than relying on ENV vars for transient values
  • Terraform modules: Expose inputs as variable blocks, not global environment configuration

Example Pattern:

# ❌ Improper: Relies on ENV var for single-run input
script.sh  # expects OPEX_VERSION to be set

# ✅ Proper: Explicit argument
script.sh --opex-version=1.2.3

# GitHub Actions
# ❌ Improper:
env:
  OPEX_VERSION: 1.2.3

# ✅ Proper:
with:
  opex-version: '1.2.3'

2. 🔒 Semantic Versioning and Immutability

Principle: Always pin versions precisely; never allow latest or semantic version ranges in production dependencies.

Rationale:

  • Floating versions (latest, ~, ^) introduce non-determinism and supply-chain risk
  • Exact versions ensure reproducible builds and deployments
  • Security: Compromised registries can't silently inject updates

Application:

  • Terraform modules: Lock required_version and module versions to exact semver (e.g., ~> 2.0 at most, specifying major and minor only)
  • GitHub Actions: Always use pinned tags/SHAs, never @v1 or @main
  • NPM dependencies: Use exact versions or conservative ranges (~1.2.3)
  • MCP/Tool versions: Version alongside related packages for coherent evolution

Pattern:

# ❌ Improper
terraform {
  required_version = ">= 1.0"  # Too loose
}

# ✅ Proper
terraform {
  required_version = "~> 1.6"  # Major.minor pinned, patches allowed
}

3. 🎯 Minimize Input Parameters

Principle: Keep the number of configurable parameters as small as possible; establish sensible defaults.

Rationale:

  • Reduces cognitive load and implementation surface area
  • Fewer parameters = fewer failure modes and simpler testing
  • Makes tools more usable; users don't have to understand all options
  • Decreases documentation burden

Application:

  • Terraform modules: Provide limited, essential variables with intelligent defaults
  • GitHub Actions: Keep inputs list focused on truly configurable aspects
  • CLI tools: Prefer convention over configuration; make common paths automatic
  • API endpoints: Provide reasonable defaults; expose advanced options only when necessary

Pattern:

# ❌ Improper: Too many optional parameters
variable "enable_monitoring" { default = false }
variable "enable_logging" { default = false }
variable "enable_alerts" { default = false }
variable "log_level" { default = "INFO" }
variable "retention_days" { default = 30 }
# ... 10+ more variables

# ✅ Proper: Unified, sensible defaults
variable "enable_observability" {
  description = "Enable monitoring, logging, and alerting with defaults"
  default = true
}

4. 🏛️ Layered Architecture and Abstraction Boundaries

Principle: Organize code in layers; each layer should only depend on the layer below it. Never leak implementation details upward.

Rationale:

  • Clear separation of concerns makes code easier to reason about
  • Prevents tight coupling and unintended dependencies
  • Implementation changes don't cascade to higher layers
  • Testability improves with isolated layers

Architecture Pattern:

3. Adapters/Infrastructure (external concerns: HTTP, databases, cloud providers)
2. Application/Use Cases (business logic and orchestration)
1. Domain (core entities and types)

Anti-Pattern:

  • Use cases exposing cloud provider details (e.g., AzureResourceGroup in domain)
  • Controllers calling repositories directly; skipping business logic layer
  • Infrastructure concerns (connection strings, API versions) bleeding into domain

Example:

// ❌ Leaked implementation detail
interface User {
  id: string;
  azureAdId: string; // Infrastructure detail in domain
}

// ✅ Proper abstraction
interface User {
  id: string;
  externalId: string; // Agnostic, can be any provider
}
// Mapping happens in adapters/

5. 📋 Comprehensive Documentation and Examples

Principle: Document not just code, but intent, use cases, and gotchas. Provide runnable examples.

Rationale:

  • Reduces onboarding time and support burden
  • Communicates design decisions and constraints
  • Examples demonstrate correct usage patterns

Application:

  • Terraform modules: Include README with use cases, examples/, and parameter descriptions
  • APIs: Document not just endpoints, but when and why to use each one
  • GitHub Actions: Explain inputs, outputs, and provide usage examples
  • CLI: Include rich help text and example commands

Minimum Documentation:

  1. Purpose: What problem does this solve?
  2. Use Cases: When should this be used? When shouldn't it?
  3. Examples: Minimal reproducible example and real-world example
  4. Gotchas: Edge cases, limitations, security considerations
  5. Related: Links to related tools/modules

6. 🔐 Security First: Sensitive Data Handling

Principle: Treat secrets and sensitive configurations as first-class concerns. Use proper mechanisms throughout the lifecycle.

Rationale:

  • Secrets in plaintext logs or state files create vulnerabilities
  • Supply-chain attacks are a real threat
  • Compliance and organizational policy require proper handling

Application:

  • GitHub Actions: Use GitHub Actions Secrets, never hardcode placeholders
  • Terraform: Use sensitive = true for outputs; externalize secret sources
  • Environment Variables: Only for non-sensitive config
  • Type System: Make secret status part of the type signature

Pattern:

# ❌ Improper
variable "webhook_url" {
  default = "placeholder"  # Will overwrite real secret in Terraform
}

# ✅ Proper
variable "webhook_url" {
  type      = string
  sensitive = true
  # No default; sourced from external secret manager
}

7. 🧪 Testing and Type Safety

Principle: Maximize static guarantees and test coverage. Use strong typing and validation. Test edge cases.

Rationale:

  • Type systems catch errors at author time, not runtime
  • Tests validate assumptions and prevent regressions
  • Failures are caught early and cheaply

Application:

  • TypeScript: Strictly enforce typing; avoid any and as type assertions
  • Terraform: Validate variable types strictly; use validation blocks for constraints
  • Testing: Test happy path, edge cases, error paths, and integration points
  • Type Assertions: Avoid as which bypasses type checking; use type guards instead

Pattern:

// ❌ Improper: Bypasses type checking
const value = something as string;

// ✅ Proper: Type guard
if (typeof something === "string") {
  // TypeScript now knows it's a string
}

8. 🔧 Functional Programming Patterns

Principle: Prefer functional composition and declarative patterns over imperative control flow.

Rationale:

  • Functional patterns are easier to reason about and test
  • Declarative code is more readable and less error-prone
  • Pure functions improve composability and reusability

Application:

  • Use andThen / chain instead of if/else for option/result handling
  • Prefer map, filter, reduce over imperative loops
  • Embrace immutability; avoid mutation-based state management

Pattern:

// ❌ Improper: Imperative with side effects
if (result.isOk()) {
  const value = result.value;
  return transform(value);
} else {
  return defaultValue;
}

// ✅ Proper: Functional composition
result.andThen((value) => transform(value)).getOrElse(defaultValue);

9. 📦 Unified Versioning for Related Components

Principle: Components that are released together should version together.

Rationale:

  • Prevents version mismatch bugs
  • Simplifies dependency management
  • Clear contract and compatibility guarantees

Application:

  • GitHub Actions and NPM packages: If tightly coupled, use the same version number
  • Terraform modules and documentation: Version together if they evolve in lockstep
  • CLI and inline scripts: Keep versions synchronized

Pattern:

# ✅ Good: Action versions match NPM package
# opex-dashboard package: v2.1.0
# opex-dashboard-generate action: v2.1.0
# Both released and versioned together via changeset

10. 🧩 Implementation Abstraction: Details in Adapters

Principle: Hide implementation details (cloud providers, HTTP libraries, etc.) behind abstraction layers.

Rationale:

  • Switching providers/implementations becomes easier
  • Core logic remains portable and testable
  • Reduces cognitive load in business logic code

Application:

  • Create adapter interfaces for external dependencies
  • Business logic depends on interfaces, not concretions
  • Multiple implementations can exist; testing uses mocks

Pattern:

// Domain/Application layer
interface AuthorizationService {
  authorize(user: User, resource: Resource): Promise<boolean>;
}

// Adapters layer
class AzureAuthorizationAdapter implements AuthorizationService {
  // Azure-specific implementation
}

class MockAuthorizationAdapter implements AuthorizationService {
  // Testing implementation
}

11. 🔍 Terraform Registry and Official Tools

Principle: When documentation for Terraform modules exists, use official HashiCorp MCP tools to retrieve authoritative information.

Rationale:

  • Terraform Registry is the source of truth
  • Official tools ensure accuracy and reduce hallucinations
  • API is reliable and well-maintained

Application:

  • When documenting module parameters: Query Terraform Registry API
  • When showing examples: Use official .tf files from module repositories
  • For updates: Always check Registry for latest versions and changes

Pattern:

# ✅ Proper
Use terraform-registry MCP tool → query module metadata → provide user accurate parameter documentation

# ❌ Improper
Assume or generate module documentation without consulting Registry

12. 🎨 Naming and Clarity

Principle: Names should be descriptive and self-documenting. Avoid abbreviations unless widely understood.

Rationale:

  • Code is read more often than written
  • Clear names reduce need for comments
  • Misnamed things create bugs and confusion

Application:

  • Variables: Use full words (containerAppEnvironment not cae)
  • Functions: Name should describe what it does (validateConfiguration not check)
  • Modules: Clear purpose (azure_container_app not acr)

13. 🚀 Supply Chain Security

Principle: Minimize the attack surface of your supply chain. Treat external dependencies with caution.

Rationale:

  • Compromised dependencies can execute arbitrary code
  • Updates from "trusted" sources can be malicious
  • The more dependencies, the larger the surface area

Application:

  • Pin all versions (see Principle 2)
  • Review dependency changes before merging
  • Minimize number of external dependencies
  • Be cautious of @latest and auto-update mechanisms
  • Monitor security advisories for used packages

Implementation Checklist for Coding Agents

When reviewing or generating code, verify:

  • Configuration uses arguments/inputs, not environment variables (for single-run)
  • All versions are pinned to exact or conservative ranges (no latest)
  • Parameter count is minimized with sensible defaults
  • Architecture respects layering; no implementation detail leakage
  • Documentation includes purpose, use cases, examples, and gotchas
  • Sensitive data uses proper mechanisms (sensitive = true, secrets management)
  • Type system is leveraged; no unnecessary any or as assertions
  • Control flow favors functional patterns over imperative if/else
  • Related components are versioned together
  • Implementation details are hidden in adapters/
  • Terraform documentation sources from Registry when possible
  • Names are clear and descriptive
  • Supply chain risks are minimized (pinned versions, few dependencies)

Related Patterns and Decisions

See also:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment