Categorygithub.com/albeebe/service
repositorypackage
0.1.3
Repository: https://github.com/albeebe/service.git
Documentation: pkg.go.dev

# Packages

No description provided by the author

# README

Service Library for Go

service is a Go library built to streamline the development of services on Google Cloud Platform. By minimizing boilerplate code and automating much of the setup, it allows you to focus on your business logic without getting bogged down in infrastructure details. The result? A clean, readable main.go file that acts like an index, offering a clear and concise overview of your service — self-documenting and easy to maintain. Whether you're setting up authentication, Pub/Sub, or handling Cloud Tasks, this library is designed to make your services simple, scalable, and production-ready from the start.

Features

  • Simplified Initialization: Load environment variables and configurations with ease, handling both local development and production environments gracefully.
  • Streamlined Service Setup: Quickly set up common GCP services like Cloud SQL, Pub/Sub, and authentication with minimal code.
  • Dependency Injection: Access shared resources like databases, logs, and Google Cloud clients through dependency injection, promoting cleaner code and easier testing.
  • Clean Entry Point: Keep your main.go file concise and readable, resembling an index that outlines the service's structure.
  • Built-in Middleware: Includes authentication and authorization middleware, request handling, and error management out of the box.
  • Production-Ready: Designed with production best practices, including graceful shutdowns, context handling, and proper error logging.

Getting Started

Installation

go get github.com/albeebe/service

Basic Usage

Here's how you can set up a simple service using this library:

package main

import (
    "net/http"

    "github.com/albeebe/service"
)

// All the environment variables are defined here
type EnvVars struct {
  GCP_PROJECT_ID       string `default:"my-project"`
  SERVICE_ACCOUNT      string `default:"[email protected]"`
  CLOUD_SQL_CONNECTION string `default:"my-service:us-east4:shared"`
  CLOUD_SQL_DATABASE   string `default:"my-database"`
  CLOUD_SQL_USER       string `default:"my-user"`
  HOST                 string `default:":8080"`
}

func main() {

  // Load all the environment variables and confirm they're set
  env := EnvVars{}
  if err := service.Initialize(&env); err != nil {
    panic(err)
  }

  // Create the service
  s, err := service.New("my-service", service.Config{
    CloudSQLConnection: env.CLOUD_SQL_CONNECTION,
    CloudSQLDatabase:   env.CLOUD_SQL_DATABASE,
    CloudSQLUser:       env.CLOUD_SQL_USER,
    GCPProjectID:       env.GCP_PROJECT_ID,
    Host:               env.HOST,
    ServiceAccount:     env.SERVICE_ACCOUNT,
  })
  if err != nil {
    panic(fmt.Errorf("failed to create service with err: %s", err.Error()))
  }

  // Add an auth provider to handle authentication
  s.AddAuthProvider(authprovider.New(s))

  // Add endpoints that do not require authentication
  s.AddPublicEndpoint("GET", "/", endpoints.GetRoot)

  // Add endpoints that require authentication
  s.AddAuthenticatedEndpoint("GET", "/authenticated", endpoints.GetAuthenticated)
  s.AddAuthenticatedEndpoint("GET", "/role", endpoints.GetRole, auth.AnyRole("viewer", "editor"))
  s.AddAuthenticatedEndpoint("GET", "/permissions", endpoints.GetPermissions, auth.AllPermissions("project.create"))

  // Add websocket endpoints
  s.AddWebsocket("/websocket", endpoints.Websocket)
  
  // Add endpoints that only services can access
  s.AddServiceEndpoint("GET", "/service", endpoints.GetService)

  // Add endpoints for Pub/Sub subscriptions
  s.AddPubSubEndpoint("_pubsub/demo", pubsub.Demo)

  // Add endpoints for Cloud Tasks
  s.AddCloudTaskEndpoint("/_tasks/demo", tasks.Demo)

  // Add endpoints for Cloud Scheduler
  s.AddCloudSchedulerEndpoint("_scheduled/demo", scheduled.Demo)

  // Begin accepting requests. Blocks until the service terminates.
  s.Run(service.State{
    Starting: func() {
      s.Log.Info("Service starting...")
    },
    Running: func() {
      s.Log.Info("Service running...")
    },
    Terminating: func(err error) {
      if err != nil {
        s.Log.Info("Service terminating with error...", slog.Any("error", err))
      } else {
        s.Log.Info("Service terminating...")
      }
    },
  })
}

Explanation

  • Configuration: Define all necessary configurations in one place.
  • Service Initialization: Initialize your service with service.New, handling all setup internally.
  • Dependency Injection: Access shared resources like the database (s.DB), logger (s.Log), and Google Cloud clients directly from the service instance.
  • Adding Endpoints: Use methods like AddPublicEndpoint and AddAuthenticatedEndpoint to register handlers.
  • Running the Service: Call Run with lifecycle callbacks for starting, running, and terminating states.

Detailed Features

Simplified Initialization

The Initialize function loads environment variables based on a provided specification. It prompts for missing variables during local development and ensures all required variables are set in production.

func Initialize(spec interface{}) error

Service Creation

Create a new service instance with New, which handles configuration validation and sets up GCP credentials.

func New(serviceName string, config Config) (*Service, error)

Dependency Injection

The library utilizes dependency injection to provide access to shared resources throughout your application. This includes:

  • Database Connection (s.DB): Access your Cloud SQL database connection.
  • Logger (s.Log): Use the built-in structured logger for consistent logging.
  • Google Cloud Clients: Access clients for services like Pub/Sub, Cloud Storage, and Cloud Tasks.
  • Context (s.Context): Use the service's context for cancellation and timeout handling.

This approach promotes cleaner code by avoiding global variables and making it easier to write unit tests.

Clean main.go

By abstracting away the boilerplate, your main.go remains clean and self-documenting, making it easy to understand the service structure at a glance.

Adding Endpoints

  • Public Endpoints: For handlers that don't require authentication.

    func (s *Service) AddPublicEndpoint(method, path string, handler func(*Service, *http.Request) *HTTPResponse)
    
  • Authenticated Endpoints: For handlers that require authentication and optional authorization.

    func (s *Service) AddAuthenticatedEndpoint(method, path string, handler func(*Service, *http.Request) *HTTPResponse, authRequirements ...auth.AuthRequirements)
    
  • Websocket Endpoints: For handlers that want to automatically upgrade HTTP requests to WebSocket connections.

    func (s *Service) AddWebsocketEndpoint(relativePath string, handler func(*Service, *websocket.Conn))
    
  • Service Endpoints: For internal service-to-service communication with strict authentication.

    func (s *Service) AddServiceEndpoint(method, path string, handler func(*Service, *http.Request) *HTTPResponse, authRequirements ...auth.AuthRequirements)
    
  • Cloud Task Endpoints: Specifically for handling Cloud Tasks.

    func (s *Service) AddCloudTaskEndpoint(path string, handler func(*Service, *http.Request) error)
    
  • Cloud Scheduler Endpoints: For handling scheduled tasks via Cloud Scheduler.

    func (s *Service) AddCloudSchedulerEndpoint(path string, handler func(*Service, *http.Request) error)
    
  • Pub/Sub Endpoints: For processing Pub/Sub messages.

    func (s *Service) AddPubSubEndpoint(path string, handler func(*Service, PubSubMessage) error)
    

Authentication and Authorization

Set up authentication providers and middleware effortlessly.

func (s *Service) AddAuthProvider(authProvider auth.AuthProvider) error

Accessing Shared Resources

Access various clients and utilities provided by the service instance:

  • Database Client:

    db := s.DB
    
  • Logger:

    s.Log.Info("Logging information.")
    
  • Pub/Sub Client:

    messageID, err := s.PublishToPubSub("topic-name", messageData)
    
  • Cloud Storage Client:

    storageClient := s.CloudStorageClient
    
  • Cloud Tasks Client:

    tasksClient := s.CloudTasksClient
    

Graceful Shutdown

Handles OS signals and context cancellations to terminate the service gracefully.

Utility Functions

  • Response Helpers:

    func Text(statusCode int, text string) *HTTPResponse
    func JSON(statusCode int, obj interface{}) *HTTPResponse
    
  • Request Parsing:

    func UnmarshalJSONBody(r *http.Request, target interface{}) error
    

Example with Dependency Injection

func endpointHandler(s *service.Service, r *http.Request) *service.HTTPResponse {
    // Access the database
    db := s.DB
    // Use the database connection
    rows, err := db.Query("SELECT * FROM data_table")
    if err != nil {
        s.Log.Error("Database query failed.", err)
        return service.InternalServerError()
    }
    defer rows.Close()

    // Process data...
    data := []DataModel{}
    for rows.Next() {
        var item DataModel
        if err := rows.Scan(&item.ID, &item.Value); err != nil {
            s.Log.Error("Row scan failed.", err)
            return service.InternalServerError()
        }
        data = append(data, item)
    }

    // Log the operation
    s.Log.Info("Data retrieved successfully.")

    // Return the response
    return service.JSON(http.StatusOK, data)
}

License

This project is licensed under the MIT License. See the LICENSE file for details.

Contributing

Contributions are welcome! Feel free to open an issue or submit a pull request with any proposed changes.