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

A Hitchhiker's Guide to Cloud Native Java EE

A Hitchhiker's Guide to Cloud Native Java EE

Cloud native applications are popular these days. They promise superior reliability and almost arbitrary scalability. They follow three key principles: they are built and composed as microservices. They are packaged and distributed in containers. The containers are executed dynamically in the cloud. But all this comes at a price: added complexity! Suddenly you need to consider cloud native design principles such as service discovery, configuration, resilience, health checks and diagnosability.

While current Java EE versions do not (yet) have dedicated APIs to fully address these principles, they do provide APIs and extension points to retrofit these concepts easily with only a few line of glue code into your plain Java EE microservice.

This code intense session will present how we have built a fully cloud-native Java EE based system consisting of several microservices for a large German car manufacturer in only 3 months. We will share our experiences as well as working code examples on how we leveraged and combined standard Java EE APIs and well known open source components to do the heavy cloud-native lifting. #Javaland #CloudNativeNerd #qaware

M.-Leander Reimer

March 13, 2018
Tweet

More Decks by M.-Leander Reimer

Other Decks in Programming

Transcript

  1. Mario-Leander Reimer Chief Technologist, QAware GmbH Contact Details Mail: [email protected]

    Twitter: @LeanderReimer Github: https://github.com/lreimer/cloud-native-javaee 12.03.2018 2 Developer && Architect 20+ years of experience #CloudNativeNerd Open Source Enthusiast
  2. Transform: extract a portion of the existing functionality into a

    new and modern system. Coexist: both systems coexist for some time. Calls agains the old functionality are diverted. Eliminate: old functionality will be removed from legacy system once no more clients are using it. Ideal for Web- and API-Monoliths. Slightly problematic for Non-RESTful URLs. Apply stepwise evolution to your existing systems and Cloud-native reconstruction using the Strangler Pattern. 4 https://martinfowler.com/bliki/StranglerApplication.html
  3. High level system overview after the reconstruction. 5 Process MQseries

    OTP APRIL Payment OpenShift Billing Payment APRIL UI B&P B2I EAI/SAP Saferpay OSMC
  4. BUILT AND COMPOSED AS MICROSERVICES 3 KEY PRINCIPLES 6 CLOUD

    NATIVE APPLICATIONS PACKAGED AND DISTRIBUTED IN CONTAINERS DYNAMICALLY ORCHESTRATED IN THE CLOUD
  5. Essential Design Principles for Cloud Native Apps. 7 Design for

    Distribution: Containers; microservices; API driven development. Design for Configuration: One image, multiple environments. Design for Resiliency: Fault-tolerant and self-healing. Design for Elasticity: Scales dynamically and reacts to stimuli. Design for Delivery: Short roundtrips and automated provisioning. Design for Performance: Responsive; concurrent; resource efficient. Design for Automation: Automated Dev & Ops tasks. Design for Diagnosability: Cluster-wide logs, metrics and traces.
  6. 8

  7. 9 Components All Along the Software Lifecycle. DESIGN ▪ Complexity

    unit ▪ Data integrity unit ▪ Coherent and cohesive features unit ▪ Decoupled unit RUN ▪ Release unit ▪ Deployment unit ▪ Runtime unit (crash, slow-down, access) ▪ Scaling unit n:1 BUILD ▪ Planning unit ▪ Team assignment unit ▪ Knowledge unit ▪ Development unit ▪ Integration unit 1:1
  8. 10 Dev Components Ops Components ?:1 System Subsystems Components Services

    Good starting point DecompositionTrade-Offs Microservices Nanoservices Macroservices Monolith + More flexible to scale + Runtime isolation (crash, slow- + Independent releases, deployments, teams + Higher utilization possible  Distribution debt: Latency  Increasing infrastructure complexity  Increasing troubleshooting complexity  Increasing integration complexity
  9. Overview of Java EE 7 APIs. 12 CDI Extensions Web

    Fragments Bean Validation 1.1 CDI 1.1 Managed Beans 1.0 JCA 1.7 JPA 2.2 JMS 2.0 JSP 2.3 EL 3.0 EJB 3.2 Batch 1.0 JSF 2.2 Interceptors 1.2 Mail 1.5 Common Annotations 1.3 JTA 1.2 JAX-WS 1.4 JAX-RS 2.0 Concurrency 1.0 JSON-P 1.0 WebSocket 1.1 JASPIC 1.1 JACC 1.5 Servlet 3.1 JCache 1.0
  10. Overview of Java EE 8 APIs. CDI Extensions Web Fragments

    Bean Validation 2.0 CDI 2.0 Managed Beans 1.0 JCA 1.7 JPA 2.2 JMS 2.0 JSP 2.3 EL 3.0 EJB 3.2 Batch 1.0 JSF 2.3 Interceptors 1.2 Mail 1.6 Common Annotations 1.3 JTA 1.2 JAX-WS 1.4 JAX-RS 2.1 Concurrency 1.0 JSON-P 1.1 JSON-B 1.0 WebSocket 1.1 JAPSIC 1.1 JACC 1.5 Security 1.0 Servlet 4.0 JCache 1.0 13
  11. The Cloud-native Java EE showcase. 15 https://github.com/lreimer/cloud-native-javaee Billing Service Payment

    Service Process Service JAX-RS JMS JCache Datagrid Service Payment Queue Billing Queue Process Queue JAX-RS JMS JPA JSON-P JAX-RS JMS JPA JSON-P
  12. @Path("process") @Produces(MediaType.APPLICATION_JSON) public class ProcessResource { @Resource ManagedExecutorService executorService; @POST

    @Consumes(MediaType.APPLICATION_JSON) public void process(@Suspended AsyncResponse response, JsonObject jsonObject) { response.setTimeout(5, TimeUnit.SECONDS); response.setTimeoutHandler((r) -> r.resume(Response.accepted().build())); executorService.execute(() -> { response.resume(Response.ok().build()); }); } @GET @Path("/{processId}/status") public Response process(@PathParam("processId") String processId) { JsonObject payload = Json.createObjectBuilder() .add("processId", processId).build(); return Response.ok(payload).build(); } Simple sync and async REST APIs with JAX-RS. 17
  13. 18 Different messaging patterns for reliable, flexible and asynchronous communication

    between microservices. P1 C1 Q1 Message Passing P1 C1 Q1 Cn Work Queue P1 C1 T1 Cn Tn Publish/Subscribe P1 C1 Q1 Q2 Remote Procedure Call
  14. @MessageDriven(name = "ProcessEventMDB", activationConfig = { @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue

    = "jms/ProcessEvents"), @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto_acknowledge"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destination", propertyValue = "PROCESS.EVENTS"), @ActivationConfigProperty(propertyName = "resourceAdapter", propertyValue = "activemq-rar"), @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = "contentType = 'application/vnd.process.v1+json'") }) public class ProcessEventMDB implements MessageListener { @Inject private Event<ProcessEvent> processEvent; @Override public void onMessage(Message message) { String eventType = getEventType(message); String body = getBody(message); if ((eventType != null) && (body != null)) { JsonReader reader = Json.createReader(new StringReader(body)); processEvent.fire(ProcessEvent.from(eventType, reader.readObject())); } } 19 Simple Message Driven Beans to receive messages. This also works for For other JCA adapters visit https://github.com/payara/Cloud-Connectors
  15. JsonObject currentWeather = Json.createObjectBuilder() .add(“processId", “4711") .add(“some", “content") .build(); StringWriter

    payload = new StringWriter(); JsonWriter jsonWriter = Json.createWriter(payload); jsonWriter.writeObject(currentWeather); TextMessage msg = session.createTextMessage(payload.toString()); msg.setJMSType(“ProcessEvent"); msg.setStringProperty("contentType", "application/vnd.process.v1+json"); @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = "(JMSType = ProcessEvent') AND (contentType = 'application/vnd.process.v1+json‘)“) JsonReader reader = Json.createReader(new StringReader(body)); JsonObject jsonObject = reader.readObject(); 20 Use JSON-P to build your JsonObject and JsonArray instances. Use JSON-P to read JSON payloads. Use JSON-P to traverse and access JSON objects and arrays. Since Java EE 8: JSON Pointers and JSON Patch add even more flexibility. Use Mime-Type versioning for your JSON messages if required. Use JMS message selectors to filter on JMS type and content type. Alternatively use flexible binary protocols like ProtoBuf. Use JSON as payload format for loose coupling. Use JSON-P to implement tolerant reader pattern.
  16. Use Apache DeltaSpike for some extra configuration features as well

    as other CDI extension magic. 22 DeltaSpike consists of a number of portable CDI extensions that provide useful features for Java application developers. Set of ready-to-use modules, including a core module and a number of optional modules for providing additional enterprise functionality to your applications. Core module with type-safe project stages and powerful interface based configuration mechanism Data and JPA module enhanced JPA experience with declarative queries, reducing boilerplate to a minimum Security module for intercept and security checking on method calls. Test-Control module for writing CDI-based tests easily @Configuration(prefix = "some.", cacheFor = 30, cacheUnit = TimeUnit.SECONDS) public interface SomeConfiguration { @ConfigProperty(name = "url") String url(); @ConfigProperty(name = "timeout", defaultValue = "30000") long timeout(); }
  17. apiVersion: v1 kind: ConfigMap metadata: name: process-service-config data: APP_NAME: process-service

    org_apache_deltaspike_ProjectStage: Production application.properties: | process.service.jwt.secret=some-secret ... feature-togglz.properties: | DO_SOME_STUFF=false ... log4j2.xml: | <?xml version="1.0" encoding="UTF-8"?> <Configuration monitorInterval="60"> <Appenders> ... </Appenders> <Loggers> ... </Loggers> </Configuration> 23 Use ConfigMaps, Secrets and Volumes to provide ENV or file based configuration data to your deployments. spec: containers: - name: process-service image: lreimer/process-service:1.1 ports: - containerPort: 8080 envFrom: - configMapRef: name: process-service-config volumeMounts: - mountPath: /process-service/data name: process-service-config-vol volumes: - name: process-service-config-vol configMap: name: process-service-config
  18. <!-- http://metrics.dropwizard.io/3.1.0/manual/servlets/ --> <servlet> <servlet-name>adminServlet</servlet-name> <servlet-class> com.codahale.metrics.servlets.AdminServlet </servlet-class> </servlet> <servlet-mapping>

    <servlet-name>adminServlet</servlet-name> <url-pattern>/admin/*</url-pattern> </servlet-mapping> 25 Retrofitting metrics, health and admin endpoints using the Dropwizard Metrics library in 30 minutes. <dependencies> <dependency> <groupId>io.dropwizard.metrics</groupId> <artifactId>metrics-core</artifactId> <version>${metrics.version}</version> </dependency> </dependencies> Usage of Dropwizard Metrics to retrofit metrics, health and admin endpoints Easy integration with any JEE7 application Definition of Custom Health Checks possible Used as Liveness und Readiness Probes https://www.robustperception.io/exposing- dropwizard-metrics-to-prometheus/
  19. # container will receive requests if probe succeeds readinessProbe: httpGet:

    path: /admin/healthcheck port: 8080 initialDelaySeconds: 60 timeoutSeconds: 5 # container will be killed if probe fails livenessProbe: httpGet: path: /admin/ping port: 8080 initialDelaySeconds: 30 timeoutSeconds: 5 Liveness and Readiness Probes for Antifragility. 26
  20. 28 Retrofitting resiliency using Netflix Hystrix is also easy. Use

    Netflix Hystrix for the resilient (synchronous) call of any external system Rock solid Circuit Breaker and Bulk Heading implementation Easy integration with any JEE application Can be used easily with JAX-RS Client API for REST Calls. Use Interceptors or Decorators to apply HystricCommands. Can be integrated with JSR 236 Concurrency API via HystrixConcurrencyStrategy Integrates seemlessly with Dropwizard Metrics <dependencies> <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-core</artifactId> <version>${hystrix.version}</version> </dependency> </dependencies>
  21. public class HystrixJsr236ConcurrencyStrategy extends HystrixConcurrencyStrategy { private final Context context;

    public HystrixConcurrencyStrategyJsr236() { context = initialContext(); } @Override public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { ThreadFactory threadFactory = lookupManagedThreadFactory(threadPoolKey); if (threadFactory != null) { return new ThreadPoolExecutor(corePoolSize.get(), maximumPoolSize.get(), keepAliveTime.get(), unit, workQueue, threadFactory); } else { LOGGER.warn("Fallback to Hystrix default thread pool executor."); return super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } } ... // 20 lines more implementation } create-managed-thread-factory concurrent/BackendThreadFactory 29
  22. FROM payara/server-full:181 ENV AS_ADMIN $PAYARA_PATH/bin/asadmin ENV DOMAIN domain1 COPY build/activemq/activemq-rar-5.15.3.rar

    /tmp/ COPY build/postgresql/* ${PAYARA_PATH}/glassfish/domains/${PAYARA_DOMAIN}/lib/ COPY build/hazelcast/* ${PAYARA_PATH}/glassfish/lib/ COPY domain-config.asadmin /tmp/ RUN $AS_ADMIN start-domain $DOMAIN && \ $AS_ADMIN --user admin --passwordfile=/opt/pwdfile multimode --file /tmp/domain-config.asadmin && \ $AS_ADMIN stop-domain $DOMAIN COPY build/libs/process-service.war /opt/payara41/glassfish/domains/domain1/autodeploy/ Example Dockerfile for Payara Server. 33 The magic happens here.
  23. deploy --type rar --name activemq-rar /tmp/activemq-rar-5.15.3.rar create-resource-adapter-config --property ServerUrl='tcp\://message-queue\:61616':UserName='admin':Password='admin' activemq-rar

    create-connector-connection-pool --raname activemq-rar \ --connectiondefinition javax.jms.ConnectionFactory --ping false --isconnectvalidatereq true jms/activeMqConnectionPool create-connector-resource --poolname jms/activeMqConnectionPool jms/activeMqConnectionFactory create-admin-object --raname activemq-rar --restype javax.jms.Queue --property PhysicalName=PROCESS.EVENTS jms/ProcessEvents create-admin-object --raname activemq-rar --restype javax.jms.Queue --property PhysicalName=BILLING.EVENTS jms/BillingEvents create-admin-object --raname activemq-rar --restype javax.jms.Queue --property PhysicalName=PAYMENT.EVENTS jms/PaymentEvents create-jdbc-connection-pool --datasourceclassname org.postgresql.ds.PGConnectionPoolDataSource \ --restype javax.sql.ConnectionPoolDataSource \ --property portNumber=5432:password='12qwasyx':user='process':serverName=process-db:databaseName='process' PostgresPool create-jdbc-resource --connectionpoolid PostgresPool jdbc/ProcessDb set resources.jdbc-connection-pool.PostgresPool.connection-validation-method=custom-validation set resources.jdbc-connection-pool.PostgresPool.validation-classname=org.glassfish.api.jdbc.validation.PostgresConnectionValidation set resources.jdbc-connection-pool.PostgresPool.is-connection-validation-required=true set resources.jdbc-connection-pool.PostgresPool.fail-all-connections=true create-managed-thread-factory concurrent/BackendThreadFactory Payara Server specific domain configuration. 34
  24. version: ‘3’ services: message-queue: image: rmohr/activemq:5.14.3 expose: - "61616" #

    the JMS port process-db: image: "postgres:9.6.3" environment: - POSTGRES_USER=process - POSTGRES_PASSWORD=12qwasyx ports: - ”15432:5432”. # the JDBC port A docker-compose.yml for building and running locally. 35 process-service: build: ./microservices/process-service image: lreimer/process-service:1.1 volumes: - ... expose: - "5701" # the outbound Hazelcast port - "54327" # the multicast Hazelcast port ports: - "18081:8080" # the HTTP endpoint depends_on: - message-queue - process-db
  25. Lightweight development workflow for short roundtrips, low infrastructure complexity and

    local troubleshooting. 37 kompose K8s and OpenShift Deployment YAML Dockerfile + docker-compose.yml Local Development Cloud Deployment
  26. Most important Kubernetes concepts. 40 Services are an abstraction for

    a logical collection of pods. Pods are the smallest unit of compute in Kubernetes Deployments are an abstraction used to declare and update pods, RCs, Replica Sets ensure that the desired number of pod replicas are running Labels are key/value pairs used to identify Kubernetes resources
  27. apiVersion: extensions/v1beta1 kind: Deployment metadata: name: process-service spec: replicas: 2

    strategy: type: RollingUpdate template: metadata: labels: io.kompose.service: process-service hazelcast: enabled spec: containers: - name: process-service image: lreimer/process-service:1.1 ports: - containerPort: 8080 Example K8s Deployment Definition. 41
  28. resources: # Define resources to help K8S scheduler # CPU

    is specified in units of cores # Memory is specified in units of bytes # required resources for a Pod to be started requests: memory: “196Mi" cpu: "250m" # the Pod will be restarted if limits are exceeded limits: memory: “512Mi" cpu: "500m" Define Resource Constraints carefully. 42
  29. -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -server -Xmx320m -Xss256k -XX:MaxMetaspaceSize=160m -XX:CompressedClassSpaceSize=32m # Or use

    G1GC -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:NewRatio=1 -XX:+CMSParallelRemarkEnabled # Use for small heaps on 64-bit VMs -XX:+AggressiveOpts -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -XX:+UseStringDeduplication # optional -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary Tune your JVM! 43 Since jdk8_131 Extra memory settings GC tuning. Fancy tuning. Diagnostics.