Skip to content

Refactor: Decouple project.ProjectConfig from provisioning.Options #6197

@vhvb1989

Description

@vhvb1989

Problem

The project package has tight coupling to the provisioning package through direct type usage in ProjectConfig:

// pkg/project/project_config.go
type ProjectConfig struct {
    Infra provisioning.Options `yaml:"infra,omitempty"`
    // ... other fields
}

This creates a hard dependency where:

  • The project package cannot be compiled without the provisioning package
  • Parsing azure.yaml requires bringing in all provisioning infrastructure logic
  • Violates separation of concerns (config data vs. provisioning behavior)
  • Makes isolated testing difficult
  • Increases dependency bloat for consumers who only need config parsing

Current Issues

  1. Unnecessary dependencies: Importing project to parse azure.yaml transitively imports provisioning and all its dependencies
  2. Mixed concerns: Configuration parsing (data) is coupled with provisioning logic (behavior)
  3. Limited reusability: Cannot use project package independently for read-only config scenarios
  4. Testing complexity: Testing config parsing requires provisioning infrastructure

Proposed Solution

Introduce a local InfraConfig type in the project package with conversion functions:

// pkg/project/infra_config.go
package project

// InfraConfig represents infrastructure configuration from azure.yaml
// This is a data-only representation that can be converted to provisioning.Options
type InfraConfig struct {
    Provider string `yaml:"provider,omitempty"`
    Path     string `yaml:"path,omitempty"`
    Module   string `yaml:"module,omitempty"`
}

// ToProvisioningOptions converts InfraConfig to provisioning.Options
func (ic *InfraConfig) ToProvisioningOptions() provisioning.Options {
    return provisioning.Options{
        Provider: provisioning.ProviderKind(ic.Provider),
        Path:     ic.Path,
        Module:   ic.Module,
    }
}

// FromProvisioningOptions creates InfraConfig from provisioning.Options
func InfraConfigFromProvisioningOptions(opts provisioning.Options) InfraConfig {
    return InfraConfig{
        Provider: string(opts.Provider),
        Path:     opts.Path,
        Module:   opts.Module,
    }
}
// pkg/project/project_config.go
type ProjectConfig struct {
    Infra InfraConfig `yaml:"infra,omitempty"`
    // ... other fields
}

Benefits

Loose coupling: project package becomes self-contained
Independent usability: Parse azure.yaml without provisioning dependencies
Separation of concerns: Clear boundary between data and behavior
Better testability: Test config parsing in isolation
Smaller dependency graph: Reduced transitive dependencies
Type safety maintained: Compile-time validation with conversion functions

Migration Path

  1. Create InfraConfig type in project package
  2. Update ProjectConfig to use InfraConfig
  3. Add conversion functions for backward compatibility
  4. Update call sites to convert when provisioning is needed:
    // Before
    provisioningOpts := projectConfig.Infra
    
    // After  
    provisioningOpts := projectConfig.Infra.ToProvisioningOptions()
  5. Update tests and verify YAML serialization remains unchanged

Architectural Principles

This refactoring follows:

  • Dependency Inversion Principle: Depend on abstractions (data structures) not concretions (behavior)
  • Single Responsibility Principle: project handles config, provisioning handles infrastructure
  • Separation of Concerns: Data vs. logic separation
  • Loose Coupling: Packages can evolve independently

Similar Improvements

Consider applying this pattern to other tightly coupled fields in ProjectConfig:

  • State *state.Config
  • Platform *platform.Config
  • Cloud *cloud.Config
  • Workflows workflow.WorkflowMap

Each should be evaluated based on whether the coupling is necessary or can be reduced.

References

  • File: pkg/project/project_config.go:35
  • Related: Package dependency analysis for projectprovisioning coupling

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions