Save 37% off PRO during our Black Friday Sale! »

痒いところに手を届かせるArchUnitの利用法 ~アーキテクチャーテストからアプリケーション解析まで~

85da685d91fda190e2e3162d0de248a4?s=47 Recruit
December 25, 2020

痒いところに手を届かせるArchUnitの利用法 ~アーキテクチャーテストからアプリケーション解析まで~

2020/11/07_日本Javaユーザグループ主催「JJUG CCC 2020 Fall」での、小谷野の講演資料になります
https://ccc2020fall.java-users.jp/

【動画】https://www.youtube.com/watch?v=tigesXMmTpc&list=PLy44EKO1L0eIxmapLWe6_60nJwt_EAsjU&index=2

#conference #engineering

85da685d91fda190e2e3162d0de248a4?s=128

Recruit

December 25, 2020
Tweet

Transcript

  1. ᙱ͍ͱ͜ΖʹखΛಧ͔ͤΔ
 "SDI6OJUͷར༻๏
 ʙΞʔΩςΫνϟʔςετ͔ΒΞϓϦέʔγϣϯղੳ·Ͱʙ JJUG CCC 2020 Fall @ 2020/11/07
 Yuji

    Koyano
  2. ykoyano Yuji Koyano @bandwagondagon w גࣜձࣾϦΫϧʔτͷΤϯδχΞ w ࣾ಺ԣஅσʔλج൫૊৫Ͱͷ։ൃ΍
 ৽نࣄۀͷ"OESPJEΞϓϦ։ൃΛܦͯ
 ݱࡏ͸ϗοτϖούʔϏϡʔςΟʔͷ


    αʔόʔαΠυΤϯδχΞΛ୲౰ 2
  3. ϗοτϖούʔϏϡʔςΟʔͱ͸ 3 ࠃ಺࠷େڃͷϔΞαϩϯɾϦϥΫϏϡʔςΟαϩϯͷݕࡧɾ༧໿αʔϏε
 ೥ؒ໿ԯສճͷ༧໿͕ߦΘΕΔʢ࣌఺ʣ IUUQTCFBVUZIPUQFQQFSKQEPDHVJEFTBJTIJOEBUBIUNM

  4. ͜ͷηογϣϯͷ໨త w ϗοτϖούʔϏϡʔςΟʔͷϞόΠϧΞϓϦ޲͚"1*ͱ
 "OESPJEΞϓϦʹ͓͚Δ"SDI6OJUͷར༻ࣄྫΛ঺հ
 ࢼߦࡨޡதͷ΋ͷ΋ؚΈ·͢  w "SDI6OJUΛར༻ͨ͠ΞʔΩςΫνϟͷ୯ମςετ͔Β
 ੩తղੳΛར༻ͨ͠ΞϓϦέʔγϣϯղੳ·Ͱ
 ༷ʑͳ"SDI6OJUͷ࢖͍ํΛ஌ͬͯ΋Β͏

    w ͔Β࣮૷͠Α͏ͱ͢Δͱେม͚ͩͲɺ΋͠Ͱ͖ͨΒخ͍ࣗ͠ಈԽΛ "SDI6OJUΛ࢖࣮ͬͯફ͍ͨ͠ͱࢥͬͯ΋Β͏
  5. ΞʔΩςΫνϟʔςετͷར༻๏ "SDI6OJUΛಋೖͨ͠ϓϩμΫτͷഎܠ ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ "SDI6OJUͷ࢓૊Έͱ"1* "SDI6OJUͷجຊతͳػೳ

  6. ΞʔΩςΫνϟʔςετͷར༻๏ "SDI6OJUΛಋೖͨ͠ϓϩμΫτͷഎܠ ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ "SDI6OJUͷ࢓૊Έͱ"1* "SDI6OJUͷجຊతͳػೳ

  7. w 5/(5FDIOPMPHZ$POTVMUJOH(NC)ࣾʹ
 Αͬͯ։ൃ͞Ε͍ͯΔ044ϥΠϒϥϦ w ΞϓϦέʔγϣϯͷΞʔΩςΫνϟʔ੍໿Λ
 6OJU5FTUͱ࣮ͯ͠૷͢Δ͜ͱͰ
 ΞʔΩςΫνϟʔͷςετΛ࣮ݱ͢Δ w +BWB͚ͩͰ͸ͳ͘,PUMJOͳͲͷ+7.ݴޠͷ
 ΞϓϦέʔγϣϯͰ΋ར༻͕Մೳ

    7 ArchUnitͱ͸ IUUQTXXXBSDIVOJUPSH
  8. 8 IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM JUnitͱͷ࿈ܞ @RunWith(ArchUnitRunner.class) // JUnit5Ͱ͸ෆཁ @AnalyzeClasses(packages = "com.myapp") public

    class ArchitectureTest { @ArchTest public static final ArchRule rule1 = classes()
 .should()... @ArchTest public static final ArchRule rule2 = classes() .should()... @ArchTest public static void rule3(JavaClasses classes) { // } } w +6OJUͱ࿈ܞͤ͞Δ͜ͱͰ
 "SVD6OJUͰ࣮૷੍ͨ͠໿Λ
 ͦͷ··ςετϝιου
 ͱͯ͠ఆٛͰ͖Δ w ଞͷςετϑϨʔϜϫʔΫʹ
 ૊ΈࠐΉ͜ͱ΋Մೳ
  9.  9 classes() .that() .resideInAPackage("..foo..") .should() .onlyHaveDependentClassesThat() .resideInAnyPackage( "..source.one..", "..foo.."

    ); ੍໿ΛJavaίʔυͷUTͰ࣮૷Ͱ͖Δ IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM
  10.  10 classes() .that() .haveNameMatching(".*Bar") .should() .onlyBeAccessed() .byClassesThat() .haveSimpleName("Bar"); Ϋϥεͷґଘؔ܎ͷ੍໿ςετ

    IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM
  11.  11 classes() .that() .implement(Connection.class) .should() .haveSimpleNameEndingWith(
 "Connection"
 ); ໋໊ن໿ͷ੍໿ςετ

    IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM
  12. 12 IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM ஈ֊తͳςετͷಋೖ w ͋Δ࣌఺ͷΞʔΩςΫνϟʔςετͷ
 ࣮ߦ݁ՌͷεφοϓγϣοτΛར༻ͯ͠
 ஈ֊తͳςετͷಋೖ͕Մೳ w Ұ෦ͷίʔυͷ੍໿ҧ൓Λແࢹͯ͠
 ৽ࠩ͘͠෼͕͋ͬͨίʔυʹର͚ͯͩ͠

    ΞʔΩςΫνϟʔͷ੍໿Λڧ੍Ͱ͖Δ ArchRule rule = FreezingArchRule .freeze( classes().should()... );

  13. ಋೖϢʔεέʔεྫ w ґଘؔ܎੍໿ɾ໋໊نଇͳͲΛςετͱ࣮ͯ͠૷ͯ͠ڧ੍͢Δ
 ΞʔΩςΫνϟʔϨϏϡʔͷࣗಈԽ
 ಛఆͷ࣮૷ϧʔϧͷ࿙ΕΛ๷͙
 ςετͱͯ͠ͷυΩϡϝϯςʔγϣϯʹΑΔΞʔΩςΫνϟʔͷ໌จԽ w ஈ֊తͳΞʔΩςΫνϟʔҠߦɾ੔උͷαϙʔτ
 ϦϑΝΫλϦϯάɾϦΞʔΩςΟϯάର৅ͷίʔυʹ෦෼తʹ65Λద༻ w

    ΞϓϦέʔγϣϯղੳͷϑϨʔϜϫʔΫͱͯ͠ར༻
 ґଘؔ܎ʹؔ͢Δ৘ใͷυΩϡϝϯτͷࣗಈੜ੒ 13
  14. ΞʔΩςΫνϟʔςετͷར༻๏ "SDI6OJUΛಋೖͨ͠ϓϩμΫτͷഎܠ ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ "SDI6OJUͷ࢓૊Έͱ"1* "SDI6OJUͷجຊతͳػೳ

  15. $PSF"1* -BOH"1* -JCSBSZ"1* $MBTTϑΝΠϧͷCZUFDPEFΛղੳͯ͠Ϋϥε΍
 ϝιουؒͷґଘؔ܎ͳͲϓϦϛςΟϒͳ৘ใΛఏڙ $PSF"1*͔ΒऔಘͰ͖Δ৘ใΛݩʹґଘؔ܎ͷ
 ੍໿Λॊೈ͔ͭ؆ܿʹ࣮૷͢ΔͨΊʹ"1*Λఏڙ -BZFSFEɾ0OJPOΞʔΩςΫνϟʔͳͲయܕతͳ
 ΞʔΩςΫνϟʔͷґଘؔ܎ϧʔϧηοτΛఏڙ 15

    ArchUnit͕ఏڙ͢ΔAPI
  16.  16 // ΫϥεϑΝΠϧ͔ΒJavaClassesΠϯελϯεΛऔಘ JavaClasses importedClasses = new ClassFileImporter() //

    ςετίʔυͷbytecode͸আ֎͢Δ .withImportOption( ImportOption.d.DO_NOT_INCLUDE_TESTS ) // Ϋϥεύε্ͷbytecodeΛಡΈࠐΉ .importClasspath(); Core APIͱbytecode w $MBTT'JMF*NQPSUFSʹΑͬͯ
 ࢦఆͨ͠ΫϥεϑΝΠϧͷ CZUFDPEFΛಡΈࠐΉ w "4.ϥΠϒϥϦΛར༻ͯ͠
 CZUFDPEFΛղੳͯ͠
 "SDI6OJUݻ༗ͷදݱʹม׵͠
 $PSF"1*Λհͯ͠։ൃऀʹ
 ৘ใΛఏڙ
  17. w +BWBόΠτίʔυͷಡΈऔΓɾ
 ॻ͖ࠐΈɾૢ࡞Λߦ͏ϑϨʔϜϫʔΫ w (SBEMF΍,PUMJO$PNQJMFSΛؚΉ
 ଟ͘ͷϓϩδΣΫτͰ࢖༻͞Ε͍ͯΔ w +%,಺෦Ͱ΋ϥϜμࣜͷ࣮ߦ࣌αϙʔτΛ
 ࣮ݱ͢ΔͨΊʹར༻ IUUQTCMPHTPSBDMFDPNKBWBNBHB[JOFSFBMXPSMECZUFDPEFIBOEMJOHXJUIBTN

    ASMϥΠϒϥϦ
  18. 18 w +BWB3FqFDUJPO"1*Λ
 ֦ுͨ͠Α͏ͳ"1* w Ϋϥεͷ৘ใʹ3FqFDUJPO"1*
 ΑΓ΋ൺֱతѻ͍΍͍͢ܗͰ
 ΞΫηεͰ͖Δ
 FY0QUJPOBMɾίϨΫγϣϯ"1* IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM

    Core APIͱRef ection API l
  19. 19 w Πϯϙʔτͨ͠CZUFDPEF্ͷ
 ΫϥεɾϝιουɾϑΟʔϧυͳͲͷཁૉؒͷ
 ΞΫηεΛશͯه࿥͠ґଘؔ܎άϥϑΛߏங w ґଘཁૉʹΞΫηε͢ΔͨΊͷ"1*Λఏڙ
 FYHFU'JFME"DDFTTFT'SPN4FMG 
 ϑΟʔϧυʹΞΫηε͍ͯ͠ΔཁૉΛऔಘ


    HFU.FUIPE$BMMT5P4FMG 
 ϝιου͕಺෦Ͱݺͼग़͍ͯ͠ΔϝιουΛऔಘ IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM Core APIͱґଘؔ܎
  20.  20 // serviceͱ͍͏จࣈΛؚΉpackage഑ԼͷΫϥε͸
 // controllerͱ͍͏จࣈΛؚΉpackage഑ԼͷΫϥεʹ
 // ΞΫηεͯ͠͸͍͚ͳ͍
 ArchRule rule

    = ArchRuleDefinition
 .noClasses() .that()
 .resideInAPackage("..service..") .should()
 .accessClassesThat()
 .resideInAPackage("..controller.."); 
 rule.check(importedClasses);
 Lang API w $PSF"1*͔ΒऔಘͰ͖Δ৘ใΛ ݩʹґଘؔ܎ͷ੍໿Λॊೈ͔ͭ ؆ܿʹ࣮૷͢ΔͨΊͷ"1* w ੍໿ʹҧ൓͢ΔΫϥεͳͲ͕
 ଘࡏͨ͠৔߹ʹςετΛ
 ࣦഊͤ͞Δ͜ͱ͕Ͱ͖Δ w جຊతʹ͸͜ͷ"1*Λ࢖ͬͯ
 ςετΛ࣮૷͢Δ͜ͱ͕ଟ͍
  21.  21 // ݕࠪର৅ͷΫϥε͕@PayloadΞϊςʔγϣϯ͕෇༩͞Εͨ
 // ϑΟʔϧυΛ͍࣋ͬͯΔ͔ΛνΣοΫ͢ΔΧελϜϧʔϧ
 DescribedPredicate<JavaClass>
 haveAFieldAnnotatedWithPayload = new

    DescribedPredicate<>( "have a field annotated with @Payload” ) { @Override public boolean apply(JavaClass input) { return checkCustomRule(input); } }; Lang APIͱΧελϜϧʔϧ w %FTDSJCFE1SFEJDBUFͳͲͷ
 ΠϯλʔϑΣʔεΛ࣮૷ͯ͠
 ಠࣗͷΧελϜϧʔϧΛ
 ࡞੒͢Δ͜ͱ͕Մೳ w ࣮ݱ͍ͨ͠ϧʔϧʹରԠ͢Δ
 "1*͕ଘࡏ͠ͳ͍৔߹͸
 -BOH"1*ͱ$PSF"1*Λ
 ૊Έ߹Θ੍ͤͯ໿Λ࣮૷͢Δ
  22.  22 Library API w ࢦఆͨ͠ύοέʔδΛ
 άϧʔϐϯάͯ͠యܕతͳ
 ΞʔΩςΫνϟʔͷ
 ϨΠϠʔͱඥ෇͚Δ
 "1*Λఏڙ

    w ϨΠϠʔͷґଘؔ܎੍໿Λ
 ͦͷ··ΞϓϦέʔγϣϯʹ
 ద༻͢Δ͜ͱ͕Ͱ͖Δ layeredArchitecture() .layer("Controller").definedBy("..controller..") .layer("Service").definedBy("..service..") .layer("Persistence").definedBy("..persistence..") .whereLayer("Controller").mayNotBeAccessedByAnyLayer() .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service");
  23.  23 Library API layeredArchitecture() .layer("Controller").definedBy("..controller..") .layer("Service").definedBy("..service..") .layer("Persistence").definedBy("..persistence..") .whereLayer("Controller").mayNotBeAccessedByAnyLayer() .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")

    .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service");
  24. w "SDI6OJUͷಛ௃
 ઃఆϑΝΠϧ΍%4-Ͱ͸ͳ͘ϥΠϒϥϦ͕ఏڙ͢Δ"1*Λ༻͍ͨ
 +BWBίʔυͰ+6OJUͳͲͷ୯ମςετͱͯ͠هड़Ͱ͖Δ w ྨࣅͷ͜ͱ͕Ұ෦࣮ݱͰ͖Δπʔϧ
 ੍໿ݕࠪɿ"TQFDU+ $IFDLTUZMF 'JOE#VHT
 ґଘؔ܎ɿK2"TTJTUBOU

    %FQFOE %FHSBQI %FQUFDUJWF ArchUnitͷಛ௃ͱྨࣅπʔϧͱͷൺֱ 24
  25. w "SDI6OJUͷಛ௃
 ઃఆϑΝΠϧ΍%4-Ͱ͸ͳ͘ϥΠϒϥϦ͕ఏڙ͢Δ"1*Λ༻͍ͨ
 +BWBίʔυͰ+6OJUͳͲͷ୯ମςετͱͯ͠هड़Ͱ͖Δ w ྨࣅͷ͜ͱ͕Ұ෦࣮ݱͰ͖Δπʔϧ
 ੍໿ݕࠪɿ"TQFDU+ $IFDLTUZMF 'JOE#VHT
 ґଘؔ܎ɿK2"TTJTUBOU

    %FQFOE %FHSBQI %FQUFDUJWF ArchUnitͷಛ௃ͱྨࣅπʔϧͱͷൺֱ 25 
 
 "SDI6OJUͷศརͦ͏ͳ͜ͱ͸Θ͔Δɻ
 ͚ͩͲɺ࣮ࡍʹͲ͏͍͏࢖͍ํΛͯ͠
 ΞϓϦέʔγϣϯʹಋೖ͍ͯ͠Δͷ͔ʁ Α͋͘Δٙ໰
  26. ΞʔΩςΫνϟʔςετͷར༻๏ "SDI6OJUΛಋೖͨ͠ϓϩμΫτͷഎܠ ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ "SDI6OJUͷ࢓૊Έͱ"1* "SDI6OJUͷجຊతͳػೳ

  27. w ϔΞαϩϯɾϦϥΫϏϡʔςΟαϩϯͷ ݕࡧɾ༧໿αʔϏε w ϞόΠϧΞϓϦ J04"OESPJE 
 8FC 1$ɾεϚʔτϑΥϯ 


    ʹͯల։ w ϞόΠϧΞϓϦ޲͚"1*ͱ
 "OESPJEΞϓϦʹ"SDI6OJUͷಋೖ ϗοτϖούʔϏϡʔςΟʔͱ͸ 27 IUUQTCFBVUZIPUQFQQFSKQEPDHVJEFTBJTIJOEBUBIUNM
  28. ϞόΠϧΞϓϦ޲͚APIͷΞʔΩςΫνϟʔ #BDLFOE
 "1*
 GPS403 #''"1*
 GPS40& J04"OESPJE ΞϓϦ %#ݕࡧΤϯδϯ 28

    w #'' #BDLFOET'PS'SPOUFOET Λར༻ͨ͠
 ϚΠΫϩαʔϏεΞʔΩςΫνϟʔΛಋೖ w ΞϓϦέʔγϣϯن໛΍νʔϜߏ੒Λߟྀͨ͠40&ɾ403ͷ෼཭ʹΑͬͯ
 "1*։ൃεϐʔυͷߴ଎ԽΛ໨ࢦ͢
  29. #BDLFOE"1* #''"1* දࣔཁ݅ʹؔΘΔॲཧ 40& 
 ΞϓϦಛ༗ͷॲཧ ΞϓϦͷϢʔεέʔεʹ߹Θͤͨ"1* 4QSJOH8FC'MVY,PUMJO 29 ٕज़ελοΫ΍੹຿ͷҧ͍

    4QSJOH.7$+BWB σʔλͷӬଓԽʹؔΘΔॲཧ 403  ۀ຿ϩδοΫ Ϧιʔεࢦ޲ͷ"1*
  30. ϞόΠϧΞϓϦ޲͚APIͷΞʔΩςΫνϟʔ #BDLFOE
 "1*
 GPS403 #''"1*
 GPS40& J04"OESPJE ΞϓϦ %#ݕࡧΤϯδϯ 30

    w ΞϓϦͷϢʔεέʔε୯ҐͰ࡞੒ͨ͠#''ͷ"1*͸
 σʔλͷϦιʔε୯ҐͰ࡞੒ͨ͠#BDLFOEͷ"1*͔Β
 ඞཁͳ৘ใΛू໿ͯ͠ΞϓϦʹఏڙ A C A B C B
  31. #BDLFOE"1* #''"1* 31 ιϑτ΢ΣΞΞʔΩςΫνϟʔ $POUSPMMFSΫϥε૚ 3FQPTJUPSZΫϥε૚ 6TF$BTFΫϥε૚ υϝΠϯϞσϧ૚ $POUSPMMFSΫϥε૚ 3FQPTJUPSZΫϥε૚

    6TF$BTFΫϥε૚ 4FSWJDFΫϥε૚ ໼ҹ͸ґଘؔ܎ͷ޲͖
  32. "OESPJEͷը໘Ϋϥε਺໿ #''ͷ"1*਺໿ 32 ਺ࣈͰݟΔΞϓϦέʔγϣϯͷن໛ #BDLFOEͷ"1*਺໿ ࢀর͢Δ%#ͷςʔϒϧ਺໿ w ೥ۙ͘ଓ͍͍ͯΔαʔϏεͰ͋Γɺ"1*͚ͩͰ΋ൺֱతେن໛ͳαʔϏε w چ"1*͔Βݱߦͷ#''ΞʔΩςΫνϟʔ΁ஈ֊తʹϦϓϨΠε͠ͳ͕Β


    ฏߦͰΤϯϋϯε΋࣮ࢪ
  33. 33 (༨ஊ) αʔϏεΛ͞Βʹ෼ׂ͠ͳ͍ͷ͔ʁ w νʔϜߏ੒΍ٕज़ཁҼ͔Β8FCΞϓϦ޲͚ͷ#''΍
 #BDLFOE"1*ͷυϝΠϯ෼ׂ͸࣮ࢪ͍ͯ͠ͳ͍ঢ়ଶ w ඞཁੑ͕͋Ε͹ࠓޙ࣮ࢪ͢ΔՄೳੑ͸͋Δ #BDLFOE"1* #''GPS8FC

    33 #BDLFOE"1* #BDLFOE"1* #''GPS"QQ
  34. IUUQTXXXTMJEFTIBSFOFU3FDSVJU-JGFTUZMFBQJC⒎CBDLFOE 34 ͞ΒͳΔৄࡉΛ஌Γ͍ͨํ͸ w ࡢ೥ͷ++6($$$4QSJOHͰ
 ฐࣾΤϯδχΞࡧख͕
 #''#BDLFOE෼ׂʹؔ͢Δ
 ൃදΛ͠·ͨ͠ w #''#BDLFOE෼ׂͷํ਑΍


    ઃܭɾ࣮૷ͷৄࡉΛ஌Γ͍ͨํ͸
 ͥͻ͝ཡԼ͍͞
  35. ΞʔΩςΫνϟʔςετͷར༻๏ "SDI6OJUΛಋೖͨ͠ϓϩμΫτͷഎܠ ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ "SDI6OJUͷ࢓૊Έͱ"1* "SDI6OJUͷجຊతͳػೳ

  36. CASE 1 : υϝΠϯؒͷґଘؔ܎ͷ੍ޚ CASE 2 : ෆ҆ఆͳίʔυͷґଘؔ܎ͷ੍ޚ 36 ΞʔΩςΫνϟʔςετͷར༻๏

  37. CASE 1 : υϝΠϯؒͷґଘؔ܎ͷ੍ޚ 37

  38. w جຊతͳΞʔΩςΫνϟʔཧղ͸
 ઃܭυΩϡϝϯτΛ௨ͯ͡
 νʔϜϝϯόʔʹਁಁ͍ͯ͠Δঢ়ଶ w 13্ͰϨΠϠʔؒͷґଘؔ܎ʹ
 ؔ͢Δࢦఠ͕ൃੜ͢Δ͜ͱ΋ͳ͘
 ϨΠϠʔʹؔ͢ΔΞʔΩςΫνϟʔ ςετ΋ಛʹඞཁͱ͍ͯ͠ͳ͍ঢ়ଶ എܠɿϨΠϠʔؒͷґଘؔ܎

    38  $POUSPMMFSΫϥε૚ 3FQPTJUPSZΫϥε૚ 6TF$BTFΫϥε૚ υϝΠϯϞσϧ૚
  39.  w ϔΞαϩϯͱϦϥΫϏϡʔςΟʔαϩϯͰ͸༧໿ͷ࢓༷͕ҟͳΔͨΊ
 ࣅͨߏ଄͕ͩύοέʔδͷҟͳΔυϝΠϯϞσϧ΍%UPΫϥε͕ଘࡏ͢Δ w EPNBJONPEFMIBJSNFTTBHF%FMJWFSFE$PVQPO
 EPNBJONPEFMLJSFJNFTTBHF%FMJWFSFE$PVQPO
 JOGSBTUSVDUVSFEUPIBJSDPVQPO$PVQPO%JTDPVOU5ZQF
 JOGSBTUSVDUVSFEUPLJSFJDPVQPO$PVQPO%JTDPVOU5ZQF എܠɿରশతͳߏ଄Λ࣋ͭυϝΠϯ

    39
  40.  ՝୊ɿҙਤ͠ͳ͍υϝΠϯͷґଘ͕ൃੜ w *%&ͷΠϯϙʔτิ׬Λ࢖ͬͯίʔσΟϯάΛߦͬͨࡍ
 ޡͬͯຊདྷґଘͯ͠͸͍͚ͳ͍ผυϝΠϯͷΫϥεʹґଘͨ͠ίʔυΛ࣮૷ w ͨ·ͨ·࢓༷ͷࠩҟΛߟྀͨ͠65ͷςετέʔε͕࿙Εͯ͠·͍ͬͯͨͨΊ
 65΍ϨϏϡʔͰ΋ϛεΛݕ஌Ͱ͖ͣϚʔδ͞Εͯ͠·ͬͨ 40

  41.  TRYɿಛఆ৚݅ԼͰͷΠϯϙʔτΛ๷ࢭ 41 @RunWith(ArchUnitRunner.class) @AnalyzeClasses(packages = BASE_PACKAGE) public class HairKireiDependencyTest

    { @ArchTest static final ArchRule 
 hairύοέʔδͷΫϥε͕kireiύοέʔδͷΫϥεʹґଘͯ͠ͳ͍͜ͱΛ֬ೝ = classes() .that() // ύοέʔδ໊ͷύεʹhairΛؚΉΫϥεΛநग़ .resideInAPackage("..hair..") // Ұ෦ͷྫ֎Λআ͍ͯɺύοέʔδ໊ͷύεʹkireiΛؚΉΫϥε΁ͷґଘΛېࢭ .should(ArchConditions.onlyDependOnClassesThat(NOT_DEPEND_KIREI_CLASS)); }
  42.  TRYɿಛఆ৚݅ԼͰͷΠϯϙʔτΛ๷ࢭ 42 private static final DescribedPredicate<JavaClass> NOT_DEPEND_KIREI_CLASS = new

    DescribedPredicate<>("must not depend on classes in kirei package") { 
 @Override public boolean apply(JavaClass input) { if (!input.getPackageName().contains("kirei")) { return true; } // @KireiResourceUsedByHairΞϊςʔγϣϯ͕෇༩͞Ε͍ͯΔΫϥε͸ྫ֎తʹґଘΛڐՄ͢Δ if (input.isAnnotatedWith(KireiResourceUsedByHair.class)) { return true; } } };
  43. TRYɿಛఆ৚݅ԼͰͷΠϯϙʔτΛ๷ࢭ 43 ϨΠϠʔ಺ͷґଘؔ܎Λ੍ޚ͢Δ IUUQTXXXPSFJMMZDPNMJCSBSZWJFX
 TPGUXBSFBSDIJUFDUVSFQBUUFSOT

  44. ิ଍ɿϞδϡʔϧ෼ׂͱͷൺֱ 44 ϨΠϠʔ಺ͷґଘؔ܎Λ੍ޚ͢Δ IUUQTXXXPSFJMMZDPNMJCSBSZWJFX
 TPGUXBSFBSDIJUFDUVSFQBUUFSOT w Ϟδϡʔϧ෼ׂͰґଘؔ܎Λ
 ੍ޚͰ͖͍ͯΔͳΒͦΕͰे෼ w ྫ֎έʔε͕͋ͬͯ෼ׂͮ͠Β͍


    ·ͩ෼ׂ͢Δ΄Ͳͷن໛Ͱ΋ͳ͍
 কདྷͷ෼ׂʹઌཱͬͨୈҰา
 ͱ͍ͬͨ৔߹ʹ͸༗༻
  45. 45 CASE 2 : ෆ҆ఆͳίʔυͷґଘؔ܎ͷ੍ޚ

  46.  IUUQTUXJUUFSDPNKBIOOJFTUBUVT 46 w ༏ઌ౓΍֎෦؀ڥͳͲͷཁҼͰ
 ࣮૷͕Ұ෦׬ྃͭͭ͠΋
 ͳ͔ͳ͔ϦϦʔεͰ͖ͳ͍ϒϥϯν͕ଘࡏ w ϒϥϯνͷϥΠϑλΠϜ͕ෆ༻ҙʹ௕͘ ͳͬͯ͠·͓ͬͯΓίϯϑϦΫτ͕


    ൃੜ͠΍͘͢ͳ͍ͬͯͨ എܠɿฏߦ͢Δϒϥϯνͱίʔυͷෳࡶ͞
  47.  എܠɿfeatureτάϧʹΑΔϒϥϯνͷ࡟ݮ 47 IUUQTXXXNBSUJOGPXMFSDPNBSUJDMFTGFBUVSFUPHHMFTIUNM w ฏߦ͢Δϒϥϯνͷ਺ΛݮΒͨ͢Ίʹ
 ੩తͳϦϦʔετάϧΛಋೖͯ͠
 ϝΠϯϒϥϯνʹϚʔδ w ࣮ͨͩݧతʹϦϦʔε͢Δ಺༰Ͱ͋Γ


    ࢓༷͕ॾࣄ৘Ͱෆ҆ఆͳঢ়ଶͩͬͨ w ͕ͨͬͯ͠ଞͷ҆ఆͨ͠ίʔυ͔Β
 ෆ҆ఆͳίʔυʹґଘͤͨ͘͞ͳ͍
  48.  ՝୊ɿෆ҆ఆͳίʔυ͕ϝΠϯϥΠϯʹ߹ྲྀ 48  #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF #PPLNBSL4FSWJDF $PVQPO4FSWJDF

    $PVQPO3FQPTJUPSZ υϝΠϯϞσϧ ໼ҹ͸ґଘؔ܎ͷ޲͖ &YQFSJNFOUBM6TFDBTF &YQFSJNFOUBM4FSWJDF &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ
  49.  ՝୊ɿෆ҆ఆͳίʔυ͕ϝΠϯϥΠϯʹ߹ྲྀ 49  #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF &YQFSJNFOUBM6TFDBTF #PPLNBSL4FSWJDF

    $PVQPO4FSWJDF &YQFSJNFOUBM4FSWJDF $PVQPO3FQPTJUPSZ &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ ໼ҹ͸ґଘؔ܎ͷ޲͖ υϝΠϯϞσϧ
  50.  ՝୊ɿෆ҆ఆͳίʔυ͕ϝΠϯϥΠϯʹ߹ྲྀ 50  #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF &YQFSJNFOUBM6TFDBTF #PPLNBSL4FSWJDF

    $PVQPO4FSWJDF &YQFSJNFOUBM4FSWJDF $PVQPO3FQPTJUPSZ &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ ໼ҹ͸ґଘؔ܎ͷ޲͖ υϝΠϯϞσϧ
  51.  ՝୊ɿෆ҆ఆͳίʔυ͕ϝΠϯϥΠϯʹ߹ྲྀ 51  #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF &YQFSJNFOUBM6TFDBTF #PPLNBSL4FSWJDF

    $PVQPO4FSWJDF &YQFSJNFOUBM4FSWJDF $PVQPO3FQPTJUPSZ &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ ໼ҹ͸ґଘؔ܎ͷ޲͖ υϝΠϯϞσϧ
  52.  52  #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF &YQFSJNFOUBM6TFDBTF #PPLNBSL4FSWJDF $PVQPO4FSWJDF

    &YQFSJNFOUBM4FSWJDF $PVQPO3FQPTJUPSZ &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ ໼ҹ͸ґଘؔ܎ͷ޲͖ υϝΠϯϞσϧ TRYɿෆ҆ఆͳίʔυ΁ґଘͰ͖ΔΫϥε΍
 ɹɹ ϝιουΛςετʹΑΓ੍ݶ͢Δ
  53.  TRYɿෆ҆ఆͳίʔυ΁ґଘͰ͖ΔΫϥε΍
 ɹɹ ϝιουΛςετʹΑΓ੍ݶ͢Δ 53 @ArchTest val `ExperimentalΞϊςʔγϣϯ͕෇༩͞ΕͨΫϥε͕
 ExperimentalΞϊςʔγϣϯ͕෇༩͞ΕͨΫϥε͔ΒͷΈґଘ͞Ε͍ͯΔ͜ͱΛ֬ೝ` =

    
 classes() .that() .areAnnotatedWith(Experimental::class.java) .should(ArchConditions.onlyHaveDependentClassesThat(ANNOTATED_CLASSES)) @ArchTest val `ExperimentalΞϊςʔγϣϯ͕෇༩͞Εͨϝιου͕
 ExperimentalΞϊςʔγϣϯ͕෇༩͞Εͨϝιου΍Ϋϥε͔ΒͷΈґଘ͞Ε͍ͯΔ͜ͱΛ֬ೝ` = 
 methods() .that() .areAnnotatedWith(TEExperimental::class.java) .should(DEPENDENT_ONLY_ON_ANNOTATED_CODE)
  54.  TRYɿෆ҆ఆͳίʔυ΁ґଘͰ͖ΔΫϥε΍
 ɹɹ ϝιουΛςετʹΑΓ੍ݶ͢Δ 54 private fun checkRecursively(input: JavaClass): Boolean

    { return when { // ExperimentalΞϊςʔγϣϯ͸਌Ϋϥεʹ͔͠෇༩͞Ε͍ͯͳ͍ͷͰ // ಗ໊Ϋϥε΍ωετͷࢠΫϥεͷ৔߹͸࠶ؼతʹ਌ΫϥεΛݟʹߦ͘ input.isAnonymousClass || input.isNestedClass -> checkRecursively( input.enclosingClass.get() ) input.isAnnotatedWith(Experimental::class.java) -> true else -> false } }
  55. ΞʔΩςΫνϟʔςετͷར༻๏ "SDI6OJUΛಋೖͨ͠ϓϩμΫτͷഎܠ ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ "SDI6OJUͷ࢓૊Έͱ"1* "SDI6OJUͷجຊతͳػೳ

  56. 56 ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ CASE 3 : PRࠩ෼ͷӨڹൣғͷݕ஌ CASE 4 : ࢓༷೺ѲϚοϓͷࣗಈ࡞੒

    CASE 5 : αʔϏεؒґଘؔ܎ͷՄࢹԽ CASE 6 : ը໘ભҠਤͷࣗಈੜ੒
  57. CASE 3 : PRࠩ෼ͷӨڹൣғͷݕ஌ 57

  58.  w ଟ͘ͷΫϥε͔Βґଘ͞Ε͍ͯΔඃґଘ౓͕
 ߴ͍ΫϥεͷΤϯϋϯε΍ϦϑΝΫλͰ͸
 ൺֱతมߋͷӨڹൣғ͕େ͖͘
 σάϨ͕ൃੜ͢Δ֬཰͕ߴ͍ w ґଘΫϥεଆͰ͸ίʔυ্ͷࠩ෼͕Ͱ΋
 ಺෦࢓༷΁ͷ҉໧తͳґଘʹΑͬͯ
 ࢓༷ͷഁյతมߋͷӨڹΛड͚Δ৔߹͕͋Δ

    58 എܠɿࠩ෼ͷӨڹൣғͱσάϨ // Τϯϋϯεલ : ਖ਼ͷ੔਺Λฦ͢
 // Τϯϋϯεޙ : ੔਺Λฦ͢
 public int calculate(int input){ var output = ... return output; } 
 // calculate()͕ਖ਼ͷ੔਺Λฦ͢ͱ͍͏
 // લఏʹґଘͯ͠ॲཧΛ࣮ߦ͢Δϝιου public int useCalculate(){ var result = calculate(10); return ... }
  59.  w 13ͷࠩ෼ͷӨڹൣғ
 σάϨ͕ൃੜ͢Δ
 Մೳੑ͕͋ΔΫϥε΍$POUSPMMFSΫϥε 
 Λػցతʹ೺Ѳ͍ͨ͠ w *%&Λ࢖ͬͯಛఆΫϥεʹର͢Δ
 ґଘؔ܎ͷϦετΞοϓ΋Մೳ͕ͩ


    13ͷࠩ෼ͷશΫϥεʹର࣮ͯ͠ߦͯ͠
 ϝϯόʔʹڞ༗͢Δͷʹ͸ख͕͔͔ؒΔ 59 ՝୊ɿࠩ෼ͷӨڹൣғͷࣗಈݕग़
  60.  w 13ͷࠩ෼ͷӨڹൣғ
 σάϨογϣϯ͕ൃੜ͢ΔՄೳੑ͕
 ɹ͋ΔΫϥε΍$POUSPMMFSΫϥε 
 ͷҰཡදΛ$*্Ͱࣗಈੜ੒ͯ͠
 13্Ͱ೺ѲͰ͖ΔΑ͏ʹͨ͠ w ϝϯόʔಉ࢜ͰӨڹൣғͷೝࣝΛ


    ڞ༗͢Δ͜ͱͰςετͰ๷͍͛ͯͳ͍
 σάϨογϣϯʹؾ͖ͮ΍͘͢͢Δ 60 TRYɿCI্͔ΒӨڹൣғΛࣗಈͰϦετΞοϓ
  61.  w "13ͰEJ⒎͕ൃੜͨ͠Ϋϥε
 #"ʹґଘ͍ͯ͠ΔΫϥε
 $#ʹґଘ͍ͯ͠ΔΫϥεɾɾɾ
 ͷΑ͏ʹґଘΫϥεΛॱʹ୧͍͚ͬͯ͹
 EJ⒎ͷӨڹൣғΛચ͍ग़͢͜ͱ͕Մೳ w EJ⒎͕ൃੜͨ͠ϑΝΠϧ໊Λ༩͑Δͱ
 ӨڹൣғͷϑΝΠϧ໊Λग़ྗ͢ΔΑ͏ͳ


    HSBEMFλεΫΛ"SDI6OJUΛ࢖࣮ͬͯ૷ 61 HowɿPRͷdiff͔ΒӨڹൣғΛׂΓग़͢ ›./gradlew scanDependenciesFromGitDiff
  62.  ิ଍ɿݕग़Ͱ͖ͳ͍Өڹൣғʹ஫ҙ w ੩తͳґଘղܾͰ͸ݕग़Ͱ͖ͳ͍έʔεʹ஫ҙ͢Δ
 ϦϑϨΫγϣϯͳͲಈతͳґଘղܾ
 ΞϓϦέʔγϣϯϑϨʔϜϫʔΫʹؔΘΔมߋ
 ઃఆϑΝΠϧͷมߋ w ͋͘·ͰӨڹൣғͷ೺Ѳͷαϙʔτͱͯ͠࢖͏ w

    σάϨογϣϯ๷ࢭ͕໨తͳΒ͹
 ΑΓྑ͍ઃܭ΍ςετͰͦ΋ͦ΋σάϨογϣϯ͕
 ൃੜ͠ʹ͘͘͢Δͷʹӽͨ͜͠ͱ͸ͳ͍ 62 03FJMMZ.FEJB *OD
 ʮ'VOEBNFOUBMTPG4PGUXBSF"SDIJUFDUVSFʯ
  63.  63 TRYɿӨڹൣғͷςετ͚ͩΛ࣮ߦ͢Δ w 13ͷEJ⒎ͷӨڹൣғ͕ར༻ͯ͠
 Өڹൣғ಺ͷςετ͚ͩΛ
 ࣮ߦͤ͞Δ͜ͱ΋Մೳ w ࣮ߦίετ͕ߴ͍ςετΛ
 ޮ཰Α࣮͘ߦ͢Δ͜ͱ΋Ͱ͖Δ

    w ςετϐϥϛουΛ
 ॎʹ෼ׂ͢ΔΠϝʔδ  4NBMM .FEJVN -BSHF ςετϐϥϛουΛॎʹ෼ׂ͢Δ
  64.  ิ଍ɿӨڹൣғʹԠͨࣗ͡ಈςετൣғͷมߋ w DG5FTU*NQBDU"OBMZTJT
 ΫϥεؒͷӨڹ౓߹͍Λࣄલʹอଘ͓͖ͯ͠
 มߋ͞ΕͨΫϥεͱؔ࿈౓͕ߴ͍
 Ϋϥεͷςετ͚ͩΛ࣮ߦ
 7JTVBM4UVEJP΍"[VSF1JQFMJOFT্Ͱར༻Մೳ 64 IUUQTNBSUJOGPXMFSDPNBSUJDMFTSJTFUFTUJNQBDUBOBMZTJTIUNM

  65. CASE 4 : ࢓༷೺ѲϚοϓͷࣗಈ࡞੒ 65

  66.  എܠɿྺ࢙͕௕͍αʔϏεͳΒͰ͸ͷن໛(࠶) 66 "OESPJEͷը໘਺໿ #''ͷ"1*਺໿ #BDLFOEͷ"1*਺໿ ࢀর͢Δ%#ͷςʔϒϧ਺໿ w ೥ۙ͘ଓ͍͍ͯΔαʔϏεͰ͋Γɺ"1*͚ͩͰ΋ൺֱతେن໛ͳαʔϏε

  67.  ՝୊ɿϝϯόʔͷ࢓༷ཧղ౓͕ภͬͯ͠·͏ w ཭೚ɾҟಈͳͲͷӨڹ΋͋Γɺෳࡶ͔ͭॏཁͳ࢓༷ͷ"1*Λ
 ࣮૷ϨϕϧͰৄࡉʹཧղ͍ͯ͠Δϝϯόʔ͕ภͬͯ͠·͏͜ͱ͕͋Δ w ୭͕Ͳͷ"1*ͷ࢓༷ʹৄͦ͠͏ͳͷ͔
 ͱ͍͏͜ͱࣗମΛ೺Ѳ͍ͯ͠Δϝϯόʔ΋ݶΒΕ͖ͯͯ͠·͏ w ϝϯόʔͷ࢓༷ཧղ౓͕ՄࢹԽͰ͖Ε͹


    λεΫ΍ϨϏϡΞʔͷΞαΠϯͷ൑அʹ΋׆༻Ͱ͖Δ 67
  68.  ՝୊ɿ࢓༷ͷҰཡԽ΍ӡ༻΋ίετ͕ߴ͍ IUUQTFOHJOFFSSFDSVJUMJGFTUZMFDPKQUFDICMPHTQFDVOEFSTUBOEJOHNBQ 68

  69.  TRYɿίϛοτཤྺͱґଘؔ܎͔Βࣗಈੜ੒ w ίϛοτͰEJ⒎͕͋ΔΫϥεʹ
 ґଘ͍ͯ͠ΔΫϥεΛ୧Ε͹
 Ͳ͔͜ͷ"1*ΤϯυϙΠϯτʹඥͮ͘ w ֤ϝϯόʔ͕ͦΕͧΕͷ"1*ʹ
 ͲΕ͚ͩมߋΛՃ͑ͨ͜ͱ͕
 ͋Δ͔Λ਺஋ԽͰ͖Δ

    69 ›./gradlew calculateContributeScore
  70.  Howɿίϛοτཤྺͱґଘؔ܎͔Βࣗಈੜ੒ w (JU)VCͷ"1*Λར༻ͯ͠
 ֤νʔϜϝϯόʔ͕ࠓ·Ͱͷ13্Ͱ
 ͲͷΫϥεʹͲͷ͘Β͍ࠩ෼Λ
 ࡞͖ͬͯͨͷ͔Λूܭ w "SDI6OJUΛ࢖ͬͯ
 ֤Ϋϥε͕Ͳͷ"1*ΤϯυϙΠϯτͰ


    ར༻͞Ε͍ͯΔ͔ͷґଘؔ܎Λऔಘ 70 ›./gradlew calculateContributeScore
  71.  TRYɿίϛοτཤྺͱґଘؔ܎͔Βࣗಈੜ੒ 71

  72.  ิ଍ɿ࢓༷ཧղ౓͕ͲΕ͘Β͍ਖ਼͍͠ͷ͔ʁ w ίϛοτܦݧ͕͋Δͱ͍ͬͯ΋ܰඍͳϦϑΝΫλͩͬͨΓͱ
 ͦΕ΄Ͳ࢓༷ཧղ౓ͱϦϯΫͯ͠ͳ͍έʔε΋͋Δ w ίϛοτ΍13ͷEJ⒎ͷେ͖͞΍ಉ࣌ฤूͨ͠ϑΝΠϧ਺ͳͲ΋
 ߟྀͯ͠ॏΈ෇͚Λ͢Δͱਫ਼౓্͕͕Δ͔΋͠Εͳ͍ w ࠓճͷϢʔεέʔεͰ͸͋͘·Ͱେ·͔ͳ܏޲͑͞


    Θ͔Ε͹Α͍ͱׂΓ੾͍ͬͯΔ w DGϖΞϓϩάϥϛϯάɾϞϒϓϩάϥϛϯάɾ҉໧஌ͷڞ༗ 72
  73. CASE 5 : αʔϏεؒґଘؔ܎ͷՄࢹԽ 73

  74. 74 എܠɿAPIͷΞʔΩςΫνϟʔ (࠶) w ΞϓϦͷϢʔεέʔε୯ҐͰ࡞੒ͨ͠#''ͷ"1*͸
 σʔλͷϦιʔε୯ҐͰ࡞੒ͨ͠#BDLFOEͷ"1*͔Β
 ඞཁͳ৘ใΛू໿ͯ͠ΞϓϦʹఏڙ #BDLFOE
 "1*
 GPS403

    #''"1*
 GPS40& J04"OESPJE ΞϓϦ %#ݕࡧΤϯδϯ A C A B C B
  75.  എܠɿྺ࢙͕௕͍αʔϏεͳΒͰ͸ͷن໛(࠶) 75 "OESPJEͷը໘਺໿ #''ͷ"1*਺໿ #BDLFOEͷ"1*਺໿ ࢀর͢Δ%#ͷςʔϒϧ਺໿ w ೥ۙ͘ଓ͍͍ͯΔαʔϏεͰ͋Γɺ"1*͚ͩͰ΋ൺֱతେن໛ͳαʔϏε

  76. ՝୊ɿαʔϏεؒͷӨڹௐࠪͷίετ͕ߴ͍ w ΤϯϋϯεɾϦϑΝΫλ࣌ʹ͓͚ΔαʔϏεؒͷӨڹௐࠪ
 Ͳͷ#BDLFOEͷ"1*͕Ͳͷ#''ͷ"1*Ͱ࢖ΘΕ͍ͯΔͷ͔
 Ͳͷ#''ͷ"1*͕ͲͷΞϓϦͷը໘Ͱ࢖ΘΕ͍ͯΔͷ͔ w ֤"1*͕ͦΕͧΕϢʔβʔʹͲͷΑ͏ͳମݧΛ
 ఏڙ͢ΔͨΊʹར༻͞Ε͍ͯΔͷ͔ w ͜ΕΒͷυΩϡϝϯτΛखಈͰϝϯςφϯε͢Δͷ΋େม

    76
  77.  TRYɿAPIؒͷґଘؔ܎දΛCI্Ͱࣗಈੜ੒ 77 #BDLFOE
 "1*
 GPS403 #''"1*
 GPS40& J04"OESPJE ΞϓϦ

    %#ݕࡧΤϯδϯ
  78.  w "SDI6OJUΛ$PSF"1*Λ࢖ͬͯ
 ϝιου͕ݺͼग़͢ϝιουΛ
 औಘ͢Δ͜ͱ͕Ͱ͖Δ w ͜ΕΛར༻ͯ͠
 ͋Δ"1*ΤϯυϙΠϯτ͕
 σʔλΛऔಘ͢Δ·Ͱͷ
 ϝιουγʔέϯεΛ


    ίʔυ͔Β௥͏͜ͱ͕Ͱ͖Δ 78 HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ
 ϝιουͷґଘؔ܎͔Βऔಘ
  79.  w ύοέʔδ΍Ϋϥεͷ
 ϨΠϠʔຖͷ໋໊نଇΛ
 ߟྀ͢Ε͹ΑΓޮ཰తʹ
 γʔέϯεΛ࡞੒Մೳ w ϥϜμؔ਺΍,PUMJO+7.͕
 ͲΜͳόΠτίʔυʹͳΔ͔͸
 एׯߟྀ͢Δඞཁ͕͋Δ৔߹΋


    ͋Δ͜ͱʹ஫ҙ 79 HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ
 ϝιουͷґଘؔ܎͔Βऔಘ  $POUSPMMFSΫϥε૚ 3FQPTJUPSZΫϥε૚ 6TF$BTFΫϥε૚ υϝΠϯϞσϧ૚
  80.  80 @RestController @RequestMapping(value = "/hair/hair-colors") @AllArgsConstructor public class HairColorController

    { private final HairColorUsecase usecase; @GetMapping public ResponseEntity<GetHairColorResponse> getHairColor() { final var body = usecase.getHairColors(); return ResponseEntity.body(body); } } w "1*ΤϯυϙΠϯτ
 IBJSIBJSDPMPST(&5͸ )BJS$PMPS6TFDBTFΫϥεͷ
 HFU)BJS$PMPSTϝιουʹ
 ґଘ͍ͯ͠Δ w Ξϊςʔγϣϯͷ஋͔Β
 "1*ΤϯυϙΠϯτͷύε΍
 ϝιου΋औಘͰ͖Δ HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ
 ϝιουͷґଘؔ܎͔Βऔಘ
  81.  81 @RestController @RequestMapping(value = "/hair/hair-colors") @AllArgsConstructor public class HairColorController

    { private final HairColorUsecase usecase; @GetMapping public ResponseEntity<GetHairColorResponse> getHairColor() { final var body = usecase.getHairColors(); return ResponseEntity.body(body); } } w "1*ΤϯυϙΠϯτ
 IBJSIBJSDPMPST(&5͸ )BJS$PMPS6TFDBTFΫϥεͷ
 HFU)BJS$PMPSTϝιουʹ
 ґଘ͍ͯ͠Δ HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ
 ϝιουͷґଘؔ܎͔Βऔಘ
  82.  82 @Usecase @AllArgsConstructor public class HairColorUsecase { private final

    HairColorRepository repository; @Nonnull public GetHairColorResponse getHairColors() { final var hairColors = repository.findAll(); final var hairColorDtos = hairColors.stream() .map(HairColorDtoMapper::map) .collect(Collectors.toList()); return GetHairColorResponse.builder() .hairColors(hairColorDtos) .build(); } } w )BJS$PMPS6TFDBTFΫϥεͷ
 HFU)BJS$PMPSTϝιου͸
 )BJS$PMPS3FQPTJUPSZΫϥεͷ
 pOE"MMϝιουʹґଘ͍ͯ͠Δ HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ
 ϝιουͷґଘؔ܎͔Βऔಘ
  83.  83 @Usecase @AllArgsConstructor public class HairColorUsecase { private final

    HairColorRepository repository; @Nonnull public GetHairColorResponse getHairColors() { final var hairColors = repository.findAll(); final var hairColorDtos = hairColors.stream() .map(HairColorDtoMapper::map) .collect(Collectors.toList()); return GetHairColorResponse.builder() .hairColors(hairColorDtos) .build(); } } w )BJS$PMPS6TFDBTFΫϥεͷ
 HFU)BJS$PMPSTϝιου͸
 )BJS$PMPS3FQPTJUPSZΫϥεͷ
 pOE"MMϝιουʹґଘ͍ͯ͠Δ HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ
 ϝιουͷґଘؔ܎͔Βऔಘ
  84.  TRYɿAPIؒͷґଘؔ܎දΛCI্Ͱࣗಈੜ੒ 84 #BDLFOE
 "1*
 GPS403 #''"1*
 GPS40& J04"OESPJE ΞϓϦ

    %#ݕࡧΤϯδϯ
  85.  TRYɿAPI/DBςʔϒϧؒͷґଘؔ܎ΛҰཡԽ 85 #BDLFOE
 "1*
 GPS403 #''"1*
 GPS40& J04"OESPJE ΞϓϦ

    %#ݕࡧΤϯδϯ
  86.  TRYɿAndroidɾBFFؒͷґଘؔ܎ΛҰཡԽ 86 #BDLFOE
 "1*
 GPS403 #''"1*
 GPS40& J04"OESPJE ΞϓϦ

    %#ݕࡧΤϯδϯ
  87.  TRYɿAndroidɾDBؒͷґଘؔ܎ΛҰཡԽ 87 #BDLFOE
 "1*
 GPS403 #''"1*
 GPS40& J04"OESPJE ΞϓϦ

    %#ݕࡧΤϯδϯ
  88. IUUQTRJJUBDPNPYTPGUJUFNTEDDDDDFD 88

  89. 89 ิ଍ɿAPIͷΰʔϧΩϟϯόε w "1*ͱΞϓϦͷը໘ͷରԠ͕ҰཡԽ͞ΕΔ͜ͱͰνʔϜؒͰ΋
 "1*ͷ඼࣭ͱϢʔβʔӨڹͷ໨ઢ͕߹Θͤ΍͘͢ͳΔ
 ͋Δ"1*ͷϨεϙϯε͕஗͘ͳΔͱϢʔβʔͷͲͷମݧʹӨڹ͕ͰΔͷ͔
 ͋Δ"1*ͰΤϥʔ͕ଟ͘ͳΔͱϢʔβʔ͸Կ͕Ͱ͖ͳ͘ͳͬͯ͠·͏ͷ͔ ᠳӭࣾʮ8FC"1*ͷઃܭʯ

  90.  ิ଍ɿ෼ࢄτϨʔγϯά w αʔϏεؒͷґଘؔ܎Λ೺Ѳ͍͚ͨͩ͠ͳΒ͹
 ෼ࢄτϨʔγϯάͳͲΛར༻͢Δ͜ͱ΋༗ޮ 90

  91. CASE 6: ը໘ભҠਤͷࣗಈੜ੒ 91

  92. 92 എܠɿAPIͷΰʔϧΩϟϯόε w "1*ͱϢʔβʔମݧ΁ͷӨڹΛߟ͑Δʹ͋ͨͬͯ
 ෳ਺ͷ"1*͕ͲͷΑ͏ͳॱ൪Ͱར༻͞Ε͍ͯΔͷ͔Λ೺Ѳ͍ͨ͠
 ΞϓϦ։ൃऀ͸ͲͷΑ͏ͳॱ൪Λ"1*Λར༻͍ͯ͠Δͷ͔
 Ϣʔβʔ͸ͲͷΑ͏ʹը໘ΛભҠ͍ͯ͠Δͷ͔ w ϩά͔Βύλʔϯ΍܏޲Λ෼ੳ͢Δ͚ͩͰͳ͘
 ͦ΋ͦ΋ը໘࢓༷ͱͯ͠Ͳ͏͍͏ભҠ͕Մೳͳͷ͔΋஌Γ͍ͨ

  93. 93 ՝୊ɿAPIͷΰʔϧΩϟϯόε w ը໘਺͕ଟ͍େن໛ΞϓϦͩͱීஈ͔ΒΞϓϦΛ৮͍ͬͯͯ΋
 ΄ͱΜͲ։͔ͳ͍ը໘͕͋ΔͳͲશͯͷը໘ભҠΛ೺Ѳ͢Δͷ͸೉͍͠ w ΞϓϦͷը໘ભҠύλʔϯ࢓༷ΛखಈͰϝϯςϯφϯε͢Δͷ͸େม

  94. 94 TRYɿը໘ભҠਤͷࣗಈੜ੒

  95. 95 Howɿը໘Ϋϥεͷґଘؔ܎Λऔಘ w "OESPJEΞϓϦͷ
 "DUJDJUZɾ'SBHNFOUΫϥεͷ
 ݺͼग़͠ͳͲͷґଘؔ܎Λར༻ͯ͠
 Ϣʔβʔ͕Ͳͷը໘͔ΒͲͷը໘ʹ
 ભҠ͠ಘΔͷ͔ͷભҠάϥϑΛ࡞੒ w ը໘ભҠάϥϑΛը૾ͱͯ͠ग़ྗ

    private fun onClickBlog() { val intent = 
 KireiBlogListTabActivity.intent(
 this, 
 salonId
 ) startActivity(intent) }
 
 private fun navigateToLogin() { val intent = LoginActivity.intent(this) startActivityForResult( intent, REQUEST_CODE_LOGIN ) }
  96. ิ଍ɿAndroidΞϓϦͱArchUnit w ΞϓϦέʔγϣϯͷߏ੒ʹΑͬͯ͸+BWBɾ,PUMJOίʔυ͚ͩͰͳ͘
 9.-ϑΝΠϧͷ಺༰΋֬ೝ͢Δඞཁ͕͋Δ͜ͱʹ஫ҙ
 DG/BWJHBUJPO$PNQPOFOUͱφϏήʔγϣϯάϥϑ w "SDI6OJU͸CZUFDPEFΛղੳ͢ΔͷͰࣗಈੜ੒͞Εͨ
 Ϋϥεͷґଘؔ܎΋ߟྀ͢Δ͜ͱ͕Ͱ͖Δ 96

  97. ΞʔΩςΫνϟʔςετͷར༻๏ "SDI6OJUΛಋೖͨ͠ϓϩμΫτͷഎܠ ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ "SDI6OJUͷ࢓૊Έͱ"1* "SDI6OJUͷجຊతͳػೳ

  98. CASE 1 : υϝΠϯؒͷґଘؔ܎ͷ੍ޚ CASE 2 : ෆ҆ఆͳίʔυͷґଘؔ܎ͷ੍ޚ 98 ΞʔΩςΫνϟʔςετͷར༻๏

  99. 99 ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ CASE 3 : PRࠩ෼ͷӨڹൣғͷݕ஌ CASE 4 : ࢓༷೺ѲϚοϓͷࣗಈ࡞੒

    CASE 5 : αʔϏεؒґଘؔ܎ͷՄࢹԽ CASE 6 : ը໘ભҠਤͷࣗಈੜ੒
  100. w "SDI6OJUͱ࢖͏ͱ+BWB΍,PUMJO+7.Ͱهड़͞Εͨ
 ΞϓϦέʔγϣϯͷΞʔΩςΫνϟʔςετ΍
 ґଘؔ܎ͷղੳͳͲ͕ൺֱతָʹ࣮૷Ͱ͖Δ w ϥΠϒϥϦͷݩʑͷ໨తͱ͸ҟͳΔ࢖͍ํͰ͋Δ͜ͱʹ஫ҙ w खಈͰͷӡ༻͸໘౗͚ͩͲؤுͬͯࣗಈԽ͢Δʹ͸ՙ͕ॏ͍
 ͦΜͳᙱ͍ͱ͜ΖʹखΛಧ͔ͤΔΑ͏ͳࣗಈԽΛ
 "SDI6OJUΛ࢖࣮ͬͯݱ͢Δ͜ͱ͕Ͱ͖Δ

    ·ͱΊ 100
  101. w IUUQTXXXBSDIVOJUPSH w IUUQTNFEJVNDPNYFCJBGSBODFFOGPSDJOHBSDIJUFDUVSFEFDJTJPOT XJUIBSDIVOJUECGDGB w IUUQTCMPHTPSBDMFDPNPUOKQVOJUUFTUZPVSBSDIJUFDUVSFXJUI BSDIVOJUKB w IUUQTBTNPXJP

    w IUUQTHJUIVCDPN5/("SDI6OJU&YBNQMFT ࢀߟURL : 1 101
  102. w IUUQTXXXPSFJMMZDPNMJCSBSZWJFX
 TPGUXBSFBSDIJUFDUVSFQBUUFSOT w IUUQTNBSUJOGPXMFSDPNBSUJDMFTSJTFUFTUJNQBDU BOBMZTJTIUNMGPPUOPUFKBWBUJB w IUUQTEPDTNJDSPTPGUDPNFOVTB[VSFEFWPQTQJQFMJOFTUFTU UFTUJNQBDUBOBMZTJT WJFXB[VSFEFWPQT

    ࢀߟURLɿ2 102
  103. ΞʔΩςΫνϟʔςετͷར༻๏ "SDI6OJUΛಋೖͨ͠ϓϩμΫτͷഎܠ ΞϓϦέʔγϣϯղੳ΁ͷར༻๏ "SDI6OJUͷ࢓૊Έͱ"1* "SDI6OJUͷجຊతͳػೳ