DEV Community

Divya Darshana
Divya Darshana

Posted on • Edited on

Database Mess? Here's How to Fix It!

Modern applications often rely on multiple datastores within a single microservice to handle diverse use cases. Managing these datastores effectively—while keeping the application lightweight, maintainable, and scalable—requires adopting robust engineering patterns. In this article, we’ll explore how patterns like the Factory Pattern and Dependency Injection (DI) help streamline datasource integration, with practical examples using GoFr, a framework I’ve been contributing to and am familiar with.


Factory Pattern in Action

The Factory Pattern encapsulates initialization logic into reusable functions, making your code clean, modular, and easy to maintain. Here's an example of how you use this pattern in GoFr:

Example with the Factory Pattern

func main() {
    app.AddMongo(mongo.New(mongo.Config{
        URI:              "mongodb://localhost:27017",
        Database:         "test",
        ConnectionTimeout: 4 * time.Second,
    }))
}
Enter fullscreen mode Exit fullscreen mode

Factory Implementation (Encapsulated Logic)

// mongo package

func New(config mongo.Config) *mongo.Client {
    client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(config.URI))
    if err != nil {
        log.Fatal("Failed to connect to MongoDB:", err)
    }

    // Additional setup like pinging or timeout logic.
    err = client.Ping(context.TODO(), nil)
    if err != nil {
        log.Fatal("Failed to ping MongoDB:", err)
    }

    return client
}
Enter fullscreen mode Exit fullscreen mode

Note: This code is for demonstration purposes only. The actual implementation in the GoFr framework may differ from the example provided below. If you're interested in exploring the official logic, feel free to check out their open-source code here.

Key Benefits of the Factory Pattern

1. Encapsulation: The factory is like a prebuilt kitchen—you don't need to worry about plumbing or appliances.

  • With a factory: Setup happens in one place; the app just uses it.
  • Without a factory:: Setup logic is duplicated everywhere, leading to maintenance nightmares.

2. Reusability: A factory acts as a blueprint.

  • With a factory: Reuse the logic anywhere by calling mongo.New(config).
  • Without a factory: Setup code is manually duplicated across the app.

3. Centralized Updates: One change in the factory updates all instances automatically.

  • With a factory: Update connection logic in one place (e.g., add tracing).
  • Without a factory: Hunt for every instance of the logic, increasing the risk of missing some.

4. Testability: Factories make mocking seamless.

  • With a factory: Return a mock object during testing to avoid real database connections.
  • Without a factory: Tests require spinning up real instances or rewriting mocks everywhere.

Dependency Injection

Dependency Injection (DI) is like hiring a contractor to build a house—you delegate the management of components (e.g., windows, doors) so you can focus on the overall design. In DI, instead of hardcoding dependencies like databases or services, they are passed from the outside, making your application modular, testable, and maintainable.

Examples: Injecting MongoDB as a datasource in GoFr

GoFr streamlines Dependency Injection (DI) through intuitive helper methods. For instance, injecting a MongoDB datasource is as simple as follows:

// AddMongo sets MongoDB in the app's container.
func (a *App) AddMongo(db container.MongoProvider) {
    db.UseLogger(a.Logger())
    db.UseMetrics(a.Metrics())

    tracer := otel.GetTracerProvider().Tracer("gofr-mongo")
    db.UseTracer(tracer)

    db.Connect()
    a.container.Mongo = db
}
Enter fullscreen mode Exit fullscreen mode

Explore the open-source implementation

This approach ensures:

  • Minimalism: Lightweight and explicit setup without unnecessary abstractions.
  • Observability: Automatic configuration of logging, metrics, and tracing.
  • Testability: Easily replace real dependencies with mocks for testing.

Comparing GoFr DI with Other Frameworks

Feature GoFr Other DI Frameworks
Configuration Encapsulation Uses helper methods (e.g., AddMongo) to inject dependencies, reducing boilerplate. May rely on automatic dependency resolution, increasing complexity.
Integrated Observability Automatically configures logging, metrics, and tracing during dependency injection. Requires additional setup or external libraries.
Container-Based Access Dependencies are stored in a container, accessible throughout the app. Relies on containers but may use reflection or hidden complexity.
Explicit vs. Magic Developers configure dependencies explicitly, providing clarity. Often relies on annotations, reflection, or automatic wiring.
Flexibility Full control over dependency injection, allowing custom configurations. More abstracted setups that may reduce control.

Here GoFr, offers greater control and simplicity compared to other DI frameworks. Similarly, you can build clean, lightweight, and testable way to manage dependencies while integrating observability tools seamlessly. AND....That wraps up this article—feel free to share your thoughts in the comments below and spread the word with your peers!


Contribute and Learn

If you're eager to explore Dependency Injection and the Factory Pattern, you can start with these open source issues to tame your Golang skills :


Want a Demo? Don't Miss out ! Watch Now ! 🚀

If you're interested in leveraging the datasources provided by GoFr as externalized support using the Factory Pattern and Dependency Injection, check out my latest video!, where I dive into this plug and play approach, demonstrate the implementation, and share a fun, hands-on demo.

Watch the Demo

Top comments (0)