This document outlines the best practices and standards for managing and upgrading dependencies across Bayat projects.
- Introduction
- Dependency Management Principles
- Dependency Selection Criteria
- Versioning and Constraints
- Dependency Evaluation Framework
- Dependency Inventory and Auditing
- Update Strategies
- Security Vulnerability Management
- Breaking Changes Management
- Automated Tooling
- Language-Specific Guidelines
- Monorepo Dependency Management
- Dependencies in CI/CD
- Dependency Documentation
- Development Dependencies vs. Production Dependencies
- Self-Hosted vs. Third-Party Services
- Legacy Project Considerations
- Case Studies
- Appendix: Useful Tools
Effective dependency management is crucial for maintaining secure, stable, and maintainable software projects. This document provides guidelines for selecting, managing, upgrading, and auditing dependencies in a consistent and efficient manner.
- Security: Outdated dependencies often contain known vulnerabilities
- Performance: Newer versions often include performance improvements
- Features: Stay current with latest features and capabilities
- Compatibility: Ensure continued compatibility with other components
- Technical Debt: Prevent accumulation of debt from outdated dependencies
- Support: Vendors eventually drop support for older versions
- Minimalism: Use as few dependencies as necessary
- Intentionality: Every dependency should serve a clear purpose
- Sustainability: Prefer actively maintained dependencies
- Stability: Balance staying current with maintaining stability
- Visibility: Maintain a clear inventory of all dependencies
- Consistency: Use consistent versions across projects when possible
- Security-First: Prioritize security updates over feature updates
- Dependency Hell: Accumulating too many interdependent packages
- Version Freezing: Never updating dependencies
- Update Anxiety: Constant major version updates without testing
- Transitive Dependency Blindness: Not knowing what your dependencies depend on
- Overly Optimistic Versioning: Using
*
orlatest
instead of specific versions - Overly Pessimistic Versioning: Pinning to exact versions without room for patches
When evaluating a new dependency, consider:
- Feature Match: How well it meets functional requirements
- Performance: Resource usage and efficiency
- Bundle Size: Impact on application size
- API Design: Quality and intuitiveness of the API
- Extensibility: Ability to extend or customize behavior
- Documentation: Quality and completeness of documentation
- Type Definitions: Availability of type definitions (for typed languages)
- Test Coverage: Extent and quality of test coverage
- Maintenance Activity: Frequency of updates
- Community Size: Adoption in the developer community
- Issue Resolution: Response time to issues and PRs
- Bus Factor: Number of active maintainers
- Commercial Support: Availability of paid support if needed
- Stack Overflow Activity: Community knowledge base
- Release Cadence: Predictable, well-managed releases
- Licensing: Compliance with organizational license policies
- Security History: Track record of security issues
- Alignment: Strategic fit with technology roadmap
- Internal Expertise: Team familiarity with the dependency
- Long-term Viability: Expected longevity of the dependency
Criteria | Weight | Option 1 | Option 2 | Option 3 |
---|---|---|---|---|
Feature completeness | High | ★★★★☆ | ★★★☆☆ | ★★★★★ |
Performance | Medium | ★★★★★ | ★★★★☆ | ★★☆☆☆ |
Maintenance activity | High | ★★★☆☆ | ★★★★★ | ★★★★☆ |
Bundle size | Medium | ★★★★☆ | ★★★★★ | ★★☆☆☆ |
Community support | Medium | ★★★★☆ | ★★★★★ | ★★★☆☆ |
License compatibility | High | ★★★★★ | ★★★★★ | ★★★★★ |
Documentation | Medium | ★★★☆☆ | ★★★★★ | ★★★★☆ |
TOTAL | 75% | 86% | 70% |
All Bayat projects should follow and respect Semantic Versioning (SemVer):
- Major version (
1.0.0
): Incompatible API changes - Minor version (
0.1.0
): Add functionality in a backward-compatible manner - Patch version (
0.0.1
): Backward-compatible bug fixes
Use appropriate version constraints based on the stability of the dependency:
Constraint Type | Example | Use When | Risk Level |
---|---|---|---|
Exact | 1.2.3 |
For mission-critical dependencies with specific tested versions | Low risk, high stability |
Patch Range | 1.2.x or ~1.2.3 |
For stable dependencies where patches are low risk | Low-medium risk |
Minor Range | 1.x or ^1.2.3 |
For reliable dependencies with good backward compatibility | Medium risk |
Major Range | >=1.2.3 |
Generally not recommended except for development tools | High risk |
Unbounded | * or latest |
Never use in production code | Extremely high risk |
- Lock Files: Always commit lock files (
package-lock.json
,yarn.lock
,Pipfile.lock
, etc.) - Resolution Strategy: In case of version conflicts, document the resolution strategy
- Peer Dependencies: Be aware of and document peer dependency requirements
Establish a structured framework for evaluating dependencies:
For new dependencies, complete the Dependency Evaluation Form:
Dependency Evaluation Form
--------------------------
Package Name:
Purpose:
Alternative Packages Considered:
Version Selected:
License:
Security Scan Results:
Bundle Size Impact:
Maintenance Activity:
Major Contributors:
Test Coverage:
Documentation Quality:
Breaking Change History:
Approved By:
Date:
Re-evaluate critical dependencies regularly:
- Mission-Critical: Every 3 months
- Important: Every 6 months
- Standard: Annually
Calculate a Dependency Health Score using:
- Months since last release (fewer is better)
- Number of open issues vs. closed issues
- Number of active contributors
- Security vulnerabilities history
- Breaking changes frequency
- Test coverage percentage
- Documentation completeness
Maintain a comprehensive inventory of dependencies:
- Direct Dependencies: Dependencies explicitly included in your project
- Transitive Dependencies: Dependencies of your dependencies
- Development Dependencies: Used only during development
- Optional Dependencies: Used in specific configurations
Perform regular dependency audits:
- Security Audit: Check for known vulnerabilities
- License Audit: Ensure license compliance
- Usage Audit: Identify unused or duplicate dependencies
- Update Audit: Identify outdated dependencies
Use visualization tools to understand dependency relationships:
- Dependency Graphs: Visualize the hierarchy of dependencies
- Impact Analysis: Understand the impact of updating a specific dependency
Establish a regular cadence for dependency updates:
- Security Updates: Immediate
- Patch Updates: Monthly
- Minor Updates: Quarterly
- Major Updates: Planned with feature releases
Choose the appropriate update approach based on project phase and criticality:
-
Continuous Updates: Regularly update to latest compatible versions
- Best for: Active development, lower criticality systems
- Advantages: Stay current, avoid large migrations
- Challenges: Requires good test coverage, more frequent integration work
-
Scheduled Batch Updates: Update groups of dependencies on a schedule
- Best for: Stable systems, medium criticality
- Advantages: Predictable, controlled impact
- Challenges: Can create larger change sets
-
LTS Alignment: Align with Long-Term Support versions of key dependencies
- Best for: High criticality systems, regulated environments
- Advantages: Stability, security, vendor support
- Challenges: Can fall significantly behind latest features
-
Feature-Driven Updates: Update when new features are needed
- Best for: Mature, feature-stable products
- Advantages: Clear business justification
- Challenges: Can accumulate technical debt
For major dependency updates, follow a gradual rollout:
- Research Phase: Understand breaking changes and migration path
- Prototype Phase: Create a proof-of-concept with the update
- Migration Planning: Develop a detailed migration plan
- Testing Environment: Apply the update in testing environment
- Canary Deployment: Deploy to a subset of production
- Full Rollout: Complete the migration
- Monitoring Period: Monitor for unexpected issues
- Retrospective: Document lessons learned
Implement continuous monitoring for security vulnerabilities:
- Automated Scans: Use automated security scanning tools
- Vulnerability Databases: Monitor CVE databases and security advisories
- Vendor Security Bulletins: Subscribe to security notifications from vendors
When a vulnerability is discovered:
- Assessment: Evaluate the severity and applicability
- Prioritization: Prioritize based on CVSS score and exposure
- Mitigation: Apply updates, patches, or workarounds
- Verification: Verify the vulnerability is resolved
- Documentation: Document the response for compliance purposes
Severity Level | CVSS Score | Response Time | Update Window |
---|---|---|---|
Critical | 9.0-10.0 | Immediate | 24 hours |
High | 7.0-8.9 | Within 1 day | 3 days |
Medium | 4.0-6.9 | Within 1 week | 2 weeks |
Low | 0.1-3.9 | Within 30 days | Next release |
Techniques for identifying potential breaking changes:
- Release Notes Review: Thoroughly read release notes for new versions
- Changelog Analysis: Review detailed changelogs
- API Comparison: Use tools to compare API signatures between versions
- Test Coverage Analysis: Identify tests that will verify compatibility
Before applying breaking changes:
- Usage Analysis: Identify where and how the dependency is used
- Compatibility Testing: Test in a controlled environment
- Migration Planning: Document required code changes
- Rollback Planning: Establish a rollback plan
After applying breaking changes:
- Regression Testing: Run comprehensive regression tests
- Integration Testing: Verify interactions with other components
- Performance Testing: Check for performance regressions
- Monitoring: Implement additional monitoring during transition
Implement these tools to automate dependency management:
-
Dependency Updates:
- Dependabot
- Renovate
- npm-check-updates
- pip-upgrader
- Gradle Version Catalog
-
Vulnerability Scanning:
- OWASP Dependency-Check
- Snyk
- WhiteSource
- GitHub Security Alerts
- npm audit / yarn audit
-
License Compliance:
- FOSSA
- WhiteSource
- Black Duck
- License Finder
-
Dependency Analysis:
- npm-ls
- pipdeptree
- Gradle buildDependents
- Maven dependency:analyze
Standard configurations for automated tools:
- Update Frequency: Configure automatic updates based on dependency type
- Grouping Rules: Group related dependencies for updating together
- Schedule Settings: Set update schedules to minimize disruption
- Approval Workflows: Configure approval requirements for different update types
- Package Manager: Use yarn or npm with strict lockfiles
- Type Definitions: Prefer packages with built-in TypeScript definitions
- Version Constraints: Use caret ranges (
^1.2.3
) for most dependencies - Peer Dependencies: Be explicit about peer dependency requirements
- Tools: Use npm-check-updates, depcheck for dependency management
// Example package.json configuration
{
"name": "bayat-project",
"version": "1.0.0",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"jest": "^29.0.0"
},
"resolutions": {
"critical-package": "1.2.3"
},
"engines": {
"node": ">=18.0.0"
}
}
- Dependency Management: Use Poetry or Pipenv with lock files
- Virtual Environments: Always use virtual environments
- Version Constraints: Use compatible release specifiers (
~=1.2.3
) - Tools: Use pip-audit, pip-requirements-check
# Example pyproject.toml with Poetry
[tool.poetry]
name = "bayat-project"
version = "1.0.0"
[tool.poetry.dependencies]
python = "^3.10"
django = "^4.2"
requests = "^2.28"
[tool.poetry.dev-dependencies]
pytest = "^7.0"
mypy = "^1.0"
[tool.poetry.group.dev.dependencies]
black = "^23.0"
- Build Tool: Use Gradle with version catalogs
- BOM: Utilize Bill of Materials for version consistency
- Version Constraints: Use strict version ranges for critical dependencies
- Tools: Use Gradle Versions Plugin, OWASP Dependency-Check
// Example Gradle version catalog (libs.versions.toml)
[versions]
spring-boot = "3.1.0"
kotlin = "1.8.21"
jackson = "2.14.2"
[libraries]
spring-boot-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "spring-boot" }
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
[bundles]
spring = ["spring-boot-starter", "spring-boot-starter-web"]
[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
- Package Management: Use NuGet with PackageReference format
- Version Constraints: Use version ranges with minimum versions
- Central Package Management: Use Directory.Packages.props for multi-project solutions
- Tools: Use NuGet Package Explorer, dotnet outdated
<!-- Example Directory.Packages.props for centralized versioning -->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.App" Version="7.0.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Serilog" Version="3.0.1" />
</ItemGroup>
</Project>
<!-- In individual project files -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>
- Modules: Use Go modules (go.mod) for dependency management
- Version Selection: Use specific versions or commit hashes
- Vendoring: Consider vendoring dependencies for critical projects
- Tools: Use govulncheck, go mod tidy
// Example go.mod file
module github.com/bayat/project
go 1.20
require (
github.com/gin-gonic/gin v1.9.1
github.com/go-sql-driver/mysql v1.7.1
github.com/sirupsen/logrus v1.9.3
)
// Pin indirect dependencies when necessary
replace (
github.com/critical-dependency/lib v1.2.3 => github.com/critical-dependency/lib v1.2.4
)
- Composer: Use Composer with specific version constraints
- Platform Requirements: Define PHP version and extension requirements
- Version Constraints: Use caret version constraints (
^1.2.3
) - Tools: Use composer-require-checker, composer-unused
// Example composer.json
{
"name": "bayat/project",
"require": {
"php": "^8.1",
"laravel/framework": "^10.0",
"guzzlehttp/guzzle": "^7.5"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.10"
},
"config": {
"sort-packages": true
}
}
- Version Consistency: Ensuring consistent versions across packages
- Interdependencies: Managing internal package dependencies
- Selective Updates: Updating dependencies for specific packages
- Hoisting: Handling dependency hoisting to reduce duplication
-
Centralized Version Management:
- Use tools like Lerna, nx, or Yarn workspaces
- Define shared dependencies in a root configuration
- Implement version resolution strategies
-
Dependency Graph Analysis:
- Visualize internal dependency relationships
- Analyze impact of updates on the entire repository
- Identify circular dependencies
-
Staged Updates:
- Update shared dependencies first
- Test affected packages
- Update package-specific dependencies
-
Release Coordination:
- Coordinate releases of interrelated packages
- Version internal dependencies appropriately
- Document breaking changes across packages
-
Dependency Installation: Optimize for CI/CD performance
- Use dependency caching
- Consider offline installations
-
Automated Checks:
- Verify lock file integrity
- Scan for vulnerabilities
- Check for outdated dependencies
- Validate license compliance
-
Integration Testing:
- Test compatibility with updated dependencies
- Run integration tests with latest dependencies
-
Verify Dependencies Before Deployment:
- Ensure all dependencies are resolvable
- Verify dependencies match lock files
-
Dependency Considerations in Rollback Strategy:
- Include dependency state in rollback procedures
- Test rollbacks with dependency changes
-
Environment Parity:
- Ensure development, staging, and production use identical dependencies
- Document any environment-specific dependency configurations
For each project, document:
-
Core Dependencies List:
- Purpose of each major dependency
- Version selection rationale
- Alternatives considered
-
Dependency Update Policy:
- Update frequency for different types of dependencies
- Approval process for updates
- Testing requirements for updates
-
Known Issues and Workarounds:
- Document known issues with specific versions
- Include any applied patches or workarounds
-
Version Constraints Rationale:
- Explain unusual version constraints
- Document version pinning reasons
# Project Dependencies
## Core Dependencies
| Dependency | Version | Purpose | Alternatives Considered | Update Policy |
|------------|---------|---------|-------------------------|---------------|
| React | ^18.2.0 | UI framework | Vue, Angular | Quarterly minor updates |
| axios | ^1.3.0 | HTTP client | fetch API, SuperAgent | Monthly patch updates |
## Dependency Management
- **Update Schedule**: Security patches immediately, minor versions monthly, major versions quarterly
- **Update Responsibility**: DevOps team handles infrastructure dependencies, development team handles application dependencies
- **Pre-Update Testing**: Full test suite must pass before merging dependency updates
## Known Issues
- **[email protected]**: Memory leak in `_.debounce()` function. Workaround in `src/utils/debounce.js`
- **[email protected]**: Incompatible with our custom loaders. Pinned to 5.69.1 until fixed
## Custom Patches
- Applied custom patch to `node_modules/problem-package/index.js` for compatibility. See `patches/problem-package+1.0.0.patch`
-
Clear Separation:
- Clearly separate development and production dependencies
- Use appropriate configuration in package managers
-
Development Dependencies:
- Testing frameworks and tools
- Linters and formatters
- Build tools
- Development servers
- Documentation generators
-
Production Dependencies:
- Runtime libraries
- Application frameworks
- Data processing libraries
- API clients
-
Define Environment Tiers:
- Development
- Testing
- Staging
- Production
-
Dependencies by Environment:
- Document dependencies used in each environment
- Validate environment parity for production dependencies
When choosing between self-hosted and third-party dependencies:
-
Evaluation Criteria:
- Operational overhead
- Security implications
- Cost analysis
- Reliability requirements
- Customization needs
-
Documentation Requirements:
- Deployment requirements for self-hosted options
- SLA and support terms for third-party services
- Migration path between options
-
Hybrid Approaches:
- Using official Docker images for self-hosting
- Self-hosting with vendor support
- Cloud-managed open-source services
-
Dependency Archaeology:
- Document historical dependency decisions
- Map current dependencies and their interactions
-
Gradual Modernization Strategy:
- Prioritize security updates
- Create a phased update plan
- Establish test coverage before major updates
-
Working with Outdated Dependencies:
- Vendor forking strategy when necessary
- Minimal patching approach
- Encapsulation to limit exposure to outdated code
When a dependency is no longer maintained:
-
Risk Assessment:
- Evaluate security implications
- Assess functional risks
- Consider future compatibility issues
-
Action Plan:
- Replace with an alternative
- Fork and maintain internally
- Encapsulate and isolate
- Plan for gradual replacement
Project: Customer Portal Application
Challenge: Upgrading from Angular 11 to Angular 15
Approach:
- Created a comprehensive inventory of all Angular dependencies
- Developed a test plan focusing on critical user journeys
- Implemented step-by-step upgrade through each major version
- Used feature flags to enable incremental migration
- Maintained dual implementations of critical components during transition
Results:
- Successful migration with minimal user disruption
- Improved performance metrics by 30%
- Better maintainability and developer experience
- Documented process for future framework updates
Project: Payment Processing Service
Challenge: Critical vulnerability in a core cryptography library
Approach:
- Implemented temporary mitigation measures
- Tested patch in isolated environment
- Deployed emergency update to all environments
- Conducted post-update security audit
- Improved dependency monitoring process
Results:
- Vulnerability addressed within 4 hours of disclosure
- No security breaches detected
- Improved automated vulnerability scanning
- Established clear security update protocols
Project: Mobile Application
Challenge: App size and startup time issues due to dependency bloat
Approach:
- Conducted comprehensive dependency audit
- Identified and removed unused dependencies
- Replaced heavy libraries with lighter alternatives
- Implemented code splitting and lazy loading
- Established dependency size budgets
Results:
- Reduced app size by 40%
- Improved startup time by 35%
- Simplified maintenance requirements
- Created clear guidelines for adding new dependencies
- npm/yarn/pnpm for package management
- npm-check-updates for finding updates
- depcheck for finding unused dependencies
- bundlephobia for analyzing bundle size impact
- npm audit for security checks
- Poetry or Pipenv for dependency management
- pip-audit for security checks
- pipdeptree for visualizing dependencies
- pyup.io for automated updates
- Gradle Versions Plugin for finding updates
- Maven Versions Plugin for Maven projects
- Dependency-Track for security monitoring
- JFrog Xray for deep dependency analysis
- NuGet Package Explorer
- dotnet outdated
- OWASP Dependency-Check
- Snyk for .NET
- govulncheck for vulnerability scanning
- deps.dev for dependency insights
- go mod why for understanding why a dependency is included
- Renovate for automated updates
- npm-dependency-graph
- Graphviz (with custom scripts)
- dependency-cruiser
- Snyk Dependency Tree
- npm ls --all
- Dependabot
- Renovate
- Snyk
- FOSSA
- WhiteSource
- Debricked
- Socket.dev
- Dependabot
- Renovate
- Greenkeeper (for npm)
- PyUp.io (for Python)
- Gradle Release Plugin
- Scala Steward (for Scala)