Search code examples
scalamixinscake-pattern

Replace only repositories on a global cake pattern application


I'm trying to use the cake pattern for the first time.

I kind of understand how it works, but would like to know if it is possible to mix already mixed traits or something like that.

What I would like is to build a global application with the cake pattern. And I want another version of that application which would be the same, except at the repository level.

Is it possible to do something like:

  trait application extends DefaultUserServiceComponent with MongoUserRepositoryComponent

  object realApplication extends application
  object fakeApplication extends FakeUserRepositoryComponent with application

I mean: reusing the already built application, when building a fake application using fake repositories?


Solution

  • Short answer: No. You would be inheriting conflicting members. See the following code snippet:

    trait Repository {def authenticate(username: String, password: String): String}
    
    trait UserServiceComponent {self: UserRepositoryComponent =>
      val userService: UserService = new UserService
      class UserService {
        def authenticate(username: String, password: String): String =
          repository.authenticate(username, password)
      }
    }
    
    trait UserRepositoryComponent {
      def repository: Repository
    }
    
    trait MongoUserRepositoryComponent extends UserRepositoryComponent {
      val repository: Repository =
        new Repository {def authenticate(username: String, password: String) = "MongoAuthed"}
    }
    
    trait MockUserRepositoryComponent extends UserRepositoryComponent {
      val repository: Repository =
        new Repository {def authenticate(username: String, password: String) = "MockAuthed"}
    }
    
    trait Application extends UserServiceComponent with MongoUserRepositoryComponent
    
    object RealApplication extends Application
    // The following will be an error: "object FakeApplication inherits conflicting members:"
    object FakeApplication extends Application with MockUserRepositoryComponent
    

    Instead, to have the desired behavior, define Application as such:

    trait Application extends UserServiceComponent {self: UserRepositoryComponent =>}
    object RealApplication extends Application with MongoUserRepositoryComponent
    object FakeApplication extends Application with MockUserRepositoryComponent
    

    To keep the hierarchy given in the OP, you would need to modify the code as such:

    trait MongoUserRepositoryComponent extends UserRepositoryComponent {
      private val _repository = new Repository {def authenticate(username: String, password: String) = "MongoAuthed"}
      def repository: Repository = _repository
    }
    
    trait MockUserRepositoryComponent extends UserRepositoryComponent {
      private val _repository = new Repository {def authenticate(username: String, password: String) = "MockAuthed"}
      def repository: Repository = _repository
    }
    
    trait Application extends UserServiceComponent with MongoUserRepositoryComponent
    
    object RealApplication extends Application
    object FakeApplication extends Application with MockUserRepositoryComponent {
      override val repository: Repository = super[MockUserRepositoryComponent].repository
    }
    

    The additional private val _repository's are necessary so that we can define repository as a function, so that it can be used as an override in FakeApplication. (Using super[type] to override only works with functions).

    EDIT: In the end, the purpose of the cake pattern is to develop a hierarchy where one does not need to override as in the last code snippet I provided. In reality, the trait Application should not exist at all, only RealApplication and FakeApplication. (In the 2nd code snippet I'm essentially doing nothing more than renaming UserServiceComponent to Application).