5.5 C
New York

Spring Framework vs Golang Web Services

Published:

Graceful application shutdown is a critical aspect of ensuring service reliability, particularly in high-load systems. 

In this article, we will explore how graceful shutdown is implemented in two popular technologies: Spring Framework and Golang, using examples that interact with a PostgreSQL database.

Why Did I Choose These Two Stacks for Comparison?

It’s quite simple: when transitioning from Java and other JVM-based technologies to languages without a robust framework, developers are faced with the reality of managing many tasks manually. In Spring, many mechanisms for resource management come out of the box, while in Go, these processes need to be implemented by hand.

For example, many developers don’t realize what Spring does under the hood. On the other hand, in Gohttps://dzone.com/articles/golang-tutorial-learn-golang-by-examples, we are responsible for ensuring proper resource cleanup to prevent issues like connection leaks in the database.

Let’s take a detailed look at how both technologies handle system signals, resource closure, connections, and background tasks, followed by a thorough comparison of their approaches.

The Concept of Graceful Shutdown

Before diving into code, let’s understand what a graceful shutdown is.

Graceful shutdown is a mechanism that allows an application to terminate cleanly after receiving a shutdown signal (e.g., SIGTERM or SIGINT).

The key objectives of a graceful shutdown are:

  1. Complete ongoing operations without accepting new ones.
  2. Release resources (e.g., database connections, files, communication channels).
  3. Shut down servers and background tasks.
  4. Prevent resource leaks and errors during restarts.

Graceful Shutdown Mechanism in Spring Framework

Spring Framework implements graceful shutdown through the application’s lifecycle (ApplicationContext) and automatic resource management.

When a Spring Boot application receives a shutdown signal (SIGTERM or SIGINT), it triggers a shutdown hook registered in the JVM:

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    applicationContext.close();
}));

Steps Involved in Graceful Shutdown in Spring

1. Triggering the ContextClosedEvent

All event listeners can perform resource cleanup at this point.

    @Component
    public class ShutdownListener implements ApplicationListener {
        
      	@Override
        public void onApplicationEvent(ContextClosedEvent event) {
            System.out.println("Context is closing... Cleaning up resources.");
        }
    }

2. Calling @PreDestroy Methods

All beans annotated with @PreDestroy execute their shutdown logic.

    @Component
    public class ExampleService {
      
        @PreDestroy
        public void onDestroy() {
            System.out.println("Bean is being destroyed...");
        }
    }

3. Closing Connection Pools

If the application uses a database with Spring Data and a connection pool (e.g., HikariCP), Spring automatically invokes the shutdown() method on the pool.

4. Terminating Background Tasks

Tasks created with @Scheduled or @Async are automatically stopped.

Basically, that’s it

Let’s see an example.

Example of Graceful Shutdown in Spring

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.PreDestroy;
import java.util.logging.Logger;

@SpringBootApplication
public class Application implements CommandLineRunner {

    private static final Logger logger = Logger.getLogger(Application.class.getName());

    @PreDestroy
    public void onShutdown() {
        logger.info("Application is shutting down gracefully...");
    }

    @Autowired
    private UserRepository userRepository;

    @Override
    public void run(String... args) {
        logger.info("Application started.");
        userRepository.findByName("Alice").ifPresent(user -> logger.info("User found: " + user.getName()));
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

In this case, developers have minimal responsibilities regarding resource management, as Spring handles most of the shutdown logic. However, for custom cleanup, methods like @PreDestroy or implementing DisposableBean are essential.

Graceful Shutdown Mechanism in Golang

In Go, graceful shutdown requires explicit implementation. The key steps are:

  1. Handling system signals. 
    • The os/signal package is used to capture termination signals.
  2. Context and cancel functions.  
    • context.Context is used to control task termination.
  3. Manual resource cleanup.
    • All open resources and connections must be closed manually using methods like Close() or cancel().

Example of Graceful Shutdown in Go

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
  
    "github.com/jackc/pgx/v4"
)

// Background task with context
func backgroundTask(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Background task stopped")
            return
        default:
            fmt.Println("Background task running...")
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    fmt.Println("Starting application...")

    // Context for task management
    ctx, cancel := context.WithCancel(context.Background())

    // Database connection
    conn, err := pgx.Connect(ctx, "postgres://user:password@localhost:5432/mydb")
    if err != nil {
        fmt.Printf("Unable to connect to database: %v\n", err)
        return
    }
    defer conn.Close(ctx)

    // Start background task
    go backgroundTask(ctx)

    // Wait for termination signal
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
    <-stop

    fmt.Println("Shutting down gracefully...")
    cancel() // Terminate tasks
    fmt.Println("Application stopped.")
}

That’s not all — it’s a good idea to create something like a Closer function that gathers all Close methods in the main function and then executes them.

In other words, all open connections and resources need to be closed manually by calling Close() methods or cancel() functions. To make this process more convenient and centralized, you can use the Closer function.

Let’s take a look at a universal Closer function for shutdown in Golang.

The Closer function allows you to pass a list of termination functions (cancel(), Close()) and automatically invoke them in the correct order. This helps prevent errors and simplifies the code.

package main

import (
    "fmt"
    "sync"
)

func Closer(closeFuncs ...func()) {
    var wg sync.WaitGroup
  
    for _, closeFunc := range closeFuncs {
        wg.Add(1)
      
        go func(f func()) {
            defer wg.Done()
            f()
        }(closeFunc)
    }
  
    wg.Wait()
}

With this function, you can clean up multiple resources in parallel:

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    conn, _ := pgx.Connect(ctx, "postgres://user:password@localhost:5432/mydb")

    // Start background task
    go backgroundTask(ctx)

    // Wait for termination signal
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
    <-stop

    Closer(
        func() { conn.Close(ctx) },
        cancel,
    )

    fmt.Println("Application stopped.")
}

Here:

1. Context and Background Tasks

The ctx context is used to manage tasks. When cancel() is called, background tasks automatically stop through the ctx.Done() channel.

2. Using the Closer Function

The Closer function takes termination functions (cancel(), Close(), etc.) and executes them in parallel. In this example, the database connection is closed using conn.Close().

  • The cancel() function is called to terminate all related tasks.

3. Completion Wait

  • The sync.WaitGroup ensures that all termination functions complete before the application shuts down.

Advantages of the Closer Function

  • Convenience. All resource cleanup functions can be grouped and passed in one place.
  • Parallel execution. Resource cleanup operations are performed in parallel, reducing overall shutdown time.
  • Safety. Using sync.WaitGroup prevents premature application termination by ensuring that all resources are fully released.

Important: The next section presents a simple comparison of the two implementations. If you’re a professional, you can skip directly to the conclusion. This visualization is intended for colleagues who are either beginners or transitioning from other programming languages.

Comparison

feature spring framework golang
Signal handling Automatic via SpringApplication Manual via os/signal
Resource managemen @PreDestroy auto management  Manual with Close() and cancel()
Flexibility High-level abstraction Full control over processes and resources
Performance overhead Potentially higher due to abstraction layers Minimal overhead
Thread management Automatic (@Async, @Scheduled) or manual green threads Manual through goroutines

In instance, with database simple connections:

Feature Spring Framework (spring data) Golang (pgx)
Database connection Automatic via application.yml configuration Manual with pgx.Connect()
Transaction management Managed by @Transactional via AOP Transactions handled through context
Connection pool handling Automatic with pools like HikariCP Manual with explicit Close() calls (but there is pgxpool)
Error handling Exceptions with try-catch or Error Handler by Spring, or ControllerAdvice Errors returned from functions

Conclusion

What should you choose? There is no single correct answer — it depends on you!

  • Spring Framework offers high-level abstractions, making development faster and easier to maintain. However, this can reduce flexibility and introduce performance overhead.
  • Golang, on the other hand, requires more manual work but gives you full control over processes and resources. This approach is especially beneficial for microservices and high-load systems.

In any case, exploring different programming languages and frameworks is always valuable. It helps you understand how things work elsewhere and refine your own practices.

Take care!

Source link

Related articles

Recent articles