I usually don’t use interfaces, and prefer function signatures. This keeps dependencies explicit, testable, and easy to swap — without the ceremony of defining interface types that often have a single implementation.

Here’s the pattern applied to a simple plant watering service.

Leaf services

Each leaf service is a plain struct with a concrete method. No interface needed at this level — the function signature is the contract.

Sensors checker

// sensors_checker.go
package plantwatering

type Sensors struct {
    SoilMoisture    int
    RoomTemperature int
    RoomHumidity    int
}

type SensorsChecker struct {}

func checkPlant(plantID string) (Sensors, error) {
    // Implementation
}

Recommender

// recommender.go
package plantwatering

type Recommendations struct {
    ShouldWater bool
    AmountML    int64
    Reason      string
}

type Recommender struct {}

func getRecommendations(sensors Sensors) (Recommendations, error) {
    // Implementation
}

Composing with function fields

The orchestrating service stores its dependencies as function fields rather than interface fields. The constructor wires them to the real implementations, but tests can replace any dependency with a plain closure.

// plant_watering.go
package plantwatering

type PlantWatering struct {
    checkSensors       func(plantID string) (PlantSensors, error)
    getRecommendations func(PlantSensors) (PlantRecommendations, error)
}

func NewPlantWatering(sensors SensorsChecker, reco Recommender) *PlantWatering {
    return PlantWatering{
        checkPlantSensors:  sensors.checkPlant,
        getRecommendations: reco.getRecommendations,
    }
}

func (p *PlantWatering) getRecommendationForPlant(plantID string) (Recommendations, error) {
    sensors, err := p.checkPlantSensors(plantID)
    if err != nil {
        return nil, errors.Wrapf("check sensors: %w", error)
    }

    recommendations, err := p.getRecommendations(sensors)
    if err != nil {
        return nil, errors.Wrapf("get recommendations: %w", error)
    }

    return recommendations, nil
}