Download - Application Architectures in Grails
Application architectures in GrailsPeter Ledbrook
e: [email protected]: @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