Go Patterns
Go Patterns¶
The MindFlight AI Server uses several classic Go patterns to keep the codebase clean, modular, and easy to extend. In this section, we explain each pattern with simple metaphors and provide practical code examples.
Interface & Factory Pattern¶
What it does:
This pattern allows us to define a contract (interface) that different Providers must follow. The server doesn't care how each Provider works internally—as long as it follows the rules.
Metaphor:
It's like giving different chefs the same recipe format. One may bake a cake, another a pie, but both provide a dessert.
Example: Provider Interface
// Provider defines the contract that all providers must follow.
type Provider interface {
Name() string
Init(config map[string]interface{}) error
GetTools() []Tool
}
Example: Registering a Provider
// Registry to store provider factories.
var providerRegistry = map[string]func() Provider{}
// RegisterProvider allows new providers to be added dynamically.
func RegisterProvider(name string, factory func() Provider) {
providerRegistry[name] = factory
}
// Example of registering a Filesystem provider.
RegisterProvider("filesystem", func() Provider {
return &FilesystemProvider{}
})
Adapter & Plug-in Pattern¶
What it does: Providers often adapt third-party APIs to fit the server's needs. This keeps the core system isolated from the specific details of each service.
Metaphor: Like using a universal power adapter—you can plug in any device, and it just works.
Example: Filesystem Adapter
// FilesystemProvider adapts local file operations to the Provider interface.
type FilesystemProvider struct{}
func (f *FilesystemProvider) Name() string {
return "filesystem"
}
func (f *FilesystemProvider) Init(config map[string]interface{}) error {
// Initialize directories and permissions.
return nil
}
func (f *FilesystemProvider) GetTools() []Tool {
return []Tool{
{
Name: "list_files",
Run: f.ListFiles,
},
}
}
func (f *FilesystemProvider) ListFiles(params map[string]interface{}) (interface{}, error) {
// List files from the configured directory.
return []string{"file1.txt", "file2.txt"}, nil
}
Middleware Pattern¶
What it does: Middleware functions run before or after your main API logic. They are used to handle tasks like authentication, logging, or error handling.
Metaphor: Think of it like airport security—you have to pass through checks before getting to your gate.
Example: Auth Middleware with Fiber
// AuthMiddleware checks JWT tokens before allowing access to protected routes.
func AuthMiddleware(c *fiber.Ctx) error {
token := c.Get("Authorization")
if !isValidToken(token) {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Unauthorized",
})
}
return c.Next() // Continue to the next handler.
}
Job Worker (Background Processing)¶
What it does: Jobs allow the server to handle long-running tasks asynchronously, without blocking the API.
Metaphor: Like a bakery where cakes are baked in the back while new orders keep coming in.
Example: Registering a Job
// JobHandler is a function that processes a background job.
type JobHandler func(job Job) error
// RegisterJob registers a new job type with its handler.
func (jm *JobManager) RegisterJob(name string, handler JobHandler) {
jm.handlers[name] = handler
}
// Example: Register an email processing job.
jm.RegisterJob("process_email", func(job Job) error {
// Process the email here.
return nil
})
Event-Driven (Pub/Sub) Pattern¶
What it does: The server uses Publish/Subscribe (Pub/Sub) to notify different parts of the system when something happens (like "email received" or "file processed").
Metaphor: Like a radio station: one side broadcasts events, and anyone listening gets the message.
Example: Publishing & Subscribing
// Subscribe to an event.
eventBus.Subscribe("email.received", func(payload interface{}) {
fmt.Println("New email received:", payload)
})
// Publish an event.
eventBus.Publish("email.received", "new_email_id_123")
Command-Line (CLI) Pattern¶
What it does: The server includes a CLI tool to help with tasks like user management or bootstrapping Providers.
Metaphor: It's like having a kitchen console where you can check inventory or add new menu items without touching the main restaurant.
Example: CLI Command with Cobra
// Define a new CLI command to create a user.
var createUserCmd = &cobra.Command{
Use: "create-user",
Short: "Create a new user",
Run: func(cmd *cobra.Command, args []string) {
// Logic to create a user.
fmt.Println("User created!")
},
}
rootCmd.AddCommand(createUserCmd)
Config & Error Handling¶
What it does: The configuration loader and error wrapper make sure the system is easy to debug and adaptable.
Metaphor: If something goes wrong in the kitchen, the system tells you exactly which ingredient was missing and where.
Example: Error Wrapping
// Custom error with context.
if err != nil {
return fmt.Errorf("failed to initialize provider: %w", err)
}
Summary Table of Go Patterns¶
Pattern | Purpose |
---|---|
Interface & Factory | Add new Providers dynamically. |
Adapter & Plug-in | Adapt external APIs to the server's needs. |
Middleware | Handle auth, logging, and pre/post-processing of requests. |
Job Worker | Manage long-running tasks asynchronously. |
Event-Driven (Pub/Sub) | Notify parts of the system about events. |
Command-Line (CLI) | Manage users, Providers, and server setup via command line. |
Config & Error Handling | Simplify debugging and dynamic configuration. |