Creating MoonGuard Pro: How I Structured My Golang Project

Since the beginning of this year, I set out to deeply learn Golang and since then I have been reading several books and watching numerous courses, and I believe it's time to create a production application to apply all the knowledge earned.

I decided to create a pro version of MoonGuard. For those who don't know, MoonGuard is a Filament plugin that allows monitoring Laravel applications. With MoonGuard, you can check uptime, the status of the SSL certificate, view errors, and monitor disk space, CPU, server memory, among other things. It's open source and has been very useful for keeping an eye on my Laravel applications.

However, using MoonGuard currently requires a few steps: creating a Laravel project, installing Filament, and then the plugin, and finally launching everything into production. Although it's not extremely complicated, I would like to simplify this whole process.

While researching Golang-developed applications, I came across Railway and its impressive templates marketplace. I liked it so much that I started replacing subscriptions of applications I use with self-hosted open source alternatives. Another day I'll tell you more about Railway. Then I thought: why not do the same with MoonGuard, but without the complexity of setting up Laravel?

This is where Golang comes in. Something I love about Golang is how easy it is to launch your applications into production, as it ultimately becomes just a single executable file once compiled.

My goal is for MoonGuard to be very easy to install and configure, and to be able to run in any environment.

Project Initiation

So I started the project, and the first challenge was how to structure it πŸ€”. I come from the Laravel world, where you almost don't have to think about that; Laravel already provides you with a basic structure you can use, and then, if you want, you can change it to use DDD like a true professional or simply put everything in one folder.

In Golang, it’s a bit different. During my research, I found that there is no defined structure for web applications. People have different opinions, and I didn't find a prevailing approach. After reading quite a lot and watching numerous videos, I decided to define my own structure. But first, let's look at some of the structures I encountered during my research.

Flat Structure

This is the simplest and, apparently, the most common when starting a new project. Basically, you put all the files in the same folder. Here's an example of what it looks like:

project/
β”‚
β”œβ”€β”€ main.go
β”œβ”€β”€ handlers.go
β”œβ”€β”€ models.go
└── routes.go

Flat project structure

Pros

  • Ideal for small projects: If you don't know where to start, this option is great. It's perfect for small applications and libraries where there aren’t many files.
  • Avoids circular dependencies: This can become a huge problem. The good thing about having all files in a single directory is that it greatly reduces the possibility of encountering circular dependencies.
  • Ease of restructuring: Later on, when you have a clearer idea of the project, it's easy to switch from a flat structure to a more modular architecture.

Cons

  • Complexity with growth: As the project grows, a flat structure can become messy and hard to manage. And let's face it, it’s not very aesthetic to have files with 2000 lines of code.
  • Lack of separation: In a flat structure, everything can be accessed and modified by any other part of the code since everything is within the same package. So, good luck with unit tests πŸ™.

Grouping by Function

As your project grows, or if you already know it's going to be big from the start, it may be a good idea to separate files by functionalities. Here's an example of what it would look like:

project/
β”‚
β”œβ”€β”€ cmd/
β”‚   └── main.go
β”œβ”€β”€ internal/
β”‚   β”œβ”€β”€ handlers/
β”‚   β”‚   └── user_handlers.go
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   └── user.go
β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   └── routes.go
β”œβ”€β”€ pkg/
β”‚   └── utils/
β”œβ”€β”€ api/
β”‚   └── user_api.go
β”œβ”€β”€ web/
β”‚   └── templates/
β”‚       └── index.html
β”œβ”€β”€ config/
β”‚   └── config.yaml
β”œβ”€β”€ scripts/
β”‚   └── build.sh
β”œβ”€β”€ docs/
β”‚   └── api.md
└── tests/
    └── user_test.go

Group by function project structure

Pros

  • Easy to reorganize: With code grouped by functions, reorganizing pieces into more complex modules becomes much easier if necessary.
  • Easy to navigate: Having parts of the code clearly defined based on their functionality makes navigating your code much easier.
  • Discourages global state: This approach helps to reduce global dependencies, which makes maintenance and testing easier.

Cons

  • Shared variables can be a problem: On the other hand, you might find yourself in situations where you don't exactly know where functionalities or shared variables should live. It happens to me πŸ™‹.
  • Initialization can be hard to track: With code distributed in multiple files, it can be a bit confusing to know where and how the application is initialized.

Domain-Driven Design (DDD)

In cases where our application is extensive and has quite a complex business logic, DDD can be a great solution. Here's an example:

project/
β”‚
β”œβ”€β”€ domain/
β”‚   β”œβ”€β”€ entities/
β”‚   β”‚   └── user.go
β”‚   β”œβ”€β”€ repositories/
β”‚   β”‚   └── user_repository.go
β”œβ”€β”€ application/
β”‚   └── services/
β”‚       └── user_service.go
β”œβ”€β”€ interfaces/
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”œβ”€β”€ user_api.go
β”‚   β”‚   └── routes.go
β”‚   └── web/
β”‚       └── templates/
β”‚           └── index.html
β”œβ”€β”€ infrastructure/
β”‚   β”œβ”€β”€ database/
β”‚   β”‚   β”œβ”€β”€ migrations/
β”‚   β”‚   └── db.go
β”‚   β”œβ”€β”€ http/
β”‚   β”‚   └── middleware.go
β”œβ”€β”€ config/
β”‚   └── config.yaml
β”œβ”€β”€ scripts/
β”‚   └── build.sh
β”œβ”€β”€ docs/
β”‚   └── architecture.md
└── tests/
    └── integration_test.go

DDD project structure

Pros

  • Improved Communication: The goal is for both developers and the business logic experts to speak the same language. This removes ambiguity and improves business understanding and communication, as long as it's done right πŸ˜‰.
  • System Modularity: The code is organized by contexts, allowing different blocks to evolve autonomously. This minimizes dependencies between modules and makes writing unit tests easier.
  • Facilitates Change: It allows isolating business areas that are subject to frequent changes, making it easier to modify or extend those areas without affecting the rest of the system.
  • Improved Code Quality: The code is better organized and easier to navigate. This allows business rules to be well-defined within the code, reducing errors and inconsistencies.

Cons

  • Initial Complexity: Adopting DDD can be complicated. It requires significant effort to understand and correctly model the business domain. At the beginning of the project, this can be a major obstacle, especially when the business logic is not very clear.
  • Increased Maintenance: Keeping the code well-structured can take a lot of time. It's crucial to constantly review our domain model, as it changes over time. If we don't maintain the DDD structure, over time everything can become a mess and very hard to manage.
  • Dependency on Domain Experts: With DDD, it is super important to work closely with domain experts. If we can’t talk to them continuously, it will be very challenging to create models that really work and are accurate.

And Here Is My Choice

According to my vision, MoonGuard is not a project small enough to use a flat structure; it would quickly become a mess. But I also don't see it as a project so complicated as to use DDD, at least not for now. Moreover, it’s just me developing this project, so using DDD doesn't make much sense. I prefer to group the code by functions, applying some ideas from Laravel's default structure. Here's an example:

moonguard-pro/
β”‚
β”œβ”€β”€ devbox
β”‚   β”œβ”€β”€ bin
β”‚   └── cmd
β”œβ”€β”€ main.go
β”œβ”€β”€ config
β”‚   β”œβ”€β”€ app.go
β”‚   β”œβ”€β”€ database.go
β”‚   β”œβ”€β”€ logger.go
β”‚   β”œβ”€β”€ notifications.go
β”‚   └── vite.go
β”œβ”€β”€ data
β”œβ”€β”€ internal
β”‚   β”œβ”€β”€ db
β”‚   β”œβ”€β”€ errors
β”‚   β”œβ”€β”€ events
β”‚   β”œβ”€β”€ handlers
β”‚   β”œβ”€β”€ logger
β”‚   β”œβ”€β”€ middlewares
β”‚   β”œβ”€β”€ models
β”‚   β”œβ”€β”€ notifications
β”‚   β”œβ”€β”€ repositories
β”‚   β”œβ”€β”€ routes
β”‚   β”œβ”€β”€ scheduler
β”‚   β”œβ”€β”€ server
β”‚   β”œβ”€β”€ services
β”‚   β”œβ”€β”€ tests
β”‚   β”œβ”€β”€ websocket
β”‚   └── workers
β”œβ”€β”€ pkg
β”‚   β”œβ”€β”€ inertia
β”‚   └── validators
└── resources

Group by function project structure

Let me explain what each of the folders contains:

  • bin/: Normally this is where the compiled executables are stored.
  • cmd/: This is where the application’s entry point is defined.
  • config/: Contains configuration files for various aspects of the application. Something similar to how it’s defined in Laravel. I will detail this in a future article.
  • data/: This directory is where I store the local database, in this case, I’m using LibSQL from Turso (SQLite). I will share more about the database later.
  • internal/: Contains the application's internal code that should not be used by other projects.
    • db/: Modules related to the database.
    • errors/: Management and definition of application errors.
    • events/: Modules for managing events.
    • handlers/: Functions that handle server requests.
    • logger/: Application logging management.
    • middlewares/: Middlewares that intercept requests or responses.
    • models/: Definition of data structures and models.
    • notifications/: Functionality related to notifications.
    • repositories/: Interfaces and logic to access data.
    • routes/: Definition of application routes.
    • scheduler/: Scheduled tasks and cron jobs.
    • server/: Server configurations and logic.
    • services/: Services containing business logic.
    • tests/: Utility files for unit tests.
    • websocket/: Functionality related to WebSockets.
    • workers/: Functionality related to worker pools.
  • pkg/: This directory stores modules that are likely to be turned into separate libraries.
  • resources/: Similar to Laravel, this is where I keep everything related to the frontend.

As you can see, this structure is based on what I have recently learned about Golang, mixed with my experience developing with Laravel.

In future articles, I will talk in more detail about each of the modules and the interesting problems I encounter while developing MoonGuard Pro. So follow me on X @guetteman to stay updated on when I publish the next article about Fiber and how I use it in MoonGuard Pro.