Tagless Final Style Repository의 Highr Kind가 다른 경우 Use Case를 작성하는 방법

11442 단어 TaglessFinalScala
여러가지 참고로 하고, 조사해 시험하고 있어 시행착오해, 드디어, 움직이는 코드가 생겼다.

주로, 다음의 Slide나, GitHub를 참조했습니다. Aoyama, 감사합니다
  • purely_functional_play_framework_application
  • htps : // 기주 b. 코 m / 아오이로 아오이 / 모레 - 아 bst 등

  • 내가 작성한 코드는 htps : // 기주 b. 코 m / 요시 요시 후지이 / s ぁ- g ぇ s s 푹신한 l-에 mp ぇ입니다.

    리포지토리



    Tagless Final Style인 Repository는, 여러가지 곳에서 눈에 접할 수 있다고 생각하기 때문에, 여기에서는 반대로 사쿠로 기재해 둡니다.
    trait UserRepository[F[_]] {
      def store(user: User): F[UserId]
    }
    
    trait AccountRepository[F[_]] {
      def store(account: Account): F[AccountId]
    }
    

    그래서, 이번 곤란한 짱은, 이, 각각의 Repository를, 같은 Use Case로 취급하고 싶지만, 각각, RDB와 Redis라든지 다른 영속화를 한다고 해도 경우에, F[_]가 다르기 때문에, 곤란해 버린다 라고 이야기.

    그래도, 역시, Use Case에 자세한 것은 반입하고 싶지 않아요. 방침만으로 쓰고 싶지 않은데 대책했다.

    Use Case



    정책만으로 쓰는 Use Case는 이하.
    import cats._
    import cats.implicits._
    
    class AccountCreateUseCase[F[_]: Monad: UserRepository: AccountRepository] {
    
      def execute(name: String, email: String): F[String] = {
        for {
          userId <- implicitly[UserRepository[F]].store(User.create(name))
          accountId <- implicitly[AccountRepository[F]].store(Account.create(email))
        } yield s"$userId-$accountId"
      }
    
    }
    

    여기에서는, User나 Account의 생성 로직이라든지, 그러한 도메인의 근처라든지는 적절히 생략하고 있습니다.

    포인트로서는
    class AccountCreateUseCase[F[_]: Monad: UserRepository: AccountRepository] {
    

    그런 다음,
    implicitly[UserRepository[F]]
    

    해야합니다.

    여기서 UserRepository와 AccountRepository를 형식 클래스로 취급합니다.

    그렇지만, 여기는, 실은, 잘 잘 모르고, 자세한 분에게 꼭 교수해 주셨으면 한다.
    어쨌든, 이렇게 쓸 필요가 있고, 또한, trait에서는 안 되고 class로 쓸 필요가 있는 것 같다.

    조금 구구한 느낌으로, htps : // bg. s 또는 c. 이오/2017/04/19/tyぺcぁせ s-인-s? HTML 을 배독하고 있으면, 아무래도,
    class AccountCreateUseCase[F[_]: Monad](implicit ur: UserRepository, ac: AccountRepository)
    

    라고 쓰는 것과 같은 느낌 같네요.

    그 후,
    import cats.implicits._
    

    를 해 두지 않으면, for식에 전개할 수 없기 때문에 주의가 필요.

    Adapters



    상세한 곳.

    UserRepository나 AccountRepository의 구현에 해당하지만, 여기서 각각의 F[_]가 다르다는 것을 다음 표현으로 보았다.
    type SpecialF[A] = ReaderT[Future, (DBConnection, RedisConnection), A]
    

    단순한 경우라면,
    type UserF[A] = ReaderT[Future, DBConnection, A]
    type AccountF[A] = ReaderT[Future, RedisConnection, A]
    

    하지만, 이것을하면 F [_]가 다르므로 이것을 type 곳에서 하나로 긁어 넣는 것으로 구현 타이밍에서 필요한 Connection을 선택하고 취급한다는 역기술 로 해 보았다.

    UserRepository의 구현만 나타내면 다음과 같은 느낌이 된다.
      implicit def userRepository(implicit ec: ExecutionContext): UserRepository[SpecialF] =
        new UserRepository[SpecialF] {
          override def store(user: User): SpecialF[UserId] =
            ReaderT {
              case (db, _) =>
                Future(user.id)
            }
        }
    

    Main



    Main은
        implicit val ec: ExecutionContext = ExecutionContext.global
    
        val useCase = new AccountCreateUseCase[SpecialF]
        val result  = useCase
          .execute("name", "[email protected]")
          .run((new DBConnection, new RedisConnection))
        assert(Await.result(result, 1.second) === "UserId(1)-AccountId(2)")
    

    같은 느낌이 된다.

    run에, tuple로, 각각의 Connection를 건네주는 느낌으로, 구현측에서 필요한 것을 취한다.

    요약



    일단, 이것으로 움직였지만, 아직 미묘한 곳이 있다.

    RDB와, Redis를 같은 Project에 포함하고 있거나, 상호 참조하고 있는 느낌이기 때문에, 거기를 분리하고 싶어지면 할 수 없을 것 같거나.

    Adapters를 구현할 때 어떤 물건이 필요한지 판단해 가는데 Use Case가 복잡하다면 꽤 거대한 Type이 생길 것 같거나…

    그렇다고는 해도, 그 근처는, 상세하고, Clean Architecture적으로는, 결정을 늦추고 싶은 내용이기도 하기 때문에, 그 근처를 트레이드 해 나가는가 되어 느낌입니다.

    아직 시행착오 도중입니다만, 여기까지 알게 된 내용으로, 정리해 둡니다.

    이상입니다.

    좋은 웹페이지 즐겨찾기