Skip to content

Application Runner

Basic Information

This entity is responsible for running the application using the driver.

package main

import (
    "github.com/componego/componego"
    "github.com/componego/componego/impl/runner"

    "github.com/componego/componego/examples/hello-app/internal/application"
)

func main() {
    runner.RunAndExit(application.New(), componego.ProductionMode)
}
This line in the main function is sufficient to start your application.

The function runner.RunAndExit runs the application and exits the program with an exit code after the application stops.

Note

If the application completes with an error, the exit code will not equal 0 (componego.SuccessExitCode).

You can also use runner.Run, which starts the application but does not exit it:

package main

import (
    "github.com/componego/componego"
    "github.com/componego/componego/impl/runner"

    "github.com/componego/componego/examples/hello-app/internal/application"
)

func main() {
    exitCode := runner.Run(application.New(), componego.ProductionMode)
    // ...
}

There are also methods RunWithContext and RunGracefullyAndExit, which allow you to run the application using a custom context or facilitate a graceful shutdown.

Application Mode

As you can see, you can run the application in different modes. By default, there are several modes available:

Name Description
componego.ProductionMode for production environment
componego.DeveloperMode for developers
componego.TestMode for application tests

But you can add any mode you want.

You can retrieve the application mode through the environment:

if env.ApplicationMode() == componego.DeveloperMode {
    // ...
}

Note

You should always use production mode when running your application on a production server. It is also recommended to use test mode when executing the application in tests.

Note

We strongly recommend using multiple application entry points, as demonstrated in this example.

We believe that applications should be aware of the mode in which they will be launched even before execution. For example, this approach allows you to read different configurations based on the environment, rather than constructing the environment according to the configuration.

Custom Runner

The custom runner is significant as it serves as an entry point where you can begin modifying the core of the framework to meet your specific requirements.

Note

If you are a beginner with our framework, please skip this section and return to it after you have thoroughly read the rest of the documentation.

Specific Driver Options

This is related to the application driver, but you can manage it through the runner. The options are factories that implement all default entities provided by the framework. So this is the key (but not the only one) how you can replace the core of the framework with your code.

For example, you can pass additional options to your application. Let's create a new Run function that accepts arguments:

package custom_runner

import (
    "context"
    "fmt"
    "os"

    "github.com/componego/componego"
    "github.com/componego/componego/impl/driver"
    "github.com/componego/componego/impl/runner/unhandled-errors"
)

func Run(app componego.Application, appMode componego.ApplicationMode, additionalOptions any) int {
    d := driver.New(&driver.Options{
        Additional: additionalOptions,
        // ... other options
    })
    exitCode, err := d.RunApplication(context.Background(), app, appMode)
    if err != nil {
        _, _ = fmt.Fprint(os.Stderr, unhandled_errors.ToString(err, appMode, unhandled_errors.GetHandlers()))
    }
    return exitCode
}

Review the options which are available in the driver code to ensure you can control everything.

Note

The ability to replace the core of the framework is crucial because it allows for flexibility and customization. This means you are not bound to the default implementations of certain functions in the framework. Instead, you can substitute them with alternative methods that comply with the required interfaces. This feature enhances the adaptability of your application, enabling you to tailor functionalities to meet specific needs or to integrate with other systems more effectively.

However, even more critical is the ability to replace business logic easily, as this functionality can be instrumental in creating mocks and other testing scenarios. The framework supports this capability as well. We've covered the details on other pages, highlighting how you can substitute specific business logic implementations to facilitate testing and improve code maintainability.

Errors Handing

Note

It is recommended to use a special application method ApplicationErrorHandler to catch global errors or panic.

At the runner level you can handle errors that were not handled at all previous levels.

Based on the previous example, the following lines can be added for error handling:

func Run(app componego.Application, appMode componego.ApplicationMode) int {
    d := driver.New(nil)
    exitCode, err := d.RunApplication(context.Background(), app, appMode)
    if err != nil {
        handlers := unhandled_errors.GetHandlers()
        _, _ = fmt.Fprint(os.Stderr, unhandled_errors.ToString(err, appMode, handlers))
    }
    return exitCode
}
You can convert the error into a string using specific handlers and display it to users. This approach allows for a user-friendly presentation of error messages. You can customize the error handling logic to fit your application’s needs. Here's how you might implement this:
handlers := unhandled_errors.GetHandlers()
handlers.AddBefore(
    "company:my-handler-name",
    func(err error, writer io.Writer, appMode componego.ApplicationMode) bool {
        if errors.Is(err, MyError) {
            // ...
            return true // return true if the error is processed
        }
        return false
    },
    "componego:vendor-proxy",
)
_, _ = fmt.Fprint(os.Stderr, unhandled_errors.ToString(err, appMode, handlers))

In addition to function AddBefore, t, there are numerous other functions designed for handling errors in a specific sequence using an ordered map. For a complete list of these methods, you can refer to the source code here.

Note

Don't hesitate to explore the core of the framework and copy methods to modify them for your specific requirements. However, it's important to adhere to the rewriting rules outlined by the framework.