9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Serverless FrameworkをつかってSpring Cloud Function !!

Last updated at Posted at 2017-11-29
  • Serverless FrameworkでLambda(java)を試したい
  • Spring Cloud Functionを試したい

の両方の欲求を一度に満たす方法を思いついたので試してみました:heart_eyes:

#Serverless Frameworkのインストール
割愛:smile:

#Serverless Frameworkでサービスをつくる

E:\workspaces\qiita> sls create --template aws-java-maven --path aws-spring-cloud-function-maven
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "E:\workspaces\qiita\aws-spring-cloud-function-maven"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.24.1
 -------'

Serverless: Successfully generated boilerplate for template: "aws-java-maven"

E:\workspaces\qiita>

一旦Serverless FrameworkはここまででSpring Cloud Functionに移ります。

Spring Cloud Functionまわり

##pom.xmlを修正します
spring-cloud-functionのサンプルを見ながら
slsでできたpomを下記のファイルのように変更しました。
ちょっと長いので直接見てください。
pom.xml

##thin.propertiesをコピーします
light weight packaging してくれます。(のはず:sweat_smile:

詳細はこちらでhttps://github.com/dsyer/spring-boot-thin-launcher

##hello spring-cloud-function!!!!
Application.javaFunction.javaを作りました。
あと中身空っぽですがProperties.javaも。。。
@FunctionScanを使ってみたかったのでjava.util.function.Functionを継承しています。
実際にスキャンするベースパッケージのデフォルトが

@ComponentScan(basePackages = "${spring.cloud.function.scan.packages:functions}",
	includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Function.class))

なのでfunctionsへ。
Function.javaにした理由はSpringFunctionInitializerがデフォルトでは「function」という名前のbeanを引っ張ってくるのでそうしてあります。
環境変数を設定することによって、そっちが優先されるので任意の名前もOKです。

これでやっとhelloできます:sunglasses:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.3.RELEASE)

2017-11-28 15:43:40.523  INFO 9740 --- [           main] Application                              : Starting Application on STYLEZPC210 with PID 9740 (E:\workspaces\e4.7\aws-spring-cloud-function-maven\target\classes started by hasegawa in E:\workspaces\e4.7\aws-spring-cloud-function-maven)
2017-11-28 15:43:40.525  INFO 9740 --- [           main] Application                              : No active profile set, falling back to default profiles: default
2017-11-28 15:43:40.575  INFO 9740 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@3bd36c8e: startup date [Tue Nov 28 15:43:40 JST 2017]; root of context hierarchy
2017-11-28 15:43:41.460  INFO 9740 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.function.web.flux.ReactorAutoConfiguration' of type [org.springframework.cloud.function.web.flux.ReactorAutoConfiguration$$EnhancerBySpringCGLIB$$771a8d51] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-11-28 15:43:41.741  INFO 9740 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-11-28 15:43:41.751  INFO 9740 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2017-11-28 15:43:41.752  INFO 9740 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.14
2017-11-28 15:43:41.826  INFO 9740 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-11-28 15:43:41.826  INFO 9740 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1254 ms
2017-11-28 15:43:41.949  INFO 9740 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-11-28 15:43:41.954  INFO 9740 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-11-28 15:43:41.955  INFO 9740 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-11-28 15:43:41.955  INFO 9740 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-11-28 15:43:41.956  INFO 9740 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-11-28 15:43:42.223  INFO 9740 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@3bd36c8e: startup date [Tue Nov 28 15:43:40 JST 2017]; root of context hierarchy
2017-11-28 15:43:42.314  INFO 9740 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-11-28 15:43:42.315  INFO 9740 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-11-28 15:43:42.340  INFO 9740 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-28 15:43:42.341  INFO 9740 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-28 15:43:42.374  INFO 9740 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-28 15:43:42.464  INFO 9740 --- [           main] o.s.c.f.web.flux.FunctionHandlerMapping  : FunctionCatalog: org.springframework.cloud.function.context.InMemoryFunctionCatalog@468e1b6, FunctionInspector: org.springframework.cloud.function.context.ContextFunctionCatalogAutoConfiguration$BeanFactoryFunctionInspector@360efe6c
2017-11-28 15:43:42.474  INFO 9740 --- [           main] o.s.c.f.web.flux.FunctionHandlerMapping  : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-11-28 15:43:42.474  INFO 9740 --- [           main] o.s.c.f.web.flux.FunctionHandlerMapping  : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-11-28 15:43:42.484  INFO 9740 --- [           main] o.s.c.f.web.flux.FunctionHandlerMapping  : Mapped "{[/**],methods=[GET]}" onto public java.lang.Object org.springframework.cloud.function.web.flux.FunctionController.get(java.util.function.Function<reactor.core.publisher.Flux<?>, reactor.core.publisher.Flux<?>>,java.util.function.Supplier<reactor.core.publisher.Flux<?>>,java.lang.String)
2017-11-28 15:43:42.485  INFO 9740 --- [           main] o.s.c.f.web.flux.FunctionHandlerMapping  : Mapped "{[/**],methods=[POST]}" onto public org.springframework.http.ResponseEntity<reactor.core.publisher.Flux<?>> org.springframework.cloud.function.web.flux.FunctionController.post(java.util.function.Function<reactor.core.publisher.Flux<?>, reactor.core.publisher.Flux<?>>,java.util.function.Consumer<reactor.core.publisher.Flux<?>>,java.lang.Boolean,org.springframework.cloud.function.web.flux.re
quest.FluxRequest<?>)
2017-11-28 15:43:42.525  INFO 9740 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-11-28 15:43:42.583  INFO 9740 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-11-28 15:43:42.587  INFO 9740 --- [           main] Application                              : Started Application in 2.497 seconds (JVM running for 5.276)

からの
POSTするデータを作成します。

event.json
{"value":"hello spring-cloud-function"}

E:\workspaces\e4.7\aws-spring-cloud-function-maven>curl -X POST "http://localhost:8080/function" -d @event.json -H "Content-Type: application/json"
[{"value":"hello spring-cloud-function:0"},{"value":"hello spring-cloud-function:1"},{"value":"hello spring-cloud-function:2"},{"value":"hello spring-cloud-function:3"},{"value":"hello spring-cloud-function:4"}]
E:\workspaces\e4.7\aws-spring-cloud-function-maven>

hello x 5でました。:)

Functionの返却値はFluxなのでServer-Sent Eventsに対応しています。
※返却値はFluxじゃなくても良いです
"Accept: text/event-stream"を追加すると

E:\workspaces\e4.7\aws-spring-cloud-function-maven>curl -X POST "http://localhost:8080/function" -d @event.json -H "Accept: text/event-stream" -H "Content-Type: application/json"
data:{"value":"hello spring-cloud-function:0"}

data:{"value":"hello spring-cloud-function:1"}

data:{"value":"hello spring-cloud-function:2"}

data:{"value":"hello spring-cloud-function:3"}

data:{"value":"hello spring-cloud-function:4"}

E:\workspaces\e4.7\aws-spring-cloud-function-maven>

レスポンスの形式が変わります。
Reactorについてはこの辺とかを参照してください。
Spring WebFluxとかSpring 5に追加されたし次回はこの辺を攻めようと思います。

と、ここまででSpring Cloud Function に満足したので:sweat_smile:
#Lambdaにデプロイする
##serverless.ymlを修正します。

serverless.yml
# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
#    docs.serverless.com
#
# Happy Coding!
# https://serverless.com/framework/docs/providers/aws/guide/serverless.yml/

service: aws-spring-cloud-function-maven

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
# frameworkVersion: "=X.X.X"

provider:
  name: aws
  runtime: java8
  timeout: 30

# you can overwrite defaults here
#  stage: dev
  region: ap-northeast-1

# you can add statements to the Lambda function's IAM Role here
#  iamRoleStatements:
#    - Effect: "Allow"
#      Action:
#        - "s3:ListBucket"
#      Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ]  }
#    - Effect: "Allow"
#      Action:
#        - "s3:PutObject"
#      Resource:
#        Fn::Join:
#          - ""
#          - - "arn:aws:s3:::"
#            - "Ref" : "ServerlessDeploymentBucket"
#            - "/*"

# you can define service wide environment variables here
#  environment:
#    variable1: value1

# you can add packaging information here
package:
  artifact: target/aws-spring-cloud-function-maven-dev-aws.jar

functions:
  hello:
    handler: org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler

#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
    events:
      - http:
          path: hello-spring-cloud-function
          method: post
          integration: lambda
          request:
            template: 
              application/json: '$input.json("$")'
#      - s3: ${env:BUCKET}
#      - schedule: rate(10 minutes)
#      - sns: greeter-topic
#      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
#      - alexaSkill
#      - iot:
#          sql: "SELECT * FROM 'some_topic'"
#      - cloudwatchEvent:
#          event:
#            source:
#              - "aws.ec2"
#            detail-type:
#              - "EC2 Instance State-change Notification"
#            detail:
#              state:
#                - pending
#      - cloudwatchLog: '/aws/lambda/hello'

#    Define function environment variables here
#    environment:
#      variable2: value2

# you can add CloudFormation resource templates here
#resources:
#  Resources:
#    NewResource:
#      Type: AWS::S3::Bucket
#      Properties:
#        BucketName: my-new-bucket
#  Outputs:
#     NewOutput:
#       Description: "Description for the output"
#       Value: "Some output value"

デプロイするまえに一回ローカルで実行してみます。

E:\workspaces\e4.7\aws-spring-cloud-function-maven>serverless invoke local --function hello -p event.json -v
Serverless: Building Java bridge, first invocation might take a bit longer.
events.js:161
      throw er; // Unhandled 'error' event
      ^

Error: spawn mvn ENOENT
    at exports._errnoException (util.js:1023:11)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:193:32)
    at onErrorNT (internal/child_process.js:359:16)
    at _combinedTickCallback (internal/process/next_tick.js:74:11)
    at process._tickDomainCallback (internal/process/next_tick.js:122:9)

動かない。。。試しに何も手を加えずクリエイトして実行してみます。。。

E:\workspaces\qiita>sls create --template aws-java-maven --path xxx
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "E:\workspaces\qiita\xxx"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.24.1
 -------'

Serverless: Successfully generated boilerplate for template: "aws-java-maven"

E:\workspaces\qiita>cd xxx

E:\workspaces\qiita\xxx>mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building hello dev
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to E:\workspaces\qiita\xxx\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory E:\workspaces\qiita\xxx\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
[INFO] Building jar: E:\workspaces\qiita\xxx\target\hello-dev.jar
[INFO]
[INFO] --- maven-shade-plugin:2.3:shade (default) @ hello ---
[INFO] Including com.amazonaws:aws-lambda-java-core:jar:1.1.0 in the shaded jar.
[INFO] Including com.amazonaws:aws-lambda-java-log4j:jar:1.0.0 in the shaded jar.
[INFO] Including log4j:log4j:jar:1.2.17 in the shaded jar.
[INFO] Including com.fasterxml.jackson.core:jackson-core:jar:2.8.5 in the shaded jar.
[INFO] Including com.fasterxml.jackson.core:jackson-databind:jar:2.8.5 in the shaded jar.
[INFO] Including com.fasterxml.jackson.core:jackson-annotations:jar:2.8.5 in the shaded jar.
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing E:\workspaces\qiita\xxx\target\hello-dev.jar with E:\workspaces\qiita\xxx\target\hello-dev-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.127 s
[INFO] Finished at: 2017-11-28T18:50:00+09:00
[INFO] Final Memory: 24M/311M
[INFO] ------------------------------------------------------------------------

E:\workspaces\qiita\xxx>serverless  invoke local --function hello
Serverless: Building Java bridge, first invocation might take a bit longer.
events.js:161
      throw er; // Unhandled 'error' event
      ^

Error: spawn mvn ENOENT
    at exports._errnoException (util.js:1023:11)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:193:32)
    at onErrorNT (internal/child_process.js:359:16)
    at _combinedTickCallback (internal/process/next_tick.js:74:11)
    at process._tickDomainCallback (internal/process/next_tick.js:122:9)

E:\workspaces\qiita\xxx>

どうやら僕のせいじゃないようです。。。

##デプロイ
mvn clean packageしてからの

E:\workspaces\e4.7\aws-spring-cloud-function-maven>sls deploy
Serverless: Packaging service...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............................
Serverless: Stack update finished...
Service Information
service: aws-spring-cloud-function-maven
stage: dev
region: ap-northeast-1
stack: aws-spring-cloud-function-maven-dev
api keys:
  None
endpoints:
  POST - https://ZZZzzzZZZzzzZZZ.execute-api.ap-northeast-1.amazonaws.com/dev/hello-spring-cloud-function
functions:
  hello: aws-spring-cloud-function-maven-dev-hello

#確認
##Lambda

E:\workspaces\e4.7\aws-spring-cloud-function-maven>serverless invoke --function hello -p event.json
[
    {
        "value": "hello spring-cloud-function:0"
    },
    {
        "value": "hello spring-cloud-function:1"
    },
    {
        "value": "hello spring-cloud-function:2"
    },
    {
        "value": "hello spring-cloud-function:3"
    },
    {
        "value": "hello spring-cloud-function:4"
    }
]

:smile:
##API Gateway

E:\workspaces\e4.7\aws-spring-cloud-function-maven>curl -X POST "https://ZZZzzzZZZzzzZZZ.execute-api.ap-northeast-1.amazonaws.com/dev/hello-spring-cloud-function" -d @event.json -H "Content-Type: application/json"
[{"value":"hello spring-cloud-function:0"},{"value":"hello spring-cloud-function:1"},{"value":"hello spring-cloud-function:2"},{"value":"hello spring-cloud-function:3"},{"value":"hello spring-cloud-function:4"}]

:grin:

#最後に
今回作ったものをgithubに公開したのでテンプレートとして使えるか確認します。
https://github.com/YusukeHasegawa/aws-spring-cloud-function-maven

E:\workspaces\qiita>sls create --template-url https://github.com/YusukeHasegawa/aws-spring-cloud-function-maven --path myService
Serverless: Generating boilerplate...
Serverless: Downloading and installing "aws-spring-cloud-function-maven"...
Serverless: Successfully installed "myService"

E:\workspaces\qiita>cd myService

E:\workspaces\qiita\myService>mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building aws-spring-cloud-function-maven dev
[INFO] ------------------------------------------------------------------------
.
.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.863 s
[INFO] Finished at: 2017-11-29T11:35:08+09:00
[INFO] Final Memory: 30M/292M
[INFO] ------------------------------------------------------------------------

E:\workspaces\qiita\myService>sls deploy
Serverless: Packaging service...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............................
Serverless: Stack update finished...
Service Information
service: myService
stage: dev
region: ap-northeast-1
stack: myService-dev
api keys:
  None
endpoints:
  POST - https://ZZZzzzZZZzzzZZZ.execute-api.ap-northeast-1.amazonaws.com/dev/hello-spring-cloud-function
functions:
  hello: myService-dev-hello

E:\workspaces\qiita\myService>curl -X POST "https://ZZZzzzZZZzzzZZZ.execute-api.ap-northeast-1.amazonaws.com/dev/hello-spring-cloud-function" -d @event.json -H "Content-Type: application/json"
[{"value":"hello spring-cloud-function:0"},{"value":"hello spring-cloud-function:1"},{"value":"hello spring-cloud-function:2"},{"value":"hello spring-cloud-function:3"},{"value":"hello spring-cloud-function:4"}]
E:\workspaces\qiita\myService>sls remove
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
....................
Serverless: Stack removal finished...

E:\workspaces\qiita\myService>

:ok_hand:

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?