Slide 1

Slide 1 text

SpringBootͰ࡞੒ͨ͠ APIςετͷίεύΛߴΊΑ͏ʂ 2024/06/16 JJUG CCC 2024 Spring ݀Ҫ ྯ(Satoshi Anai)

Slide 2

Slide 2 text

ࣗݾ঺հ • ݀Ҫ ྯ(͞ͱ͠) • ෱Ԭ͔Βདྷ·ͨ͠ʢॳࢀՃͰ͢ʣ • X(چTwitter): @gorou_178 • Bluesky: gorou178.bsky.social • ۚ༥ܥͰEMͯ͠·͢ • εΩϧ: GCP/Java/Kotlin/PHP/AWS • ίʔώʔ/ϓϦϯ/θϧμ/ϥϯχϯά

Slide 3

Slide 3 text

͸͡ΊΔલʹ • ͜ͷεϥΠυ͸͢Ͱʹެ։͍ͯ͠·͢ • X(چTwitter)ͷϋογϡλάʹͯӾཡ͍ͩ͘͞ • ίʔυ͸ҎԼϦϙδτϦʹެ։͍ͯ͠·͢ • gorou-178/petapi - GitHub • gorou-178/grpc-springboot-example - GitHub • ϑΟʔυόοΫ͍͚ͨͩΔͱ͏Ε͍͠Ͱ͢ʂʂ

Slide 4

Slide 4 text

ΰʔϧ • APIΛrunnίϚϯυͰγφϦΦςετ͢Δํ๏͕ཧղͰ͖Δ • APIͷγφϦΦςετ͸ɺΧόϨοδܭଌͱܕνΣοΫͰςετ ͷίεύΛߴΊΒΕΔ

Slide 5

Slide 5 text

Noΰʔϧ • OpenAPI΍gRPCͷٕज़తͳৄࡉ • ࡉ͔ͳςετख๏ʹ͍ͭͯ

Slide 6

Slide 6 text

E2Eςετ͍ͯ͠·͔͢ʁ🙋

Slide 7

Slide 7 text

E2Eςετ • ४උ͕େมͰͰ͖͍ͯͳ͍ • ΍Γ͍͚ͨͲͰ͖͍ͯͳ͍ • ݱঢ়ͷςετͰे෼ػೳ͍ͯ͠Δ • ͲͷΑ͏ʹಋೖ͢΂͖͔Θ͔Βͳ͍

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

ίετ͕ߴ͍

Slide 10

Slide 10 text

αόϯφศΓʙࣗಈςετʹؔ͢Δ࿈ࡌͰಘΒΕͨ஌ݟͷ·ͱΊʢ2023೥5݄൛ʣ https://speakerdeck.com/twada/automated-test-knowledge-from-savanna-202305-edition?slide=45

Slide 11

Slide 11 text

αόϯφศΓʙࣗಈςετʹؔ͢Δ࿈ࡌͰಘΒΕͨ஌ݟͷ·ͱΊʢ2023೥5݄൛ʣ https://speakerdeck.com/twada/automated-test-knowledge-from-savanna-202305-edition?slide=47

Slide 12

Slide 12 text

αόϯφศΓʙࣗಈςετʹؔ͢Δ࿈ࡌͰಘΒΕͨ஌ݟͷ·ͱΊʢ2023೥5݄൛ʣ https://speakerdeck.com/twada/automated-test-knowledge-from-savanna-202305-edition?slide=34

Slide 13

Slide 13 text

αόϯφศΓʙࣗಈςετʹؔ͢Δ࿈ࡌͰಘΒΕͨ஌ݟͷ·ͱΊʢ2023೥5݄൛ʣ https://speakerdeck.com/twada/automated-test-knowledge-from-savanna-202305-edition?slide=34 ͜ͷ͋ͨΓͷςετΛ

Slide 14

Slide 14 text

αόϯφศΓʙࣗಈςετʹؔ͢Δ࿈ࡌͰಘΒΕͨ஌ݟͷ·ͱΊʢ2023೥5݄൛ʣ https://speakerdeck.com/twada/automated-test-knowledge-from-savanna-202305-edition?slide=34 ͜͜ʹ࣋ͬͯ ߦ͖͍ͨʂ

Slide 15

Slide 15 text

ίεύͷྑ͍ςετ • খ͍͞γεςϜʹରͯ͠

Slide 16

Slide 16 text

ίεύͷྑ͍ςετ • খ͍͞γεςϜʹରͯ͠ • ୯ҰϚγϯͰ؆୯ʹಈ͔ͤ

Slide 17

Slide 17 text

ίεύͷྑ͍ςετ • খ͍͞γεςϜʹରͯ͠ • ୯ҰϚγϯͰ؆୯ʹಈ͔ͤ • ςετ͕࡞Γ΍͍͢

Slide 18

Slide 18 text

ίεύͷྑ͍ςετ • খ͍͞γεςϜʹରͯ͠ • ୯ҰϚγϯͰ؆୯ʹಈ͔ͤ • ςετ͕࡞Γ΍͍͢ • ࣗಈతʹςετ͕࣮ߦ͞ΕΔ

Slide 19

Slide 19 text

ςετ͕؆୯Ͱ͖Ε͹Α͍ʁ

Slide 20

Slide 20 text

ςετ͕؆୯Ͱ͖Ε͹Α͍ʁ ੈͷதͦΜͳʹ؁͘ͳ͍

Slide 21

Slide 21 text

αόϯφศΓʙࣗಈςετʹؔ͢Δ࿈ࡌͰಘΒΕͨ஌ݟͷ·ͱΊʢ2023೥5݄൛ʣ https://speakerdeck.com/twada/automated-test-knowledge-from-savanna-202305-edition?slide=34

Slide 22

Slide 22 text

৴པෆೳੑʹཱͪ޲͔͏ • ςετͷ৴པੑ͕େࣄ 
 →͢͹΍͍ϑΟʔυόοΫϧʔϓΛ·ΘͤΔ 
 →؆୯ʹ࣮ߦͰ͖Δ͜ͱ΍ࣗಈԽ͸ͱͯ΋େࣄ

Slide 23

Slide 23 text

ίεύͷྑ͍ςετʢ࠶ܝʣ • খ͍͞γεςϜʹରͯ͠ • ୯ҰϚγϯͰ؆୯ʹಈ͔ͤ • ςετ͕࡞Γ΍͍͢ • ࣗಈతʹςετ͕࣮ߦ͞ΕΔ

Slide 24

Slide 24 text

ίεύͷྑ͍ςετʢ࠶ܝʣ • খ͍͞γεςϜʹରͯ͠ → API • ୯ҰϚγϯͰ؆୯ʹಈ͔ͤ • ςετ͕࡞Γ΍͍͢ • ࣗಈతʹςετ͕࣮ߦ͞ΕΔ

Slide 25

Slide 25 text

APIͷςετ

Slide 26

Slide 26 text

APIͷςετ • Postman • JMeter • locust • K6 • Cypress • etc…

Slide 27

Slide 27 text

APIͷςετ • Postman → ಠࣗUI(JSON) • JMeter → ಠࣗUI(jmxϑΝΠϧ) • locust → Python • K6 → JavaScript(goja) • Cypress → JavaScript • etc…

Slide 28

Slide 28 text

πʔϧ࡞੒ͷͨΊʹݴޠґଘ͕͋Δ →Ұఆͷֶशίετ͕͔͔Δ

Slide 29

Slide 29 text

APIͷςετ • curl΍grpcurlΛར༻

Slide 30

Slide 30 text

APIͷςετ • curl΍grpcurlΛར༻ 
 →ύϥϝʔλଟͯ͘ࢦఆ໘౗

Slide 31

Slide 31 text

APIͷςετ • curl΍grpcurlΛར༻ 
 →ύϥϝʔλଟͯ͘ࢦఆ໘౗ 
 →OSʹΑͬͯΤεέʔϓจࣈ͕ҟͳΓେม

Slide 32

Slide 32 text

APIͷςετ • curl΍grpcurlΛར༻ 
 →ύϥϝʔλଟͯ͘ࢦఆ໘౗ 
 →OSʹΑͬͯΤεέʔϓจࣈ͕ҟͳΓେม • खಈςετʢେมʣ

Slide 33

Slide 33 text

΋ͬͱαΫοͱ࡞Γ͍ͨʂʂ

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

runnίϚϯυ • ಡΈํʮϥϯΤψʯ • ΦϖϨʔγϣϯࣗಈԽπʔϧ k1LoW/runn - GitHub • γφϦΦϑΝΠϧΛyamlܗࣜͰॻ͍ͯγφϦΦΛ࣮ߦͰ͖Δ • HTTPϦΫΤετɺgRPCϦΫΤετɺ೚ҙͷίϚϯυ࣮ߦ… • ͍ΖΜͳػೳ͕͋Δ

Slide 36

Slide 36 text

runnίϚϯυ • γφϦΦςετ͚ͩͰ͸ͳ͍ • ΧόϨοδܭଌ • ෛՙςετ΋Մೳ

Slide 37

Slide 37 text

Ϣʔεέʔε঺հ

Slide 38

Slide 38 text

1. SpringBoot + OpenAPI

Slide 39

Slide 39 text

1. SpringBoot + OpenAPI • SpringBoot(Java)ͱOpenAPIΛར༻ͯ͠APIΛ࡞੒ • Swagger(yaml)ΛϕʔεʹrunnͰςετͰ͖ΔΑ͏ʹ͢Δ

Slide 40

Slide 40 text

ެࣜαϯϓϧʮPetstore SampleʯΛ׆༻ OpenAPI(Swagger)Λ४උ

Slide 41

Slide 41 text

openapi-generator-cliΛdocker͔Βར༻ͯ͠ 
 OpenAPI͔Βιʔείʔυੜ੒ OpenAPI͔Βιʔείʔυੜ੒ docker run --rm \ -v "${PWD}/build/openapi:/out" \ openapitools/openapi-generator-cli:v7.3.0 generate \ --generator-name spring \ --input-spec "${PWD}/petstore.yaml" \ --output /out

Slide 42

Slide 42 text

ੜ੒ͨ͠ίʔυΛར༻ͯ͠APIΛ࣮૷ APIΛ࣮૷ @Controller @RequestMapping("/pet") public class PetController implements PetApi { @PostMapping("/") @Override public ResponseEntity addPet(Pet pet) { return ResponseEntity.ok(new Pet("addDog", Collections.emptyList())); } @DeleteMapping("/") @Override public ResponseEntity deletePet(Long petId, String apiKey) { return ResponseEntity.ok().build(); }

Slide 43

Slide 43 text

ҎԼͷ༷ͳγφϦΦΛ࡞੒(pet-api-runn.yml) runnͷrunnbook(γφϦΦϑΝΠϧ)࡞੒ desc: "pet api test" runners: req: endpoint: http://localhost:8080 openapi3: ./petstore.yaml steps: getPets: req: /pet/123: get: headers: Accept: application/json body: null test: steps.getPets.res.status == 200

Slide 44

Slide 44 text

໰୊ͳ͚Ε͹ҎԼͷ༷ʹͳΔ runnͷγφϦΦ࣮ߦʢςετʣ $ runn run pet-api-runn.yml . 1 scenario, 0 skipped, 0 failures

Slide 45

Slide 45 text

APIυΩϡϝϯτͷΧόϨοδΛදࣔ 
 ͲͷAPI͕ςετͰ͖͍ͯͳ͍͔͸ "—long" ΦϓγϣϯͰදࣔՄ runn coverage $ runn coverage pet-api-runn.yml Spec Coverage -------------------------------------- Total 5.0% OpenAPI Petstore:1.0.0 5.0%

Slide 46

Slide 46 text

APIυΩϡϝϯτͷϨεϙϯε࢓༷ͱҟͳΔϨεϙϯεΛ 
 ฦͨ͠৔߹ɺΤϥʔʹͳͬͯৄࡉ͕දࣔ͞ΕΔ runn OpenAPI3 check $ runn run pet-api-runn.yml F 1) pet-api-runn.yml xxxxxxxxxxxxxxxx Failure/Error: http request failed on "pet api test".steps.getPets: openapi3 validation error: response body doesn't match schema #/components/schemas/Pet: Error at "/tags": Value is not nullable

Slide 47

Slide 47 text

2. SpringBoot + gRPC

Slide 48

Slide 48 text

2. SpringBoot + gRPC • SpringBoot(Kotlin) + gRPCͰAPIΛ࡞੒ • ProtocolBufferΛར༻ͯ͠runnͰςετ͢Δ • Buf CLIΛར༻ͯ͠ίʔυੜ੒

Slide 49

Slide 49 text

Buf CLI • Buf͕࡞ͬͨprotobufͷ։ൃΛࢧԉ͢Δπʔϧ • Ͱ͖Δ͜ͱ • protobufͷlint • protobufͷഁյతมߋݕ஌ • protobuf͔Βͷίʔυੜ੒ • etc

Slide 50

Slide 50 text

protobufΛ࡞੒ syntax = "proto3"; option java_package = "com.example.grpc.server.greeter"; option java_outer_classname = "Greeter"; message HelloRequest { string name = 1; } message HelloReply { string message = 1; } service GreeterService { rpc SayHello (HelloRequest) returns (HelloReply) {} }

Slide 51

Slide 51 text

gRPC Code Generate • Buf CLIͰgRPCίʔυੜ੒ • KotlinͰಈ͔͢৔߹ɺJavaίʔυͱkotlinίʔυͷੜ੒͕ඞཁ

Slide 52

Slide 52 text

buf.ymlΛ࡞੒ version: v1 breaking: use: - FILE lint: use: - DEFAULT allow_comment_ignores: true

Slide 53

Slide 53 text

buf.gen.ymlΛ࡞੒ protocolbuffers͸͋͑ͯv25.1Λࢦఆ(ഁյతมߋ͕͋ΔͨΊ) version: v1 plugins: - plugin: buf.build/grpc/java out: src/main/java - plugin: buf.build/grpc/kotlin out: src/main/kotlin - plugin: buf.build/protocolbuffers/java:v25.1 out: src/main/java - plugin: buf.build/protocolbuffers/kotlin:v25.1 out: src/main/kotlin

Slide 54

Slide 54 text

gRPCίʔυΛੜ੒ • buf.gen.ymlͷઃఆͰίʔυΛੜ੒ • ࠓճ͸Javaͱkotlinίʔυ͕ੜ੒͞ΕΔ $ buf generate

Slide 55

Slide 55 text

ੜ੒͞Εͨίʔυ ੜ੒͞Εͨ Javaͱkotlinίʔυ

Slide 56

Slide 56 text

gRPCαʔϏεΛ࡞੒ • ੜ੒͞ΕͨίʔυΛܧঝͯ͠த਎Λ࣮૷ class GreeterService : GreeterServiceGrpc.GreeterServiceImplBase() { override fun sayHello( request: Greeter.HelloRequest, responseObserver: StreamObserver) { val reply = Greeter.HelloReply.newBuilder().apply { message = "Hello ${request.name}" }.build() responseObserver.onNext(reply) responseObserver.onCompleted() } }

Slide 57

Slide 57 text

runnγφϦΦϑΝΠϧΛ࡞੒ ϦΫΤετ/Ϩεϙϯε͔Βੜ੒Ͱ͖Δ runn newίϚϯυ 
 localhostͩͱTLSΤϥʔ͕ग़ΔͨΊ —grpc-no-tlsΛ෇༩ $ runn new —grpc-no-tls --out test-runn.yml -- grpcurl \ -d '{"name": "abc"}' localhost:9090 GreeterService/SayHello $ cat test-runn.yml desc: Generated by `runn new` runners: greq: grpc://localhost:9090 steps: - greq: GreeterService/SayHello: message: name: abc

Slide 58

Slide 58 text

γφϦΦςετ΋ࣗಈੜ੒ • runn new —and-run Ͱඞཁ࠷௿ݶͳςετ΋ग़ྗ $ runn new --and-run --grpc-no-tls --out test-runn.yml \ -- grpcurl -d '{"name": "test"}' localhost:9090 GreeterService/SayHello $ cat test-runn.yml desc: Generated by `runn new` runners: greq: grpc://localhost:9090 steps: - greq: GreeterService/SayHello: message: name: abc test: | current.res.headers["content-type"][0] == "application/grpc" && current.res.headers["grpc-accept-encoding"][0] == "gzip" && compare(current.res.message, {"message":"Hello abc"}) && current.res.status == 0

Slide 59

Slide 59 text

γφϦΦͷ࣮ߦ runn runͰγφϦΦͷ࣮ߦ͕Ͱ͖Δ 
 γϯάϧόΠφϦʔͳͷͰCI΁ͷ૊ΈࠐΈ΋ָ $ runn run --grpc-no-tls test-runn.yml . 1 scenario, 0 skipped, 0 failures

Slide 60

Slide 60 text

runnΛར༻͢Δ͜ͱͰ • Spec(OpenAPI/Protocol Buffer)Λ࡞੒ • ίʔυੜ੒ͯ͠ͻͱ·ͣಈ͘ঢ়ଶʹ͢ΔʢΤϥʔঢ়ଶͰΑ͍ʣ • runn newͰγφϦΦςετΛ࡞Δ • ࣮૷ͱςετͱϦϑΝΫλϦϯάΛ܁Γฦ͢ 
 →ϑΟʔυόοΫϧʔϓΛճͤΔ

Slide 61

Slide 61 text

runnͷϝϦοτ • ݴޠඇґଘ(yamlϑΝΠϧͰಈ͔ͤΔ) • ఆٛϑΝΠϧͱͷ੔߹ੑΛอͯΔ • खܰ͞ʢ࠷௿ݶͷγφϦΦΛૉૣ͘࡞ΕΔʣ • ࡉ͔͍νΣοΫ͸ޙͰϝϯς͍ͯ͜͠͏ • ࣗಈԽͷ͠΍͢͞ʢγϯάϧόΠφϦʔʣ

Slide 62

Slide 62 text

runnͷσϝϦοτ • ձࣾɺνʔϜͰར༻͍ͯ͠Δςετπʔϧ͕͢Ͱʹ͋Γɺ৐Γ ׵͑Δͷ͕େม • ෳ਺ͷίϯϙʔωϯτͷґଘؔ܎͕͋Δ৔߹ɺϞοΫ΍ελϒ Λ४උ͢Δඞཁ͕͋Δ

Slide 63

Slide 63 text

runnҎ֎ͷબ୒ࢶ • ձࣾɾνʔϜͷن໛ʹΑͬͯςετࣗಈԽαʔϏεಋೖΛݕ౼ • ελϒͷ४උ·Ͱͯ͘͠ΕΔπʔϧΛબͿ • ͳͲͳͲʢঢ়گʹΑΔʣ

Slide 64

Slide 64 text

·ͱΊ

Slide 65

Slide 65 text

·ͱΊ • E2EͷಋೖίετΛԼ͛ɺ؆୯ʹςετ͕Ͱ͖ΔrunnίϚϯυ • ݴޠඇґଘͰCIϑϨϯυϦʔ • ΧόϨοδͷܭଌɺܕνΣοΫͰϑΟʔυόοΫϧʔϓΛૣ͘ ճ͠΍͍͢ 
 →ίεύ͕ྑ͍

Slide 66

Slide 66 text

ΰʔϧ • APIΛrunnίϚϯυͰγφϦΦςετ͢Δํ๏͕ཧղͰ͖Δ • APIͷγφϦΦςετ͸ɺΧόϨοδܭଌͱܕνΣοΫͰςετ ͷίεύΛߴΊΒΕΔ 
 →APIͷϑΟʔυόοΫαΠΫϧ͕ૣ͘ͳΔ͜ͱΛظ଴

Slide 67

Slide 67 text

͋Γ͕ͱ͏͍͟͝·ͨ͠🐶

Slide 68

Slide 68 text

ࢀߟURL • k1LoW/runn - GitHub • runnΫοΫϒοΫ • Quick Start Kotlin - gRPC • BufͱProtobufͰ࢝ΊΔϞμϯgRPC։ൃ • αόϯφศΓʙࣗಈςετʹؔ͢Δ࿈ࡌͰಘΒΕͨ஌ݟͷ·ͱΊʢ2023೥5݄൛ʣ • Buf CLI • https://circleci.com/blog/testing-pyramid/ • https://github.com/bufbuild/protovalidate • Protocol Buffers v26.0 ͷഁյతมߋ - qiita