GoFr - An opinionated Go Framework (original) (raw)

Inter-Service HTTP Calls

GoFr promotes microservice architecture and to facilitate the same, it provides the support to initialize HTTP services
at application level using AddHTTPService() method.

Support for inter-service HTTP calls provide the following benefits:

  1. Access to the methods from container - GET, PUT, POST, PATCH, DELETE.
  2. Logs and traces for the request.
  3. Circuit breaking for enhanced resilience and fault tolerance.
  4. Custom Health Check Endpoints

Usage

Registering a simple HTTP Service

GoFr allows registering a new HTTP service using the application method AddHTTPService().
It takes in a service name and service address argument to register the dependent service at application level.
Registration of multiple dependent services is quite easier, which is a common use case in a microservice architecture.

The services instances are maintained by the container.

Other provided options can be added additionally to coat the basic HTTP client with features like circuit-breaker and
custom health check and add to the functionality of the HTTP service.
The design choice for this was made such as many options as required can be added and are order agnostic,
i.e. the order of the options is not important.

Service names are to be kept unique to one service.

app.AddHTTPService(<service_name> , <service_address>)

Example

package main

import (
    "gofr.dev/pkg/gofr"
)

func main() {
    // Create a new application
    app := gofr.New()

    // register a payment service which is hosted at http://localhost:9000
    app.AddHTTPService("payment", "http://localhost:9000")

    app.GET("/customer", Customer)

    // Run the application
    app.Run()
}

Accessing HTTP Service in handler

The HTTP service client is accessible anywhere from gofr.Context that gets passed on from the handler.
Using the GetHTTPService method with the service name that was given at the time of registering the service,
the client can be retrieved as shown below:

svc := ctx.GetHTTPService(<service_name>)
func Customer(ctx *gofr.Context) (any, error) {
    // Get the payment service client
    paymentSvc := ctx.GetHTTPService("payment")

    // Use the Get method to call the GET /user endpoint of payments service
    resp, err := paymentSvc.Get(ctx, "user", nil)
    if err != nil {
        return nil, err
    }

    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    return string(body), nil
}

Additional Configurational Options

GoFr provides its user with additional configurational options while registering HTTP service for communication. These are:

Important: The password must be base64 encoded in your configuration/environment variables. GoFr will decode it internally before creating the Authorization header.

Example:

# Generate base64 encoded password
echo -n "your-password" | base64
# Output: eW91ci1wYXNzd29yZA==

Rate Limiter Store: Customization GoFr allows you to use a custom rate limiter store by implementing the RateLimiterStore interface.This enables integration with any backend (e.g., Redis, database, or custom logic)

Interface:

type RateLimiterStore interface {
Allow(ctx context.Context, key string, config RateLimiterConfig) (allowed bool, retryAfter time.Duration, err error)
StartCleanup(ctx context.Context)
StopCleanup()
}

Usage:

rc := redis.NewClient(a.Config, a.Logger(), a.Metrics())

a.AddHTTPService("cat-facts", "https://catfact.ninja",
    service.NewAPIKeyConfig("some-random-key"),
    service.NewBasicAuthConfig("username", "password"),
    
    &service.CircuitBreakerConfig{
       Threshold: 4,
       Interval:  1 * time.Second,
  },

   &service.DefaultHeaders{Headers: map[string]string{"key": "value"}},

   &service.HealthConfig{
       HealthEndpoint: "breeds",
  },
   service.NewOAuthConfig("clientID", "clientSecret",
       "https://tokenurl.com", nil, nil, 0),

  &service.RetryConfig{
      MaxRetries: 5
  },
  
  &service.RateLimiterConfig{
      Requests: 5,
      Window:   time.Minute,
      Burst:     10,
      Store:    service.NewRedisRateLimiterStore(rc)}, // Skip this field to use in-memory store
    },
)

Best Practices: