Share
Embed
OptiontとEitherによるログイン処理のエラーハンドリングを検討
Kushiro Taichi
August 26, 2020
Transcript
28 slides
-
⾃⼰紹介 技術 学⽣時代 研究でPythonを⽤いた数理最適化と機械学習(領域は物流。通称、オペレーション ズリサーチ) 個⼈でGo, React 社会⼈ 業務はScala(Play Framework),
TypeScript(Angular, RxJS) 個⼈でHaskell, オブジェクト指向, DDD, 圏論, アジャイル, Vim, Go, Rust, インタプ リンタ -
エンティティ定義 // User.Id は別途定義されているものとする case class User( id: Option[User.Id], name:
String ) // User と⼀対⼀の関係を持つ case class UserPassword( userId: User.Id, password: String ) -
処理の流れを整理 . ユーザーから name と password の⼊⼒を受け取る . 受け取った name
を⽤いて、 User クラスインスタンス(以下、 user )を取得する処 理を⾏う . user を取得できたかどうかのエラーハンドリングを⾏う . 取得した user の id によって UserPassword クラスインスタンス(以下、 userPassword )を取得する処理を⾏う . 取得した userPassword の password と⼊⼒で受け取った password を⽐較し、エラー ハンドリングを⾏う . パスワードが正しければ、認証処理を⾏う -
DBから値を取得するメソッド // User 型の値を取得 def getByName(name: String): Future[Option[User]] = ???
// UserPassword 型の値を取得 def get(userId: User.Id): Future[Option[UserPassword]] = ??? -
DBから値を取得する処理の例 // コントーラー内処理 for { userOpt: Option[User] <- userDao.getByName(name) }
yield println(userOpt) // userOpt を出⼒ // User 型の値が⾒つかった場合 // Some(User(id = Some(1), name = "yaga")) // User 型の値が⾒つからなかった場合 // None -
コントローラー処理(Option ver) (login: LoginFormData) => { for { userOpt: Option[User]
<- userDao.getByName(login.name) result <- userOpt match { case None => Future.successful(NotFound("not found name")) case Some(user) => for { Some(userPassword) <- userPasswordDao.get(user.withId) result <- userPassword.verify(login.password) match { case false => Future.successful(Unauthorized("invalid password")) case true => authMethods.loginSuccess(user, Redirect(homeUrl)) } } yield result } } yield result } -
DBから値を取得する処理の例 // コントーラー内処理 for { userOpt: Option[User] <- userDao.getByName(name) userEither:
Either[Result, User] = userOpt.toRight(NotFound("not found name")) } yield println(userEither) // userEither を出⼒ // User 型の値が⾒つかった場合 // Right(User(id = Some(1), name = "yaga")) // User 型の値が⾒つからなかった場合 // Left(NotFound("not found name")) -
コントローラー処理(Either ver) (login: LoginFormData) => { for { userOpt: Option[User]
<- userDao.getByName(login.name) userEither: Either[Result, User] = userOpt.toRight(NotFound("not found name")) userPasswordEither: Either[Result, UserPassword] <- userEither match { case Left(l) => Future.successful(Left(l)) case Right(user) => userPasswordDao.get(user.withId).map(_.toRight(NotFound)) } result <- userPasswordEither match { case Left(l) => Future.successful(l) case Right(userPassword) => userPassword.verify(login.password) match { case false => Future.successful(Unauthorized("invalid password")) case true => authMethods.loginSuccess(userOpt.get, Redirect(homeUrl)) } } } yield result } -
CatsのEitherTで書いた場合(番外編) (login: LoginFormData) => { val result: EitherT[Future, Result, Result]
= for { user <- EitherT(userDao.getByName(login.name).map(_.toRight(NotFound("not found name")))) userPassword <- EitherT(userPasswordDao.get(user.withId).map(_.toRight(NotFound))) result <- EitherT( userPassword.verify(login.password) match { case false => Future.successful(Left(Unauthorized("invalid password"))) case true => authMethods.loginSuccess(user, Redirect(homeUrl)).map(Right(_)):w } ) } yield result result.merge } -
Scalaを学ぶにあたってやったこと(上から順) ドワンゴ研修資料 Tour of Scala Output Todoアプリ作成 N予備校基礎・応⽤ 実践Scala⼊⾨ ⼊社した会社の研修資料(標準ライブラリのメソッドを学ぶ)
Scalaの標準ライブラリを読む Output Todoアプリ作成(2回⽬) -
ベースプロジェクト概要 今⽇の発表資料のサンプルコードを含む Play Frameworkを使⽤ 簡易的なユーザー認証処理ができる状態 defaultブランチはあえて標準ライブラリのみで実装 ブランチを切り替えると⾊々な実装パターンが Option <-> Either
<-> EitherT for yield <-> flatMap, map イシューに改善点を列挙してある コメント・イシュー追加歓迎します! ⾃分のコードが正解ではないので参考程度に