IntroCake Pattern is a Scala-specific solution to Dependency Injection problem. In Java, from where Scala has inherited a lot, DI problem is solved by either passing environment as a parameter (Env monad), or using specific ad-hoc tools, like Guice. Scala is a much more powerful language, and Cake Pattern is available here. In my opinion, Cake Pattern is the best. It is similar to passing parameters, but differently. The first publication was by Jonas Bonér in 2008, Real-World Scala: Dependency Injection (DI) I personally found the article kind of hard to read; hope this one is easier. The pattern itself is easy.
Real World ExampleWe have an application which depends on configuration and credential files, and talks to a server via some proprietary API. We want to work on this application. By working I mean freely refactoring it from its initial prototype state to a well-written piece of code, enriched with logging, statistics, status reporting, etc., and keep it alive, that is, modifiable. One cannot seriously do it using real world connections; tests take minutes to run, connections are brittle etc. So we want to be able to run logic without the need to connect to anything. And this includes also System.out output and System.exit() calls. How do we do it? Let's start with the "real world" part.
Abstracting The File SystemThe full source code can be found on github, here is a small piece. To use it in your application, instead of "manually" finding/opening/closing files, one just has to write somewhere in the client code.
Mocking the File SystemTo avoid creating multiple files just to make sure the code works in various circumstances, we better mock the file system. Here's an example: We can throw in tons of such cases, covering all the situations, which is less probable if you have to deal with dozens of actual files. Now good, but how can we replace a file system with this mock file system? This is how.
Eating The CakeHere
MyAppis a trait, with an accompanying object. In production circumstances we use the accompanying object, which instantiates
FS, the object representing a regular file system. In the trait,
FileSystemis represented as an abstract value; so if we extend
MyAppin our test suite, we can pass our test file system: Now we can manipulate the app to our pleasure.
Intercepting EverythingIn addition, in our test suite we can define this: And then write Provided that we did define the default implementations of
exitetc, we are now in control of the application.
Rinse, RepeatBut what if
MyAppuses another piece of code that talks to an external service? We have to do some instrumentation in that other piece of code, using the same cake pattern; the default implementation would be coming from the accompanying object, but in the case of test suite we can instantiate with something completely different, and pass it along to