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

資産運用スタートアップの開発で採用した、PlayによるClean Arcitectureて...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

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

Avatar for Yoshinobu Wakamatsu

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