using grails to power your electric car
TRANSCRIPT
Small Intro
● Marco Pas (1973)
› Education in Chemistry and then moved to Transportation & Logistics to finally IT.. what is next?
● Software Engineer
● Burgundian lifestyleWikipedia 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 OfficeValidation / 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 eventsgrails.plugins.springsecurity.useSecurityEventListener = true
// executed when a user succesfully authenticates into the applicationgrails.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")
@GZIPclass 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 communicationchannel 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.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==Sec-WebSocket-Protocol: chatSec-WebSocket-Version: 13Origin: http://example.com
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=Sec-WebSocket-Protocol: chat
Vertx.io
● Asynchronous Application Development
› Polyglot, Simplicity, Scalability, Concurrency
› Distributed Event Bus
› WebSocket support
● VerticlesServer.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