Structuring Go Code for CLI Applications
This article is a guest post by Marian Montagnino.
This article is a guide on the first steps of creating a new Go CLI application, beginning with some of the most popular ways to structure code, describing each, and weighing up their pros and cons. We’ll also discuss the concept of domain-driven design, as this can influence the resulting structure of an application. Additionally, we will look at how Cobra CLI can generate scaffolding for CLI applications, streamlining the initial setup process.
Programming is like any other creative process. It all begins with a blank slate. Unfortunately, when faced with a blank slate and minimal experience with programming applications from scratch, doubt can kick in – without knowing how to start, you may feel that it’s not possible at all.
Commonly Used Program Layouts for Robust Applications
Along your programming journey, you may come across many different structures for applications. There is no universally agreed standard programming layout for Go (although the Go team do give some pointers). Given all this freedom, however, the choice of the structure must be carefully made because it will dictate whether we understand and know how to maintain our application. The proper structure for the application will ideally also be simple, easy to test, and directly reflect the business design and how the code works.
When choosing a structure for your Go application, use your best judgment. Do not choose arbitrarily. Listen to the advice in context and learn to justify your choices. There’s no reason to choose a structure early, as your code will evolve over time and some structures work better for small applications while others are better for medium to large applications.
Program Layouts
Let’s dig into some common structural patterns that have been developed for the Go language so far. Understanding each option will help you choose the best design structure for your next application.
Flat Structure
This is the simplest structure to start with and is the most common when you are starting with an application, only have a small number of files, and are still learning about the requirements. It’s much easier to evolve a flat structure into a modular structure, so it’s best to keep it simple at the start and partition it out later as the project grows.
Pros:
- Great for small applications and libraries
- No circular dependencies
- Easy to refactor into a modular structure
Cons:
- Can become complex and disorganized as the project grows
- Everything can be accessed and modified by everything else
Grouping Code by Function
Code is separated by its similar functionality. In a Go REST API project, as an example, Go files are commonly grouped by handlers and models.
Pros:
- Easy to refactor into other modular structures
- Easy to organize
- Discourages a global state
Cons:
- Shared variables or functionality may not have a clear place to live
- It can be unclear where initialization occurs
Grouping by Module
Grouping by module means creating individual packages that each serve a function and contain everything necessary to accomplish these functions within them.
Pros:
- Easier to maintain
- Faster development
- Low coupling and high cohesion
Cons:
- Complex and harder to understand
- Requires strict rules to remain well organized
- Circular dependencies may occur
Grouping by Context
This type of structure is typically driven by the domain or the specific subject for which the project is being developed. Hexagonal architecture, also called ports and adapters, is a popular domain-driven design architecture that conceptually divides the functional areas of an application across multiple layers.
Pros:
- Increased communication between business team and developers
- Flexible as business requirements change
- Easy to maintain
Cons:
- Requires domain expertise and understanding the business before implementation
- Costly due to longer initial development times
- Not suited to short-term projects
Common Folders in Go Projects
No matter the chosen structure, there are commonly named folders across existing Go projects. Following this pattern will help to increase understanding for maintainers and future developers of the application:
- cmd: The main entry point for the application. The directory name matches the name of the application.
- pkg: Contains code that may be used by external applications.
- internal: Contains private code and libraries that cannot be accessed by external applications.
- vendor: Contains the application dependencies created by the
go mod vendor
command. - api: Contains code for the application’s REST API.
- web: Contains specific web assets and application components.
- configs: Contains configuration files.
- init: Contains system initiation and process management scripts.
- scripts: Contains scripts to perform various builds, installations, analyses, and operations.
- build: Contains files for packaging and continuous integration.
- deployments: Stores configuration and template files related to system and container orchestration.
- test: Stores test files.
Creating a Generic CLI Application
To illustrate how these concepts come together, let’s consider an example of a generic CLI application that performs a set of common tasks such as file manipulation, data processing, and interacting with external APIs. Here’s an example layout:
/Users/username/go/src/github.com/example/my-cli-app
project-root
├── cmd
│ ├── root.go
│ ├── subcommand1.go
│ └── subcommand2.go
├── internal
│ ├── fileutils
│ └── apiutils
├── models
├── services
│ └── data
├── pkg
└── vendor
- cmd: Entry points for various subcommands in the CLI.
- internal: Internal utility packages for file manipulation and API interactions.
- models: Structs for domain entities used across the application.
- services: Business logic services, such as data processing.
- pkg: Shared packages that may be used by other applications.
- vendor: Application dependencies.
Using Cobra CLI to Generate Scaffolding
Cobra is one of the most widely used frameworks for creating modern CLI applications in Go. One of the main advantages of using Cobra is its own CLI generation tool, called the Cobra Generator, which generates the scaffolding for a new application.
Install it with the following command:
go install github.com/spf13/cobra-cli@latest
Then, set up the structure for your new CLI with the following command:
cobra-cli init my-cli-app
This command creates the basic scaffolding for a CLI application named my-cli-app. The scaffolding including a cmd directory with a root command and a main.go file. You can then add new commands to your CLI by using the cobra-cli add command:
cobra-cli add subcommand
Each of these commands creates a new file under the cmd directory. There will be a file generated called subcommand.go, this is where you can define all the functionality for your new subcommand.
Conclusion
In this article, we have learned how to create a structure for a new Go CLI application based on the unique requirements of the business domain. We looked at the most popular folder structures for applications and the pros and cons of each. We also discussed how to write documentation on use cases and nonfunctional requirements. Additionally, we explored how Cobra CLI can simplify the process by generating scaffolding for CLI applications.
While this guide provided an example layout and the main folders that exist within that example, remember that this is an example of a more developed project. Start simple, always with a flat structure, and grow into a modular structure as your project evolves. With these principles and tools, you’ll be well on your way to building organized and maintainable Go CLI applications.
If you’re interested in learning more…
I’m excited to announce my collaboration with byteSizeGo to bring you “The Art of Command Line Interfaces.” Go code with me and learn to build a health check CLI tool from scratch. We’ll start with an empty folder and guide you through creating a fully functional CLI application, ready for distribution and installation on all major operating systems.
Get your copy here.
And special thanks…
To @kasiazien (Kat Zien) who did an amazing talk in 2018 at GopherConEU called How do you structure your go apps? You can watch the talk here to dive even deeper on each program layout.