Search code examples
godependency-injectionrevel

Revel Dependency Injection


I'm looking to have my revel controllers use various services, which I mock out for unit tests. I'm new to Go; in C# I'd inject them using dependency injection. Is there a common way to do this in revel?

It seems like the best way I've found is to initialise the real services in the controller's Before() method (possibly using a method solved by wire), and to set the mock versions in the the test's Before() method. Or is there a better way?


Solution

  • I use a filter to inject dependencies.

    The filter tests if the controller implements specific interfaces and stuffs the correct dependency in. Here's an example that inserts a database-related dependency:

    func DependencyFilter(c *revel.Controller, filterChain []revel.Filter) {
        if ctrl, ok := c.AppController.(DataServiceController); ok {
            ctrl.SetDataService(<your dependency>)
        }
    
        // Different dependencies could be injected here:
        //if ctrl, ok := c.AppController.(FooController); ok {
        //  ctrl.SetFooService(<your dependency>)
        //}
    
        // Call the next filter
        if len(filterChain) > 0 {
            filterChain[0](c, filterChain[1:])
        }
    }
    

    Where DataServiceController is:

    type DataServiceController interface {
        SetDataService(ds services.DataService)
    }
    

    I inserted my filter as the penultimate entry in init.go:

    revel.Filters = []revel.Filter{
        revel.PanicFilter,             // Recover from panics and display an error page instead.
        // ...
        DependencyFilter,              // Add dependencies
        revel.ActionInvoker,           // Invoke the action.
    }
    

    Most of my controllers need the same dependencies, so I have a base controller that they all embed:

    type BaseController struct {
        *revel.Controller
        DataService services.DataService
    }
    
    func (c *BaseController) SetDataService(ds services.DataService) {
        c.DataService = ds
    } 
    

    So my concrete controllers look like this:

    type Home struct {
        BaseController
    }
    
    func (c Home) Index() revel.Result {
        // ...
    }
    

    There might be better ways, but this is my approach.