The Main Package

Keep the main package as small and focused as possible.

The main package defines the entry point of an application. This means that the code in this package is responsible for:

  • obtaining the configuration required to set up the application from external sources
  • setting up the proper lifecycle (the topic is explained in detail in Module 2 - Foundation):
    • setting up proper error flow
    • making sure all allocated and occupied resources are always cleaned up and released
    • providing guarantees that all deferred calls are always executed
    • ensuring the service stops gracefully
  • instantiating external dependencies for a service (this and other application-specific questions are considered in Module 3):
    • clients to any external services
      • databases and storages
      • other services
    • any other connections without which the service cannot exist
    • any files required for the service to operate (incl. pid, lock, pipes)
    • the logger
  • performing pre-flight checks
  • creating an instance of the service
  • starting up the service.

The main package (and, as you'll know in a moment, the main.go file) is the right place to instantiate, create, check anything that is required for a service or application to start up and run correctly. Think about this the following way: anything that your app can't fix at runtime, must originate from the main package (and, as you'll know later, passed as arguments to the constructors of parts which the app is made of). As a consequence, anything else should live outside of the main package.

Here are some examples of what can originate from the main function:

  • instances of any databases a service is using, unless the service can function fully without them, or the service is designed such that it can self-heal
  • any permissions in the system/platform under which the service is running, unless the service can fix or legally workaround missing entitlements
  • any files and related permissions that are relied upon at later stages, such as unix sockets, log files, temporary locations, configuration files, storages for data, and so forth
  • anything else without which it does not make sense to continue execution of the process, because there is no reason to allocate memory and waste resources if the environment you're running in and circumstance do not allow operating healthily. Unless, of course, you're expecting so and prepared for that.

It's worth mentioning that there are some libraries that implicitly facilitate a poor layout of the main package. There are many applications out there available for looking at to obtain enough evidence to support this. When a project uses such a library, the recommendations from documentation are often taken as is, and the main package (and often the entire project) is deteriorated by the code which uses, is used or required by such libraries. So instead of simply copying code from the examples, think of a better way of organising code, so that the main package remains the entry point with as less contents as possible.

The contents of the main package, ideally, should be in the single file, main.go. The most important part of the main.go file is the main function. The package, file and function must be free from clutter, short and focused.

The suggestions above are not hard to follow when a service is well designed and thought through. The primary goal of these is to make the instantiation process clean and straightforward, as well as explicit. Another important goal is to reduce, as much as possible, the mental load required when working on a project. The main.go file is the introduction to the story you tell in code. So make sure the reader is welcomed to follow it, and not confused.