This projects is a starting foundation for developing, testing, and deploying an application using the following stack of technologies together:
- Vagrant (Virtual Machine management)
- Ansible (Machine configuration management)
- PostgreSQL (Database)
- Play2 - Scala (Web Framework)
- Slick (Database query/access library)
- play-slick (Play Framework integration plugin for Slick)
- Grunt (Javascript task runner) Assists the Play framework with the following:
- Javascript linting
- Less compilation and minification
- Javascript compilation and minification
- Static image compression
- Live reloading
- Javascript test running
- RequireJS (Modular JS development)
- AlmondJS (Shim loader for minifed requirejs files)
- AngularJS (Front-end JS framework)
- Bootstrap (Front-end CSS framework)
- Font Awesome (Vector icon font, replaces and extends Bootstrap's Glyphicons)
- Mocha (Javascript unit framework)
First things first, you'll need to install Virtual Box and Vagrant. Once those are finished, open a command prompt at the root of the PlayGround repository and execute:
vagrant up
vagrant ssh
cd /vagrant
play debug run
The application will be available on your dev machine at localhost:8080. Congratulations, you're ready to start programming.
Running play debug run
will open a remote debugger on port 9999 of the host machine.
Vagrant's file syncing does not broadcast file events when syncing changes from the host machine. This unfortunately
means that the play run
command will not notice the file changes and will not automagically update the running code.
You'll have to open a second ssh session into the guest machine (vagrant ssh
from another console) and touch
one
of the source files. (touch /vagrant/app/views/main.scala.html
should work)
Before diving head first into writing your own web application, you'll want to rebrand it from PlayGround
to
whatever you decide to name your project. You'll want to look in the following files and update them appropriately:
- /ansible/group_vars/all
(todo)
Refer to this post on Git Branching strategy
You may wish to read up on how to keep your tables and logic separated. Ideally, your logic for querying the database should be separated from how you use the results. Keeping all such querying code in one location simplifies any refactoring process, and is generally more maintainable.
###LESS is More###
While Play2 can handle LESS and JavaScript compilation, Grunt is much more flexible and capable in its range of abilities.
Simple plugins at project/GruntWatch.scala and porject/GruntTask.scala allow easy integration with sbt. This means
executing play run
will still live reload your less and javascript changes, and play test
or play stage
will still
invoke Grunt as needed.
###RequireJS and Minification### The PlayGround repo offers one opinion on how to set up your javascript files so that the following criteria are all met:
- A single "path.js" file is used for RequireJS path and shim configuration across Dev, Test, and Production
- In dev (ie, non-production mode), the application serves the original, un-minified javascript and css files.
- In production, the application serves a single concatenated, minified, and gzipped javascript file that has the RequireJS functionality built in (ie, no need to use RequireJS to load your script in production)
- In test, the normal paths and shim configs are usable without launching the entire javascript application
It is somewhat unfortunate that AngularJS and RequireJS both implement their own dependency management system. I believe proper integration between the two is on the roadmap for future AngularJS revisions, but that still leaves the conundrum of how to handle it now.
What I have found to be fairly effective is to have RequireJS modules create their own AngularJS module, and return the name of the AngularJS module. This eliminates the need for "magic names" that appear across RequireJS modules.
In a normal Play2! application, the configuration is stored in conf/application.conf
. However, we use ansible to
create this file from a template. Do not modify the application.conf directly, as any changes you make will be lost.
The correct way to alter the configuration is to update the template in ansible/templates/application.conf.j2
, then
reprovision vagrant (vagrant provision
) from your host machine or execute ansible-playbook ansible/site.yml -i ansible/vagrant --tags "configuration"
from the application root on the Vagrant VM.
###Package.json### Since npm and grunt are used only for frontend tasks, package.json has an unimportant name and version number. Additionally, it lists "download" as a dependency. Download is actually a dependency of another one of the required packages, but it was nested so deep it was causing file name length issues with Windows hosts. Listing it as a top level dependency fixed the problem.
You should definitely write unit tests for both your Scala and Javascript code.
To write JavaScript unit tests, add or modify a file in /test/js/specs
that ends in spec.js
. These files will be
pulled in via RequireJS in the test runner and executed. The test runner uses a combination of
Rhino, Jasmine, and
EnvJS to seamlessly execute your tests from the command line.
To debug these unit tests, you can execute sbt jasmine-gen-runner
or play jasmine-gen-runner
to generate a html
file you can load into a browser for debugging. Running jasmine-gen-runner
will create a file that works on the
guest machine if you execute the command from the guest machine, and will work on the host machine if you execute the
command from the host machine.
Running jasmine-gen-runner
from a Windows host machine will generate an html file that does not currently work. Feel
free to submit a pull request to my fork of the plugin on GitHub.
To run in production mode, use play start
.
(Alternatively: see Play's documentation on starting in
production mode)
Using ansible for deployment. The app
ansible role will install your app as a service in /etc/init.d. It will
also reuse the /opt/{{app-name}} directory across versions, so make sure any files you want to persist across version
deploys are stored elsewhere. (todo)
Why don't we have the actual production or staging inventory files checked into source control?
- The contents of these files could change independent of the functionality of your code, and tying these to source control will unnecessarily clutter your commit history
Okay, but why is the build
inventory file checked in? Couldn't I change the bots being used?
- Yes, but
build
will run directly on the build bot, usingansible_connection=local
. It doesn't need to know what machine(s) you're using for building, as you are already using them (This does imply a requirement that Ansible is a requirement on each build slave).
####Example Jenkins build and deployment process##### Step 1: Update version number
sed -i "s/-SNAPSHOT/.$BUILD_NUMBER/g" ansible/group_vars/all
Step 2: Ensure the build environment is correct.
ansible-playbook ansible/site.yml -i ansible/buildbot
Step 3: Test and package the application (Jenkins' text logging doesn't like the color format codes, so we disable it)
play -Dsbt.log.noformat=true clean compile test dist
Step 4: Create the inventory file
??? (This step depends on how you are managing your servers)
Step 5: Deploy the application
ansible-playmbook ansible/site.yml -i /path/to/inventory --extra-vars="app_secret=MyActualSecret db_pass=TheActualPassword"
The above deployment scenario means your secrets are available on the build machine, but are not included in source control. If you wish to use a different method for managing secrets, you can use any of Ansible's mechanics for separating out variables.
- Investigate SSL best practices for nginx and ansible deployment
- Better security configurations on production machines (SSH configuration, sudo'ers list, etc)
sbt-jasmine-plugin
should attempt to use therequire.js
that Play2 uses internally, instead of including it manually- Investigate performance impact of synchronous JDBC drivers and PostgreSQL vs ansyncrhonous drivers and MongoDB.
- Investigate LiquiBase for managing database evolutions, instead of relying on applyEvoloutions.default=true in the init.d script for evolutions
- Investigate Datomic/Datomisca for data modeling purposes
- Investigate a smoother Jenkins master/slave and Ansible integration, so that Ansible is not a requirement on each build slave.
- Try to port the javascript router to the static compilation
- Round robin deploy with no downtime
Code provided here is licensed under the MIT License
All included libraries are redistributed in accordance with their licenses.