88

Dependency Injection in Functional Programming

Embed Size (px)

Citation preview

Page 1: Dependency Injection in Functional Programming
Page 2: Dependency Injection in Functional Programming
Page 3: Dependency Injection in Functional Programming
Page 4: Dependency Injection in Functional Programming
Page 5: Dependency Injection in Functional Programming
Page 6: Dependency Injection in Functional Programming
Page 7: Dependency Injection in Functional Programming
Page 8: Dependency Injection in Functional Programming

Actually, I’m a Spy Turtle.

Page 9: Dependency Injection in Functional Programming

Actually, I’m a Spy Turtle.

QW

I recommend Uncle Bob’s “The Little Mocker” post

Page 10: Dependency Injection in Functional Programming
Page 11: Dependency Injection in Functional Programming
Page 12: Dependency Injection in Functional Programming
Page 13: Dependency Injection in Functional Programming
Page 14: Dependency Injection in Functional Programming

WS

Page 15: Dependency Injection in Functional Programming

Validate client

Authenticate user

Issue access token

clientId, clientSecret

email, password

access token

Client

User

Page 16: Dependency Injection in Functional Programming

class SignInService(clientService: ClientService, authenticatorService: AuthentictorService, accessTokenService: TokenService) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- clientService.validateClient(clientId, clientSecret) user <- authenticatorService.authenticateUser(email, password) accessToken <- accessTokenService.createAccessToken(user.id,

client.id) } yield (accessToken) }

Page 17: Dependency Injection in Functional Programming

class SignInService(clientService: ClientService, authenticatorService: AuthentictorService, accessTokenService: TokenService) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- clientService.validateClient(clientId, clientSecret) user <- authenticatorService.authenticateUser(email, password) accessToken <- accessTokenService.createAccessToken(user.id,

client.id) } yield (accessToken) }

Page 18: Dependency Injection in Functional Programming

class SignInService(clientService: ClientService, authenticatorService: AuthentictorService, accessTokenService: TokenService) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- clientService.validateClient(clientId, clientSecret) user <- authenticatorService.authenticateUser(email, password) accessToken <- accessTokenService.createAccessToken(user.id,

client.id) } yield (accessToken) }

Page 19: Dependency Injection in Functional Programming

class SignInService(clientService: ClientService, authenticatorService: AuthentictorService, accessTokenService: TokenService) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- clientService.validateClient(clientId, clientSecret) user <- authenticatorService.authenticateUser(email, password) accessToken <- accessTokenService.createAccessToken(user.id,

client.id) } yield (accessToken) }

T

Page 20: Dependency Injection in Functional Programming

class SignInService(clientService: ClientService, authenticatorService: AuthentictorService, accessTokenService: TokenService) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- clientService.validateClient(clientId, clientSecret) user <- authenticatorService.authenticateUser(email, password) accessToken <- accessTokenService.createAccessToken(user.id, client.id) } yield (accessToken) }

Page 21: Dependency Injection in Functional Programming

class ClientService(jsonClient: JsonClient) {

def validateClient(clientId: String, clientSecret: String): Future[Client]={

jsonClient.getJson( Path() / "clients" / clientId ).map { case JsonResponse(OkStatus, json, _, _) => { val id = (json \ "id").as[Long] val secret = (json \ "secret").as[String] if (secret != clientSecret) { throw new Exception("Client doesn't match") } Client(id)

Page 22: Dependency Injection in Functional Programming

class ClientService(jsonClient: JsonClient) {

def validateClient(clientId: String, clientSecret: String): Future[Client]={

jsonClient.getJson( Path() / "clients" / clientId ).map { case JsonResponse(OkStatus, json, _, _) => { val id = (json \ "id").as[Long] val secret = (json \ "secret").as[String] if (secret != clientSecret) { throw new Exception("Client doesn't match") } Client(id)

Page 23: Dependency Injection in Functional Programming

class ClientService(jsonClient: JsonClient) {

def validateClient(clientId: String, clientSecret: String): Future[Client]={

jsonClient.getJson( Path() / "clients" / clientId ).map { case JsonResponse(OkStatus, json, _, _) => { val id = (json \ "id").as[Long] val secret = (json \ "secret").as[String] if (secret != clientSecret) { throw new Exception("Client doesn't match") } Client(id)

Page 24: Dependency Injection in Functional Programming

class ClientService(jsonClient: JsonClient) {

def validateClient(clientId: String, clientSecret: String): Future[Client]={

jsonClient.getJson( Path() / "clients" / clientId ).map { case JsonResponse(OkStatus, json, _, _) => { val id = (json \ "id").as[Long] val secret = (json \ "secret").as[String] if (secret != clientSecret) { throw new Exception("Client doesn't match") } Client(id)

Page 25: Dependency Injection in Functional Programming

class ClientService(jsonClient: JsonClient) {

def validateClient(clientId: String, clientSecret: String): Future[Client]={

jsonClient.getJson( Path() / "clients" / clientId ).map { case JsonResponse(OkStatus, json, _, _) => { val id = (json \ "id").as[Long] val secret = (json \ "secret").as[String] if (secret != clientSecret) { throw new Exception("Client doesn't match") } Client(id)

EL

Page 26: Dependency Injection in Functional Programming

class ClientService(jsonClient: JsonClient) {

def validateClient(clientId: String, clientSecret: String): Future[Client]={

jsonClient.getJson( Path() / "clients" / clientId ).map { case JsonResponse(OkStatus, json, _, _) => { val id = (json \ "id").as[Long] val secret = (json \ "secret").as[String] if (secret != clientSecret) { throw new Exception("Client doesn't match") } Client(id)

Page 27: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonClient = mock[JsonClient] val clientService = new ClientService(jsonClient)

val jsonResponse = JsonResponse(OkStatus, Json.fromString(...))when(jsonClient.get(Path() / "clients" / "clientId")

.thenReturn(Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)

}

Page 28: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonClient = mock[JsonClient] val clientService = new ClientService(jsonClient)

val jsonResponse = JsonResponse(OkStatus,Json.fromString(...))when(jsonClient.get(Path() / "clients" / "clientId")

.thenReturn(Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)

}

Page 29: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonClient = mock[JsonClient] val clientService = new ClientService(jsonClient) val jsonResponse = JsonResponse(OkStatus, Json.fromString(...)

when(jsonClient.get(Path() / "clients" / "clientId") .thenReturn(Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)

}

Page 30: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonClient = mock[JsonClient] val clientService = new ClientService(jsonClient)

val jsonResponse = JsonResponse(OkStatus,Json.fromString(...))when(jsonClient.get(Path() / "clients" / "clientId")

.thenReturn(Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)

}

Page 31: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonClient = mock[JsonClient] val clientService = new ClientService(jsonClient)

val jsonResponse = JsonResponse(OkStatus,Json.fromString(...))when(jsonClient.get(Path() / "clients" / "clientId")

.thenReturn(Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)

}

Page 32: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonClient = mock[JsonClient] val clientService = new ClientService(jsonClient)

val jsonResponse = JsonResponse(OkStatus,Json.fromString(...))when(jsonClient.get(Path() / "clients" / "clientId")

.thenReturn(Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)

}

B

Page 33: Dependency Injection in Functional Programming

F

Page 34: Dependency Injection in Functional Programming

class ClientService(jsonClient: JsonClient) {

def validateClient(clientId: String, clientSecret: String): Future[Client]={ jsonClient.getJson( Path() / "clients" / clientId ).map { case JsonResponse(OkStatus, json, _, _) => { val id = (json \ "id").as[Long] val secret = (json \ "secret").as[String] if (secret != clientSecret) { throw new Exception("Client doesn't match") } Client(id)

Page 35: Dependency Injection in Functional Programming

class ClientService(getJson: (Path) => Future[JsonResponse]) {

def validateClient(clientId: String, clientSecret: String): Future[Client]={ getJson( Path() / "clients" / clientId ).map { case JsonResponse(OkStatus, json, _, _) => { val id = (json \ "id").as[Long] val secret = (json \ "secret").as[String] if (secret != clientSecret) { throw new Exception("Client doesn't match") } Client(id)

:I

Page 36: Dependency Injection in Functional Programming

val jsonClient = JsonClient(host)

val clientService = new ClientService(jsonClient.getJson)

Page 37: Dependency Injection in Functional Programming

val jsonClient = JsonClient(host)

val clientService = new ClientService(jsonClient.getJson)

Page 38: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonResponse = JsonResponse(OkStatus, Json.fromString(...)

val clientService = new ClientService(_ => Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)}

Page 39: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonResponse = JsonResponse(OkStatus, Json.fromString(...)

val clientService = new ClientService(_ => Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)}

Page 40: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonResponse = JsonResponse(OkStatus, Json.fromString(...)

val clientService = new ClientService(_ => Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)}

Page 41: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonResponse = JsonResponse(OkStatus, Json.fromString(...)

val clientService = new ClientService(_ => Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)}

Page 42: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonResponse = JsonResponse(OkStatus, Json.fromString(...)

val clientService = new ClientService(_ => Future.value(jsonResponse))

clientService.validateClient("clientId", "secret") ==== Client(5)}

!E

Page 43: Dependency Injection in Functional Programming

when(jsonClient.getJson(Path()/"clients"/"clientId")

.thenReturn(Future.value(jsonResponse))

:CA

Page 44: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonResponse = JsonResponse(OkStatus, Json.fromString(...) var arg: Option[Path] = None

val getJson = (path:Path) => { arg = Some(path) Future.value(jsonResponse) }

val clientService = new ClientService(getJson)

clientService.validateClient("clientId", "secret")) ==== Client(5) arg ==== Some(Path() / "clients" / "clientId")}

Page 45: Dependency Injection in Functional Programming

"returns client if credentials match" in new Scope {

val jsonResponse = JsonResponse(OkStatus, Json.fromString(...) var arg: Option[Path] = None

val getJson = (path:Path) => { arg = Some(path) Future.value(jsonResponse) }

val clientService = new ClientService(getJson)

clientService.validateClient("clientId", "secret")) ==== Client(5) arg ==== Some(Path() / "clients" / "clientId")}

:S

Page 46: Dependency Injection in Functional Programming

:MF

Page 47: Dependency Injection in Functional Programming

–Martin Fowler

http://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs

Page 48: Dependency Injection in Functional Programming

~Martin Fowler

http://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs

Page 49: Dependency Injection in Functional Programming

~Martin Fowler

http://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs

intermediate state

Page 50: Dependency Injection in Functional Programming

~Martin Fowler

http://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs

Side effects

Page 51: Dependency Injection in Functional Programming

Yay! No more mock soup

FSB

Page 52: Dependency Injection in Functional Programming

Validate client

Authenticate user

Issue access token

clientId, clientSecret

email, password

access token

Client

User

Page 53: Dependency Injection in Functional Programming

class SignInService(clientService: ClientService,authenticatorService: AuthentictorService,accessTokenService: TokenService

) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- clientService.validateClient(clientId, clientSecret) user <- authenticatorService.authenticateUser(email, password) accessToken <- accessTokenService.createAccessToken(user.id, client.id) } yield (accessToken) }

Page 54: Dependency Injection in Functional Programming

class SignInService(clientService: ClientService,authenticatorService: AuthentictorService,accessTokenService: TokenService

) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- clientService.validateClient(clientId, clientSecret) user <- authenticatorService.authenticateUser(email, password) accessToken <- accessTokenService.createAccessToken(user.id, client.id) } yield (accessToken) }

Page 55: Dependency Injection in Functional Programming

class SignInService(validateClient: (String, String) => Future[Client],authenticateUser: (String, String) => Future[User],createAccessToken: (Long, Long) => Future[AccessToken]

) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }

:FO

Page 56: Dependency Injection in Functional Programming

object SignIn { def signIn(validateClient: (String, String) => Future[Client], authenticateUser: (String, String) => Future[User], createAccessToken: (Long, Long) => Future[AccessToken]) (clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }}

Page 57: Dependency Injection in Functional Programming

object SignIn { def signIn(validateClient: (String, String) => Future[Client], authenticateUser: (String, String) => Future[User], createAccessToken: (Long, Long) => Future[AccessToken]) (clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }}

Page 58: Dependency Injection in Functional Programming

:C

object SignIn { def signIn(validateClient: (String, String) => Future[Client], authenticateUser: (String, String) => Future[User], createAccessToken: (Long, Long) => Future[AccessToken]) (clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }}

Page 59: Dependency Injection in Functional Programming

class SignInService(validateClient: (String, String) => Future[Client],authenticateUser: (String, String) => Future[User],createAccessToken: (Long, Long) => Future[AccessToken]

) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }

:N

Page 60: Dependency Injection in Functional Programming

class SignInService(validateClient: (String, String) => Future[Client],authenticateUser: (String, String) => Future[User],createAccessToken: (Long, Long) => Future[AccessToken]

) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }

Page 61: Dependency Injection in Functional Programming

class SignInService(validateClient: (String, String) => Future[Client],authenticateUser: (String, String) => Future[User],createAccessToken: (Long, Long) => Future[AccessToken]

) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }

Page 62: Dependency Injection in Functional Programming

class SignInService(validateClient: (String, String) => Future[Client],authenticateUser: (String, String) => Future[User],createAccessToken: (Long, Long) => Future[AccessToken]

) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }

:IJA

Page 63: Dependency Injection in Functional Programming

class SignInService(validateClient: (String, String) => Future[Client],authenticateUser: (String, String) => Future[User],createAccessToken: (Long, Long) => Future[AccessToken]

) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }

Page 64: Dependency Injection in Functional Programming

class SignInService(validateClient: (ClientId, ClientSecret) => Future[Client],authenticateUser: (String, String) => Future[User],createAccessToken: (Long, Long) => Future[AccessToken]

) { def signIn(clientId: String, clientSecret: String, email: String, password: String): Future[AccessToken] = { for { client <- validateClient(clientId, clientSecret) user <- authenticateUser(email, password) accessToken <- createAccessToken(user.id, client.id) } yield (accessToken) }

:W

Page 65: Dependency Injection in Functional Programming

object SignInApi extends FinagleBasedServer with AppConfigComponent{

val clientsJsonClient = JsonClient(host) val tokensClient = JsonClient(host)

val dbClient = MySqlClient(dbconfig) val passwordsRepo = new PasswordRepository(dbClient.prepare, dbClient.execute)

val clientService = new ClientService(clientsJsonClient.get) val authenticatorService = new AuthentictorService(passwordsRepo.getPassword) val tokenService = new TokenService(tokensClient.get)

val signInService = new SignInService( clientService.validateClient, authenticatorService.authenticateUser, tokenService.createAccessToken )

val signInHandler = new SignInHandler(signInService.signIn)

override def createRoutes(httpServer: HttpServer): Unit = { httpServer.register("/sign-in", signInHandler) }}

MSEV

Page 66: Dependency Injection in Functional Programming

trait JsonClients { self: ConfigComponent =>

val clientsJsonClient = JsonClient(config.clientsHost) val tokensClient = JsonClient(config.tokensHost)}

Page 67: Dependency Injection in Functional Programming

trait Repositories { self: ConfigComponent =>

val dbClient = MySqlClient(config.dbHost, config.dbName, ...) val passwordsRepo = new PasswordRepository(dbClient.prepare,

dbClient.execute)}

Page 68: Dependency Injection in Functional Programming

trait Repositories { self: ConfigComponent =>

val dbClient = MySqlClient(config.dbHost, config.dbName, ...) val passwordRepo = new PasswordRepository(dbClient.prepare,

dbClient.execute)}

Page 69: Dependency Injection in Functional Programming

trait Services extends JsonClients with Repositories { self: ConfigComponent =>

val clientService = new ClientService(clientsJsonClient.get) val authenticatorService = new AuthentictorService(passwordsRepo.getPassword) val tokenService = new TokenService(tokensClient.get)

val signInService = new SignInService( clientService.validateClient, authenticatorService.authenticateUser, tokenService.createAccessToken )}

Page 70: Dependency Injection in Functional Programming

trait Services extends JsonClients with Repositories { self: ConfigComponent =>

val clientService = new ClientService(clientsJsonClient.get) val authenticatorService = new AuthentictorService(passwordsRepo.getPassword) val tokenService = new TokenService(tokensClient.get)

val signInService = new SignInService( clientService.validateClient, authenticatorService.authenticateUser, tokenService.createAccessToken )}

Page 71: Dependency Injection in Functional Programming

trait Services extends JsonClients with Repositories { self: ConfigComponent =>

val clientService = new ClientService(clientsJsonClient.get) val authenticatorService = new AuthentictorService(passwordsRepo.getPassword) val tokenService = new TokenService(tokensClient.get)

val signInService = new SignInService( clientService.validateClient, authenticatorService.authenticateUser, tokenService.createAccessToken )}

Page 72: Dependency Injection in Functional Programming

trait Services extends JsonClients with Repositories { self: ConfigComponent =>

val clientService = new ClientService(clientsJsonClient.get) val authenticatorService = new AuthentictorService(passwordsRepo.getPassword) val tokenService = new TokenService(tokensClient.get)

val signInService = new SignInService( clientService.validateClient, authenticatorService.authenticateUser, tokenService.createAccessToken )}

Page 73: Dependency Injection in Functional Programming

object ExampleSignInApi extends FinagleBasedServer with ConfigComponent with Services{

val signInHandler = new SignInHandler(signInService.signIn)

override def createRoutes(httpServer: HttpServer): Unit = { httpServer.register("/sign-in", signInHandler) }}

S

Page 74: Dependency Injection in Functional Programming

object ExampleSignInApi extends FinagleBasedServer with ConfigComponent with Services{

val signInHandler = new SignInHandler(signInService.signIn)

override def createRoutes(httpServer: HttpServer): Unit = { httpServer.register("/sign-in", signInHandler) }}

S

Page 75: Dependency Injection in Functional Programming

H

Page 76: Dependency Injection in Functional Programming

●●●●

E

Page 77: Dependency Injection in Functional Programming

def getUser(id: Int): Reader[DB,User] = Reader( (db:DB) => User("duana") )

def getTracks(user: User): Reader[DB,Track] = Reader( (db:DB) => Track(42))

val myProgram: Reader[DB,String] = for { user <- getUser(123) track <- getTracks(user) } yield track.toString

myProgram.perform(db)

Page 79: Dependency Injection in Functional Programming

Page 80: Dependency Injection in Functional Programming

TE

Page 81: Dependency Injection in Functional Programming

Manual DI Magic DI

● Requires discipline● Static check● No framework or fancy

language features to learn● Boring

● Enforces consistency● Runtime check?● Problems can be hare-y ● Slick

Page 82: Dependency Injection in Functional Programming

U

Manual DI Magic DI (Framework)

● Requires discipline● Static check● No framework or fancy

language features to learn● Boring

● Enforces consistency● Runtime check?● Problems can be hare-y ● Slick

Stop spreading the news!

Page 83: Dependency Injection in Functional Programming

:L

Manual DI Magic DI (Framework)

● Requires discipline● Static check● No framework or fancy

language features to learn● Boring

● Enforces consistency● Runtime check?● Problems can be hare-y ● Slick

Page 84: Dependency Injection in Functional Programming

Manual DI Magic DI (Framework)

● Requires discipline● Static check● No framework or fancy

language features to learn● Boring

● Enforces consistency● Runtime check?● Problems can be hare-y ● Slick

Page 85: Dependency Injection in Functional Programming

Manual DI Magic DI (Framework)

● Requires discipline● Static check● No framework or fancy

language features to learn● Boring

● Enforces consistency● Runtime check?● Problems can be hare-y ● Slick

:M

Page 86: Dependency Injection in Functional Programming
Page 87: Dependency Injection in Functional Programming

def makeTalk(askChrisBerkhout: (Talk) => Better[Talk],askAaronLevinAboutFP: (None) => Some[Idea],askBrianGuthrieAboutClojure: (Idea) => Better[Idea])(ideas: Talk): Better[Talk]

makeTalk(..) was invoked by com.thoughtworks.Birgitta

Page 88: Dependency Injection in Functional Programming

@ThoughtWorks, @SoundCloudDev

@chrisberkhout, @aaronmblevin, @bguthrie, @birgitta410,@davcamer, @rentalcustard

@theophani, @ellenkoenig, @harrydeanhudson

Questions @starkcoffee