dependency injection in functional programming

Post on 12-Apr-2017

345 Views

Category:

Software

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Actually, I’m a Spy Turtle.

Actually, I’m a Spy Turtle.

QW

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

WS

Validate client

Authenticate user

Issue access token

clientId, clientSecret

email, password

access token

Client

User

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) }

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) }

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) }

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

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) }

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)

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)

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)

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)

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

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)

"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)

}

"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)

}

"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)

}

"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)

}

"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)

}

"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

F

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)

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

val jsonClient = JsonClient(host)

val clientService = new ClientService(jsonClient.getJson)

val jsonClient = JsonClient(host)

val clientService = new ClientService(jsonClient.getJson)

"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)}

"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)}

"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)}

"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)}

"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

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

.thenReturn(Future.value(jsonResponse))

:CA

"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")}

"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

:MF

–Martin Fowler

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

~Martin Fowler

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

~Martin Fowler

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

intermediate state

~Martin Fowler

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

Side effects

Yay! No more mock soup

FSB

Validate client

Authenticate user

Issue access token

clientId, clientSecret

email, password

access token

Client

User

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) }

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) }

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

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) }}

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) }}

: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) }}

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

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) }

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) }

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

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) }

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

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

trait JsonClients { self: ConfigComponent =>

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

trait Repositories { self: ConfigComponent =>

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

dbClient.execute)}

trait Repositories { self: ConfigComponent =>

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

dbClient.execute)}

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 )}

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 )}

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 )}

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 )}

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

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

H

●●●●

E

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)

TE

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

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!

: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

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

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

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

@ThoughtWorks, @SoundCloudDev

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

@theophani, @ellenkoenig, @harrydeanhudson

Questions @starkcoffee

top related