Upgrade to Pro — share decks privately, control downloads, hide ads and more …

資産運用スタートアップの開発で採用した、PlayによるClean Arcitectureでの設計・開発事例

資産運用スタートアップの開発で採用した、PlayによるClean Arcitectureでの設計・開発事例

Yoshinobu Wakamatsu

October 26, 2019
Tweet

More Decks by Yoshinobu Wakamatsu

Other Decks in Technology

Transcript

  1. ϑΝϯυ ૊੒اۀ ౤ࢿՈ आΓखاۀ ϑΝϯυ΁ग़ࢿ ି෇ ෼഑ ฦࡁ རଉΛؚΊͯ ͓ۚΛฦ͢

    ӡ༻͞Εͨ ͓ۚΛड͚औΔ 'VOETͷ͘͠Έ  4DBMBؔ੢ 4VNNJU 
  2. γεςϜΞʔΩςΫνϟ #BDLFOE4FSWJDF 4DBMB DB, Datastore, External REST Services, etc. 4FSWJDFGSPOUFOE

    "ENJOGSPOUFOE 3&45"1* 3&45"1*  4DBMBؔ੢ 4VNNJU 
  3. γεςϜΞʔΩςΫνϟ #BDLFOE4FSWJDF 4DBMB DB, Datastore, External REST Services, etc. 4FSWJDFGSPOUFOE

    "ENJOGSPOUFOE ౤ࢿՈ޲͚ ΢ΣϒαΠτ 3&45"1* 3&45"1* ࣾ಺޲͚ ΦϖϨʔγϣϯը໘ αʔϏε͝ͱʹಠཱɾૄ݁߹ͳϑϩϯτΤϯυ  4DBMBؔ੢ 4VNNJU 
  4. γεςϜΞʔΩςΫνϟ #BDLFOE4FSWJDF 4DBMB DB, Datastore, External REST Services, etc. 4FSWJDFGSPOUFOE

    "ENJOGSPOUFOE 3&45"1* 3&45"1* ڞ௨όοΫΤϯυ αʔϏε υϝΠϯ͕ෳࡶɾີ઀ʹؔ܎͢ΔόοΫΤϯυ͸ ϞϊϦγοΫͳαʔϏεͰߏ੒  4DBMBؔ੢ 4VNNJU 
  5. ϞϊϦγοΫͳΞʔΩςΫνϟͷ࠾༻  • υϝΠϯͷؔ܎੔ཧ͸೉͍͠ • ޡͬͨίϯςΩετͷ෼ׂ͸ෳࡶ͚ͩ͞ΛߴΊͯ͠·͏ ΞΧ΢ϯτ ϑΝϯυ ސ٬࣋෼ ,:$

    ஫จ ೖग़ۚ υϝΠϯ಺ͷؔ܎ੑ͕੔ཧ͞ΕΔ·Ͱมߋίετͷ௿͍ߏ଄Λ࠾༻͍ͨ͠ σϙδοτ࢒ߴ ࢿۚҠಈ ސ٬ 'VOETʹ͓͚ΔίϯςΩετͷྫ  4DBMBؔ੢ 4VNNJU 
  6. ੍ޚͷྲྀΕͱΠϯλʔϑΣΠε • ੍ޚͷྲྀΕ্Ґ ˠ ԼҐʢந৅΁ͷґଘʣ • ΠϯλʔϑΣΠεΛ໌ࣔతʹఆٛͤͣ௚઀ݺͼग़͠ • ࣮૷͔Β෼཭͢ΔԸܙ͕গͳ͍ͱ൑அ •

    ੍ޚͷྲྀΕԼҐ ˠ ্Ґʢৄࡉ΁ͷґଘʣ • ݪଇ௨ΓԼҐϨΠϠʔͰΠϯλʔϑΣΠεΛఆٛɾ࣮૷Λ༩͑Δ  4DBMBؔ੢ 4VNNJU 
  7. ੍ޚͷྲྀΕ$POUSPMMFS "DUJPO def getList: Action[AnyContent] = asyncSecuredJsonAction(defaultRoles) { implicit request

    => ... usecase.fetchCustomerList.perform(customerCondition).map { case (customers, count) => Response(200, ListResponse(count, customers.map(CustomerCardDetailResponseModel.apply))) } } • 1MBZͷ$POUSPMMFS "DUJPO Λ࢖༻ • ೖग़ྗܗࣜͷม׵ͱ6TF$BTFͷݺͼग़͠ʹݶఆ͢Δ࣮૷ϧʔϧ  4DBMBؔ੢ 4VNNJU 
  8. ੍ޚͷྲྀΕ6TF$BTF case class FetchCustomerList()( implicit val customerRepository: CustomerReadRepository, implicit val

    ec: ExecutionContext ) extends UseCase { def perform(customerCondition: CustomerQueryCondition): Future[(Seq[Customer], Int)] = { for { customers <- customerRepository.findByCondition(customerCondition) count <- customerRepository.countByCondition(customerCondition) } yield { (customers, count) } } } • ΞϓϦέʔγϣϯݻ༗ͷಈ࡞Λ࣮૷ • 6TF$BTF୯ҐͰΫϥεΛ෼཭  4DBMBؔ੢ 4VNNJU 
  9. ੍ޚͷྲྀΕ3FTQPOTF.PEFM • ϨεϙϯεͷܕΛDBTFDMBTTͰఆٛ • &OUJUZ͋Δ͍͸ͦͷू໿͔Β3FTQPOTF.PEFMΛड͚औΓγϦΞϥΠζ case class CustomerStatusResponseModel( code: String,

    name: String, ) extends ResponseModel object CustomerStatusResponseModel { import CustomerPresenter._ def apply(status: CustomerStatus) = { new CustomerStatusResponseModel( status.code, status.name ) } }  4DBMBؔ੢ 4VNNJU 
  10. 4XBHHFSͷ࢓༷ͱ࣮૷্ͷܕͷ౷Ұ case class CustomerStatusResponseModel( code: String, name: String, ) extends

    ResponseModel object CustomerStatusResponseModel { import CustomerPresenter._ def apply(status: CustomerStatus) = { new CustomerStatusResponseModel( status.code, status.name ) } } 4XBHHFS্Ͱͷఆٛ 4XBHHFSͰఆٛͨ͠ܕͱΞϓϦέʔγϣϯ্Ͱ࣮૷͢Δํͷ౷Ұ ΞϓϦέʔγϣϯ্ͷ࣮૷  4DBMBؔ੢ 4VNNJU 
  11. %PNBJO υϝΠϯݻ༗ͷ֓೦ɾ ϏδωεϩδοΫΛදݱ͢ΔϨΠϠʔ *OGSBTUSVDUVSF *OUFSGBDFT "QQMJDBUJPO %PNBJO ओͳߏ੒ཁૉ • &OUJUZ

    7BMVF0CKFDU΋ؚΉʣ • %PNBJO4FSWJDF • 3FQPTJUPSZ  4DBMBؔ੢ 4VNNJU 
  12. 3FQPTJUPSZ • ӬଓԽɾӬଓԽ͞ΕͨσʔλͷࢀরΛఏڙ͢ΔΠϯλʔϑΣΠε • %#͚ͩͰͳ͘3&454FSWJDF $MJFOUͳͲɺ ෭࡞༻Λ൐͏೚ҙͷૢ࡞ͷΠϯλʔϑΣΠεͱͯ͠ఆٛ trait CustomerReadRepository extends

    ReadRepository with CustomerBaseRepository trait CustomerBaseRepository { def findCustomers: Future[Seq[Customer]] }  4DBMBؔ੢ 4VNNJU 
  13. trait CustomerRepository extends ReadWriteRepository with CustomerBaseRepository { def save(customer: Customer):

    Future[ID[Customer]] } trait CustomerReadRepository extends ReadRepository with CustomerBaseRepository trait CustomerBaseRepository { def findCustomers: Future[Seq[Customer]] } SFBEPOMZ SFBE XSJUFՄೳ SFBEPOMZXSJUBCMFͳΠϯλʔϑΣΠε෼཭  4DBMBؔ੢ 4VNNJU 
  14. class FindCustomers( implicit val customerRepository: CustomerReadRepository, val ec: ExecutionContext, )

    extends UseCase { def perform(): Future[Seq[Customer]] = { ... val customersF = customerRepository.findCustomers ... } } ಡΈऔΓઐ༻Ͱ͋Δ͜ͱ͕ อূ͞Ε͍ͯΔ ੍ݶͷ͋Δ࣮૷Λద༻Մೳ ʢFHSFBEFSFOEQPJOUͷ࢖༻ SFBEPOMZXSJUBCMFͳΠϯλʔϑΣΠε෼཭  4DBMBؔ੢ 4VNNJU 
  15. *OGSBTUSVDUVSF࣮૷ͷྫ • %#BDDFTT 4MJDL • 3FEJT • 3&454FSWJDF )551 •

    4.51 • ϚωʔδυαʔϏεʢ"NB[PO424 4 ,.4 FUDʣ  4DBMBؔ੢ 4VNNJU 
  16. *OGSBTUSVDUVSFͷ࣮૷ྫ4MJDL trait CustomerTable extends ColumnMappers { this: Profile => import

    profile.api._ class Customers(tag: Tag) extends Table[CustomerRow](tag, "customer") { def id = column[ID[Customer]]("id", O.SqlType("INT"), O.PrimaryKey, O.AutoInc) def email = column[String]("email", O.SqlType("VARCHAR(256)")) def status = column[CustomerStatus]("status", O.SqlType("INT")) ... type TableRecord = (Option[ID[Customer]], String, CustomerStatus) def * = (id.?, email, status) <> ( (t: TableRecord) => { val (id, email, status_) = t CustomerRow(id, email, status) }, (customer: CustomerRow) => { Option((customer.id, customer.email, customer.status)) } ) } }  4DBMBؔ੢ 4VNNJU 
  17. *OGSBTUSVDUVSFͷ࣮૷ྫ4MJDL class CustomerDAO(val dbConfig: DatabaseConfig[JdbcProfile])(implicit val ec: ExecutionContext) extends CustomerDAOLike

    with Profile with DefaultSlickDBConfig trait CustomerDAOLike extends CustomerRepository with CustomerReadRepository with CustomerTable this: Profile with SlickDBConfig[JdbcProfile] => def save(customer: Customer) = { val query = customerQuery += customer db.run(query.transactionally) } ... }  4DBMBؔ੢ 4VNNJU 
  18. *OGSBTUSVDUVSFͷ࣮૷ྫ3&45 4FSWJDF trait CustomerRestServiceLike extends CustomerReadRepository { implicit val ec:

    ExecutionContext val client: WSClient val baseUrl: String lazy val baseUrl = s”..." def findCustomers: Future[Seq[Customer]] = { val requestUrl = s"$baseUrl/customer" for { response <- client.url(requestUrl).get() } yield { val results = for { ... } yield result } results.flatten } } }  4DBMBؔ੢ 4VNNJU 
  19. *NQMJDJUQBSBNFUFSʹΑΔ%* trait InfrastructureComponents extends DatabaseComponents with CacheLayerComponents with S3Components ...

    { this: BuiltInComponentsFromContext => }  ϨΠϠʔ୯ҐͰͷ$PNQPOFOUʹNJYJO trait DatabaseComponents extends SlickComponents { this: BuiltInComponentsFromContext => implicit lazy val customerDAO: CustomerRepository = new CustomerDAO(dbConfig) implicit lazy val customerReadDAO: CustomerReadRepository = new CustomerDAO(slaveDbConfig) ... }  ύοέʔδ͝ͱͷΠϯελϯεॳظԽΛఆٛ  4DBMBؔ੢ 4VNNJU 
  20. *NQMJDJUQBSBNFUFSʹΑΔ%* class ApplicationComponents(context: Context) extends BuiltInComponentsFromContext(context) with I18nComponents with HttpFiltersComponents

    with AdminControllerComponents with ServiceControllerComponents with InfrastructureComponents { ... }  ֤ϨΠϠʔͷ$PNQPOFOUTΛ"QQMJDBUJPO$PNQPOFOUT΁NJYJO class MainApplicationLoader extends ApplicationLoader { def load(context: Context): Application = { new ApplicationComponents(context).application } }  "QQMJDBUJPO-PBEFSͷఆٛ  4DBMBؔ੢ 4VNNJU 
  21. ಉҰΠϯλʔϑΣΠε্ͷ࣮૷ͷަ׵ trait DatabaseComponents extends SlickComponents { this: BuiltInComponentsFromContext => implicit

    lazy val customerDAO: CustomerRepository = new CustomerDAO(dbConfig) implicit lazy val customerReadDAO: CustomerReadRepository = new CustomerDAO(slaveDbConfig) } σϑΥϧτͰղܾ͞ΕΔ࣮૷ʢJNQMJDJU lazy val adminCustomerComponents = new CustomerComponents { import CustomerComponents._ val fetchCustomerList = FetchCustomerList() ... } *NQMJDJUQBSBNFUFSʹΑΔ%*  4DBMBؔ੢ 4VNNJU 
  22. ಉҰΠϯλʔϑΣΠε্ͷ࣮૷ͷަ׵ ίϯετϥΫλύϥϝʔλͰ࣮૷Λ໌ࣔతʹࢦఆ lazy val adminCustomerComponents = new CustomerComponents { import

    CustomerComponents._ val fetchCustomerList = FetchCustomerList(cachedCustomerDAO, ec) ... } trait CacheLayerComponents { this: BuiltInComponentsFromContext with RedisComponents with DatabaseComponents => lazy val cachedCustomerDAO: CusotmerReadRepository = new CustomerRedisCache(customerReadDAO)(redisClient, executionContext) } ҟͳΔ࣮૷ͷྫʢΩϟογϡʣ  4DBMBؔ੢ 4VNNJU