dependency injection in functional programming
Post on 12-Apr-2017
346 Views
Preview:
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