Application Architectures in Grails

Preview:

DESCRIPTION

Talk given at Spring One 2GX 2013. Tries to get Grails developers to start thinking about the structure of their applications.

Citation preview

Application architectures in GrailsPeter Ledbrook

e: p.ledbrook@cacoethes.co.ukt: @pledbrook

Wednesday, 11 September 13

Book Author

Wednesday, 11 September 13

We start with our domain classes using the usual hasMany, belongsTo etc.

Book AuthorDomain

Scaffolded BookController

Scaffolded AuthorController

Wednesday, 11 September 13

Create instant web UI with scaffolded controllersCan be retained for administrative UI if secured by Spring Security, Shiro, etc.

Book AuthorDomain

Scaffolded BookController

Scaffolded AuthorController

LibraryService

LibraryController Views

Wednesday, 11 September 13

Build out proper UI using controller actions and views, utilising business logic in the servicesControllers stick to HTTP management

Thank you

Wednesday, 11 September 13

Some time left for questions...

Only joking

Wednesday, 11 September 13

Is it always the best architecture?

Wednesday, 11 September 13

How many people are using it?

Book Author

Why do we start here?

Wednesday, 11 September 13

“The database is just a detail that you don’t need to figure out right away”

NO DB - May 2012Robert “Uncle Bob” Martin

Wednesday, 11 September 13

Domain-driven Design

Wednesday, 11 September 13

Not the same as domain classes firstModel your domain first without integration concernsIt’s in operation at all stages of development, not just up-frontReminded of a problem domain related to managing meetings and attendees - focused so hard on the DB tables that the program logic was a dog’s breakfast.

Think Physics

Friction

Gravity

Wednesday, 11 September 13

from Wikmedia CommonsWednesday, 11 September 13

We can use the model to calculate useful information, such as how long it takes for a ball to roll down a hillThe model only includes significant complexity - ignores the restFormula 1 makes use of CFD because they need it at the bleeding edge

Remember: you’re trying to solve a business problem

Wednesday, 11 September 13

You need to understand the problem domainThe model needs to reflect that understandingGradle is a great example of a rich, evolving, and useful domain model

The Life Preserver

Domain

RESTPersistenceMes

sagi

ng

EventsCourtesy of Simplicity Itself

Wednesday, 11 September 13

Note how persistence is treated as an integration pointOpens up novel approachesCould use mybatis + Flyway instead of GORM for example

An example - reporting

ReportController ReportService

Jasper

Wednesday, 11 September 13

High volume transactional web site, optimised for writeEverything was OK at this point

An example - reporting

ReportController ReportService

Jasper + HTML reports with paging

Breakage!

Wednesday, 11 September 13

The logic for building reports was complexWho is responsible for the paging? The HTML generation?Where is the state kept? The service? A domain class?

An example - reporting

PublisherReportHTTP

Request

SummaryTable 1Table 2...Table N

It’s a command object!

Wednesday, 11 September 13

Let’s try againThe logic for building the report and pagination is in the PublisherReport class

An example - reporting

class ReportController { def print(PublisherReport report) { JasperRenderer.render report }

def json(PublisherReport report) { render report as JSON }

...}

Wednesday, 11 September 13

The controller is now very thinThe report can support parameters for sub-reports etc.The domain model is embodied in the command object

CQRS

Wednesday, 11 September 13

CQRS

ommandueryesponsibilityegregation

Wednesday, 11 September 13

The writes use a different model from the readsWill be coming back to this later

What is my domain?

Domain Model

HTTP Database

? ?

Wednesday, 11 September 13

Always ask yourself this question throughout life of projectAnd is it closer to the user’s perspective or the persistence model? Or neither?Former argues for a model based on command objects, the latter based on domain classes.

Post content

Wednesday, 11 September 13

The command model is very simple: author + post content + date

Wednesday, 11 September 13

Query model much more complexMultiple timelinesConversation threadsRetweets

Wednesday, 11 September 13

So working from your domain first is a good thingAnd remember that different contexts have potentially different views of the model, i.e. the user/client, persistence, other system componentsDDD doesn’t preclude the CRUD/service-based architectureSo what are the driving forces behind architecture beyond the model?

Rich clients

Wednesday, 11 September 13

We’re not talking Warren Buffet hereThings like GMail

Once upon a time...

Wednesday, 11 September 13

Flash

Wednesday, 11 September 13

Pretty (but often useless - or just pretty useless)

Java

Wednesday, 11 September 13

Remember applets?Liked the approach (particularly WebStart) but not often used. The browser was a delivery mechanism, not a platform

It’s all about the Javascript!

AngularJSKnockout.js

Backbone.js

Underscore.jsjQuery.js

Moustache

Wednesday, 11 September 13

The browser is now a platform for rich applicationsBut how do these impact the Grails app?The whole process of building a page on each request goes out the window

Google I/O 2012

Android activations to date

400 million

Apple WWDC 2012

iOS devices sold to date

365 million

Wednesday, 11 September 13

Let’s not forget Firefox OSLots of people potentially hitting a site at any one time!Typical Grails architecture may struggle to handle the load (OpenSessionInViewInterceptor, transactions, GSPs, thread-per-request)

An aside

If the whole Java client thing had worked out, would you use it for every web application you wrote?

Would you use it for Wikipedia?

Wednesday, 11 September 13

Before jumping onto the whole “single-page app” bandwagon, work out whether it’s appropriate for your app

Shared templates

HandlebarsViewResolver

or

<hbt:render template="..."/>

GSP

Wednesday, 11 September 13

Not much to talk about on client architecture, but template reuse is something to think aboutView resolver only makes sense if client-side templates are complete viewshbt is a fictitious tag namespace representing a plugin based on Handlebars for Java

AJAX + JSON endpoints

enabler for async

Wednesday, 11 September 13

Rich UIs don’t talk HTML - use JSON endpoints (aka “REST”)Asset delivery via Resources or asset-pipeline pluginsMore scope for asynchronicity, since no wait for full page updateGrails 2.3 introduces some nice features for REST

What’s the need for SiteMesh & GSP then?

Wednesday, 11 September 13

Difficult to impossible to remove these currentlyGrails 3 will finally extricate them, allowing you to remove them from your project

Aside 2

Don’t be afraid to use Ruby/Node.js tooling

Grunt

Bower

Yeoman

Compass/SASS

Wednesday, 11 September 13

If you go for a heavy Javascript UI, consider Ruby/Node.js toolingGenerally richer than Java-based tooling

Async

for scalability

Wednesday, 11 September 13

To solve the problem of dealing with large number of concurrent requestsWithout adding lots more servers

Grails Promise API

import static grails.async.Promises.*

class ReportController { def print(PublisherReport report) { task { // Expensive report creation here } } ...}

Wednesday, 11 September 13

We can now return Promise instances from actionsThe expensive task no longer blocks the request thread, but...

Controller

Request Thread Pool

Worker Thread Pool

HTTP Request

OffloadTask

Return thread

Wednesday, 11 September 13

The request threads are now free, but burden is on worker thread poolIf all worker tasks are synchronous, have we gained scalability?In cases where just a few URLs are blocking for long(ish) periods of time, yes (kind of)But otherwise, now bottleneck is on worker thread pool

Make efficient use of server resources

Wednesday, 11 September 13

Async all the way through - Grails Promises, GPars, messagingRemember that some things are inherently synchronous (think Fibonacci)

Grails app

ReportController

TagService PostService

Remote access

Wednesday, 11 September 13

NetFlix style model: coarse-grained, self-contained services/apps accessed from other appsUsually via REST

Async controllersimport static grails.async.Promises.*

class ReportController { def tagService def postService

def home() { tasks tags: tagService.tagsWithCount() trends: tagService.trendingTags() timeline: postService.timeline( params.userId) } ...}

Wednesday, 11 September 13

tagService and postService are both asyncThe model values are evaluated in parallelThis is a PromiseMap - view rendered only when all map values evaluated

Async controllersimport static grails.async.Promises.*

class TagService { def remoteTagService

def tagsWithCount() { task { remoteTagService.tagsWithCount() } } ...}

Wednesday, 11 September 13

You can also use @DelegateAsync to create async version of synchronous serviceCurrently not Grails’ sweet spot due to the solution’s lightweight nature......perhaps makes sense with Grails 3?

Rich domain model + Promises API/GPars?

Wednesday, 11 September 13

Fully async backendA good domain model makes it easy to identify parallelisable workNo simple solutions though! Concurrency is still a tricky problem.

GPars supports

• Dataflow

• Communicating Sequential Processes (CSP)

• Actor model

Wednesday, 11 September 13

Messaging

Wednesday, 11 September 13

A common solution to concurrency and scale

MyObject TheirObjectcall

Wednesday, 11 September 13

MyObject TheirObject

Router

Router

message

response response

message

Headers

Body

Wednesday, 11 September 13

Decoupling via messagesEncourages separation of concerns & responsibilities

MyObject OtherObject

Router

Router

messagemessage

responseresponse

Cloud

Wednesday, 11 September 13

Easy to change and move objectsScales well (think Actor model of concurrency)

Internal External

JMS

RabbitMQ

Events

Spring Integration

Apache Camel

Wednesday, 11 September 13

Internal and external can be integratedEvents is a special case of messaging (which I look at next)

Spring IntegrationMyController

MyService

message

DB Persister

SplitterJMS Twitter

A channel (pipe)

Message endpoint

Wednesday, 11 September 13

Based on Enterprise Integration Patterns (filters & pipes)Many options for routing and transforming messagesLogging adapters and wire tapping for debug

Spring Integration Groovy DSL

def builder = new IntegrationBuilder()

def ic = builder.doWithSpringIntegration { messageFlow("flow") { filter { it == "World" } transform(inputChannel: "transformerChannel") { "Hello " + it } handle { println "**** $it ****" } }}

ic.send "flow.inputChannel", "World"ic.send "transformerChannel", "Earth"

Wednesday, 11 September 13

Debugging

Code comprehension

Performance (kind of)

Wednesday, 11 September 13

Events

Wednesday, 11 September 13

Special case of messaging

Event bus - ApplicationContext

ApplicationContext

PluginService

publish

PluginUpdateService YourListener

GORM

Wednesday, 11 September 13

Event bus - ApplicationContextclass PluginService { def publish(PluginDetails info) { ... publishEvent(new PluginUpdateEvent(...)) }}

class PluginUpdateService implements ApplicationListener<PluginUpdateEvent> {

def onApplicationEvent(PluginUpdateEvent e) { ... }}

Wednesday, 11 September 13

with spring-events plugin

Immutable “mesages”

@groovy.transform.Immutableclass PluginUpdateEvent { String name String version String group ...}

Wednesday, 11 September 13

Event listeners on separate threads (from thread pool)

Event bus - (grails-)events

Event bus (Reactor)

PluginService

publish

PluginUpdateService YourListener

Wednesday, 11 September 13

Event bus - (grails-)events

Event bus (Reactor)

Browser

RabbitMQ (pending)

events-push plugin

Wednesday, 11 September 13

Event bus - (grails-)eventsclass PluginService { def publish(PluginDetails info) { ... event "pluginUpdate", info }}

class PluginUpdateService { @Selector def pluginUpdate(PluginDetails info) { ... }}

Wednesday, 11 September 13

with spring-events plugin

AppEvents.groovy

includes = ["push"]

doWithReactor = { reactor("grailsReactor") { ext "browser", ["pluginUpdate"] }}

Wednesday, 11 September 13

In grails-app/confCan control which events are propagated to the browserThe “push” include sets up a

Include grailsEvents.js

window.grailsEvents = new grails.Events(baseUrl)

grailsEvents.on("pluginUpdate", function(data) { // do something});

Wednesday, 11 September 13

A CQRS architecture

Updates

Views

Concurrency via event bus

Store changes

Separate data stores for queries

Wednesday, 11 September 13

Why? Updates and querying often have different data requirements.For example, Lanyrd use Redis structured data supportAll read databases can be rebuilt from master events DBCQRS designed for scale

Plugin ArchitecturesApp

Feature plugin 1

Feature plugin 2

Feature plugin 3

Events/SI/Message broker

Wednesday, 11 September 13

So why use messages to interact between the plugins?

Plugin ArchitecturesApp

Feature plugin 1

Feature plugin 2

Feature plugin 3

Events/SI/Message broker

App 2

Wednesday, 11 September 13

Easy to separate out into apps deployed independently

Ultimately, think about what you need...

Wednesday, 11 September 13

...don’t just go the “standard” route

automatically

Wednesday, 11 September 13

Thank you

Wednesday, 11 September 13

Recommended