Skip to content

diegoholiveira/go-sample

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Effective Application Architecture in Go

This repository demonstrates a practical implementation of dependency injection and component design in Go. It showcases how these patterns create testable, maintainable, and flexible applications.

Core Principles

Dependency Injection

Rather than creating dependencies internally, components receive them through constructors:

// OpenWeatherAPI receives its dependencies
func NewOpenWeatherAPI(client doer, apiKey string, logger *log.Logger) *OpenWeatherAPI {
    return &OpenWeatherAPI{
        client:  client,
        apiKey:  apiKey,
        baseURL: "https://api.openweathermap.org/data/2.5",
        logger:  logger,
    }
}

This explicit wiring in main.go creates a clear dependency graph:

// Wire the application components
weatherAPI := openweather.NewOpenWeatherAPI(httpClient, apiKey, logger)
cachedAPI := openweather.NewCachedWeatherAPI(weatherAPI, cache, logger)
handler := weather.NewWeatherHandler(cachedAPI, logger)

Interface-Based Design

Interfaces define behavior contracts for components:

// Consumer-defined interface
type WeatherAPI interface {
    GetCurrentTemperature(ctx context.Context, city string) (float64, error)
}

// Multiple implementations can satisfy this interface
type CachedWeatherAPI struct { ... }
type OpenWeatherAPI struct { ... }

Each interface is defined where it's needed, focusing on required behavior rather than implementation details.

Single Responsibility Components

Our components each have focused responsibilities:

  • OpenWeatherAPI: External API communication
  • CachedWeatherAPI: Transparent caching decorator
  • InMemoryCache: Data storage with expiration
  • WeatherHandler: HTTP request handling

Testing Benefits

This architecture enables precise, focused tests:

func TestGetCurrentTemperature_CacheHit(t *testing.T) {
    // Mock dependencies with testify
    mockAPI := new(MockOpenWeatherAPI)
    mockCache := new(MockCache)

    cachedAPI := &CachedWeatherAPI{
        api:    mockAPI,
        cache:  mockCache,
        logger: logger,
    }

    // Test specific behaviors in isolation
    mockCache.On("Get", "London").Return(20.5, true)
    temp, err := cachedAPI.GetCurrentTemperature(context.Background(), "London")

    require.NoError(t, err)
    assert.Equal(t, 20.5, temp)

    // Verify expected interactions
    mockAPI.AssertNotCalled(t, "GetCurrentTemperature")
}

Testing each component in isolation helps identify issues precisely and simplifies test maintenance.

Practical Implementation

Our implementation demonstrates several key patterns:

  1. Decorator Pattern: The CachedWeatherAPI decorates the standard API with caching
  2. Dependency Inversion: High-level modules depend on abstractions, not details
  3. Constructor Injection: Dependencies are provided at creation time
  4. Interface Segregation: Interfaces are small and focused on specific needs

These patterns create a codebase that's easy to understand, test, and extend with new features.

About

Effective Application Architecture in Go

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages