Share
Embed
Using Grails to power your Electric Car
Marco Pas
May 22, 2013
Transcript
44 slides
-
Small Intro • Marco Pas (1973) › Education in Chemistry
and then moved to Transportation & Logistics to finally IT.. what is next? • Software Engineer • Burgundian lifestyle Wikipedia tells us : 'enjoyment of life, good food, and extravagant spectacle' -
Agenda • What the heck am I trying to solve!!
• How on earth did we solve it? • What are our plans for the future? -
Convention Fueling != Electric Vehicle Charging • EV Charging Challenges:
› Impossible to store & forward electricity › Charge often (Limited Range) › Time to charge (from minutes to hours) › Compatibility (plugs, charging cables) -
Public EV Chargers in numbers • 2011: EU 12.000 •
2020: EU 660.000 2011 2020 Denmark 280 5.000 Germany 1.900 150.000 Italy 1.300 125.000 Netherlands 1.700 32.000 United Kingdom 703 122.000 -
Charging Process Back Office Validation / Verification • Is the
card valid? • Electricity pricing? • How much can I charge? • Who is the customer? • Did you make a reservation? • …. -
Requirements • Implement a platform that enables EV Infra management:
› To monitor a Charge Point (CP) network › Supports remote management › Track charging sessions • Including charge authorization & transaction storage › Multi-tenancy › 3rd party integration using Web Services (SOAP / REST) -
Open Charge Point Protocol (OCPP) • Open protocol between charging
stations and a managing central system aka back office › Asynchronous › Based on SOAP (v1.2) › Working group with support from all manufacturers! -
Agenda • What the heck am I trying to solve!!
• How on earth did we solve it? • What are our plans for the future? -
Release 1.0 ! ! SOAP ~ Contract First with Grails
was not that easy, so we moved to a pure JAVA/Spring OCPP application -
Jackson JSON Mapper public class JsonMapper extends ObjectMapper { public
JsonMapper() { super(); /** * De-Serialization options JSON -> OBJECT */ // - ignore unknown fields - otherwise the construction of the object will fail! this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // - make it possible to also construct empty objects this.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); /** * Serialization options OBJECT -> JSON */ // properties with non-null values are to be included in the resulting JSON this.setSerializationInclusion(JsonInclude.Include.NON_NULL); } } -
Jackson JSON Mapper • JSON → Object • Object →
JSON resources.groovy beans = { // a preconfigured Jackson JSON mapper with defaults jsonMapper(JsonMapper){} } def jsonContent = “{'name': 'John Doe'}” Person person = jsonMapper.readValue(jsonContent, Person.class) Person person = new Person(name: 'John Doe') def jsonContent = jsonMapper.valueToTree(person) -
Theming support • Login-Logout & user configurable themes • Using
Spring Security Core Plugin Config.groovy // make sure we can act on security events grails.plugins.springsecurity.useSecurityEventListener = true // executed when a user succesfully authenticates into the application grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx → // .. code intentionally emitted .. // session.theme = “MyNameOfTheTheme” } page.gsp <g:if test="${session?.theme == null}"> <% session.theme="${grailsApplication.config.default.theme}"%> </g:if> <r:require module="${session?.theme}"/> <g:external uri="/css/themes/${session?.theme - "theme_"}/images/favicon.ico"/> -
Technical Debt • Client was very happy but.... shame on
us: › Tightly coupled › Poor test coverage › Spring project & Grails project › Adding functional value is just to much fun! • But... ready for round 2.. › Thanks to Grails we could refactor with great ease and speed! -
Release 2.0 • Guidelines: › Focus on creating a modular
platform › Test Driven Development › Use Grails for everything!! › Minimize the use of plugins!!! -
Grails CXF Plugin • WSDL › Contract First & Code
First • Wire Interceptors › Logging, Security • Support for versioning @GrailsCxfEndpoint(address='/myCustomSoapService/v2/') -
Annotated example @GrailsCxfEndpoint( address='/centralsystem/ocpp/v1/5', wsdl = 'wsdl/ocpp_15_centralsystem.wsdl', expose = EndpointType.JAX_WS_WSDL,
soap12 = true, inInterceptors = ["logSoapInboundInterceptor", "setReplyToSOAPHeaderInInterceptor"], outInterceptors = ["logSoapOutboundInterceptor"]) @WebService( name = "CentralSystemService", targetNamespace = "urn://Ocpp/Cs/2012/06/", serviceName = "CentralSystemService", portName = "CentralSystemServiceSoap12" ) @GZIP class CentralSystemOcpp15Service implements CentralSystemService { // ... code intentionally omitted // ... contains the methods that needs to be implemented due to the 'implements' } -
Demo • Create a contract first webservice using Grails CXF
plugin › Source WSDL: CustomerService.wsdl › Steps: • Create grails project • Install CXF plugin • Use WSDL2JAVA to generate web service implementation • Create Grails service that implements the web service interface • Test using SOAPUI -
AMQP - Advanced Message Queuing Protocol • Asynchronous and synchronous
message exchange › Enables modular platform architecture -
RabbitMQ – an AMQP implementation • Grails RabbitMQ Plugin ›
High-level abstraction for sending and receiving messages › Fallback to Spring Template class MessageReceiveService { static rabbitQueue = 'helloQ' void handleMessage(message) { // handle message… } } class MessageSendController { def sendMessage = { rabbitSend 'helloQ', 'Hello!' } } -
RabbitMQ Synchronous class MessageSendController { def rabbitTemplate // use the
Spring rabbitTemplate directly def sendMessage = { def response = rabbitTemplate.convertSendAndReceive 'helloQ', 'Hello World' println response } } class MessageReceiveService { static rabbitQueue = [queues: 'helloQ', messageConverterBean: ''] void handleMessage(Message message) { // determine the reply queue def returnQueue = message.messageProperties.replyTo // return the response to temporary return queue.. rabbitSend returnQueue, 'Hello there' } } -
Demo • Send and Consume a message via RabbitMQ ›
Steps: • Install RabbitMQ plugin • Configure Grails app to use RabbitMQ • Create code to publish and consume a message -
Testing • Functional & Unit Testing › Build-Test-Data › Spock
• Load & Performane Testing › BadBoy / Apache JMeter -
Some stuff we have used • Grails Plugins › Spring
Security › Export › RabbitMQ › CXF › Fixture › Spock › Build-Test-Data • Non-Plugins › Twitter BootStrap › Jackson JSON -
Agenda • What the heck am I trying to solve!!
• How on earth did we solve it? • What are our plans for the future? -
Release 3.0 • Additional protocol implementation in JSON › Eliminating
the verbosity of SOAP! › To ([<MessageTypeId>, “<UniqueId>”, “<Action>”, {<Payload>}]) <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa5="http://www.w3.org/2005/08/addressing" xmlns:cp="urn://Ocpp/Cp/2012/06/" xmlns:cs="urn://Ocpp/Cs/2012/06/" xmlns:imp="urn://iMOVE/Cp/2011/09/" xmlns:ims="urn://iMOVE/Cs/2011/09/"> <SOAP-ENV:Header> <wsa5:MessageID>940</wsa5:MessageID> <wsa5:From> <wsa5:Address>http://127.0.0.1:1234</wsa5:Address> </wsa5:From> <wsa5:ReplyTo SOAP-ENV:mustUnderstand="true"> <wsa5:Address>http://127.0.0.1:1234</wsa5:Address> </wsa5:ReplyTo> <wsa5:To SOAP-ENV:mustUnderstand="true">http://www.starwarsrules.com/services/centralsystem/ocpp/v1/5</wsa5:To> <wsa5:Action SOAP-ENV:mustUnderstand="true">/Heartbeat</wsa5:Action> <cs:chargeBoxIdentity SOAP-ENV:mustUnderstand="true">CHARGER_001_1234</cs:chargeBoxIdentity> </SOAP-ENV:Header> <SOAP-ENV:Body> <cs:heartbeatRequest /> </SOAP-ENV:Body> </SOAP-ENV:Envelope> [2, “19223201”, “HeartBeat”, {“”}] <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa5="http://www.w3.org/2005/08/addressing" xmlns:cp="urn://Ocpp/Cp/2012/06/" xmlns:cs="urn://Ocpp/Cs/2012/06/" xmlns:imp="urn://iMOVE/Cp/2011/09/" xmlns:ims="urn://iMOVE/Cs/2011/09/"> <SOAP-ENV:Header> <wsa5:MessageID>940</wsa5:MessageID> <wsa5:From> <wsa5:Address>http://127.0.0.1:1234</wsa5:Address> </wsa5:From> <wsa5:ReplyTo SOAP-ENV:mustUnderstand="true"> <wsa5:Address>http://127.0.0.1:1234</wsa5:Address> </wsa5:ReplyTo> <wsa5:To SOAP-ENV:mustUnderstand="true">http://www.starwarsrules.com/services/centralsystem/ocpp/v1/5</wsa5:To> <wsa5:Action SOAP-ENV:mustUnderstand="true">/Heartbeat</wsa5:Action> <cs:chargeBoxIdentity SOAP-ENV:mustUnderstand="true">CHARGER_001_1234</cs:chargeBoxIdentity> </SOAP-ENV:Header> <SOAP-ENV:Body> <cs:heartbeatRequest /> </SOAP-ENV:Body> </SOAP-ENV:Envelope> -
WebSockets • Defines an API establishing a "socket" connections between
a client and a server › Providing full-duplex communication channel over a single TCP connection › HTTP upgrade by Protocol Negotiation › Firewall friendly! (port 80) › No reconnect handling or guaranteed message delivery -
WebSocket handshake • Client Request • Server Response GET /mychat
HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat Sec-WebSocket-Version: 13 Origin: http://example.com HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat -
Vertx.io • Asynchronous Application Development › Polyglot, Simplicity, Scalability, Concurrency
› Distributed Event Bus › WebSocket support • Verticles Server.groovy vertx.createHttpServer().requestHandler { req -> req.response.headers().put("Content-Type", "text/html; charset-UTF-8"); req.response.end("<html><body><h1>Hello from vert.x!</h1></body></html>"); }.listen(8080) vertx run Server.groovy vertx run Server.groovy -instances 4