overture noun
- an orchestral piece at the beginning of an opera, suite, play, oratorio, or other extended composition.
- an introduction to something more substantial
Overture is the start of something, an introduction. So this gem provides an introduction to your new application (or service). It helps you start developing your application so you can build a full orchestra eventually.
Install it
If you're starting from scratch, you can install the gem manually by running:
$ gem install overture
If you have an existing app and you want to use Overture, you can add it to your Gemfile:
gem 'overture'
What it can do?
Title says can because you can choose what you need from Overture. You don't need to use everything. If you only need a database connection adapter, just pull that. Or if you need a basic Sinatra configuration for a JSON API, just use that.
Overture is a set of tools or modules that you can use independently. All modules can interplay together as well, and they'll let you get something working pretty quickly.
In most cases you only need the hammer to nail a nail.
Creating an app
After you install the gem, you can run this command to generate an app directory layout:
$ overture generate app my-app
The last argument is the name of the folder you want to use for your app. This will generate a directory structure like the following:
├── Gemfile
├── Gemfile.lock
├── Procfile
├── Rakefile
├── bin
│ ├── console
│ └── dev_http_api
├── config
│ ├── environment.rb
│ └── puma.rb
├── config.ru
└── lib
├── http_api
│ ├── endpoints.rb
│ └── server.rb
├── interactors
└── models
This command will create all necessary files to create a full application. This means you'll have three layers:
Application logic
All logic that is not specific to your domain (business), is handled here. In other words, this is where all your models (entities) live. Specifically in the lib/models
directory.
Business logic
All the logic that is specific to your domain (business) goes here. This layer is handled by Interactors, also known as Use Cases. These classes are created in the lib/interactors
directory.
Presentation layer
This is basically the output of your application that is used by other applications or services. This layer is managed by a Sinatra app that accepts and responds to JSON HTTP endpoints. The endpoints are defined in the lib/http_api/endpoints.rb
file. You can also organize your endpoints in different files/classes, preferably subclasses of the HttpApi::Server
class defined in the lib/http_api/server.rb
file.
All other files outside of lib
are practically configuration files. You can see what they do by opening them.
Once you create your app, you can run bin/dev_http_api
to start up a Rack server. This will run in the port 3000 by default but you can change it in the config/puma.rb
file or with the PORT
env var.
Configuring via environment variables
Overture uses dotenv to load environment variables defined in the .env
file of your project.
You should NOT version your
.env
file. Instead you can add a.env.example
file with dummy values.
Connecting to a database
You can use Overture::Database
to create a connection to a database using Sequel.
Once you've generated your app structure, in your config/environment.rb
file uncomment the line that requires the database module, and the one that creates the connection instance:
require 'overture/database'
DB = Overture::Database.connect(ENV.fetch('DATABASE_URL'))
As you can see, it uses the environment variable DATABASE_URL
. You can define it in your .env
file (see Configuring via environment variables section).
Model the datasets
You can map your database tables to Ruby classes that inherit the Overture::Model
class.
For example if you have a users
table, you can create a User
model like this:
class User < Overture::Model
end
This will automatically map to the users
table following the Sequel convention for the tables naming as described here.
You now can persist and retrieve records in the users
table:
User.create(first_name: 'John')
user = User.first(first_name: 'John')
user.update(first_name: 'Juan')
Refer to the Sequel documentation to see what methods are available.
Version your schema with migrations
To create the tables in your database you can do it manually or use migration files that will also help you revert to a specific version if needed.
In order to create a new migration file you run:
$ overture generate migration users
This will create a file under the migrations
folder. Open it and change to your needs. See the Sequel migration documentation to know what you can do.
Implement your business logic
As mentioned above, your business logic lis handled by interactors. An interactor is a subclass of the Interactor class. with some methods added for convenience. See the Overture::Interactor
class to find what methods are added.
Digest and validate passwords
Overture comes with a password digester module that helps you digest and validate passwords.
To use it, include it in your class and call digest_password(password)
or valid_password?(password_digest, plain_password)
methods to digest or validate a password respectively.
For example:
require 'overture/password_digester'
class AuthenticateUser < Overture::Interactor
include Overture::PasswordDigester
required :email, :password
def call
user = User.first(email: context.email)
fail!('user does not exist' unless user
context.authenticated = valid_password?(user.password_digest, context.password)
end
end
Communicate with the exterior
The Overture::HttpApi::Server
class is a Sinatra app configured to accept and respond to JSON.
It includes the Overture::HttpApi::Helpers
module too, which provides some common helpers.
When you generate an app with Overture, it creates a subclass of Overture::HttpApi::Server
that you can configure to your needs.
You can define your endpoints in the HttpApi::Endpoints
class. Or organize them in subclasses of HttpApi::Server
that you can mount in the main HttpApi::Endpoints
class.
Serializing your models
When you have a model class (i.e. User
), and you want to present it as a JSON. You can use the json()
helper method and it will automatically try to find a corresponding serializer for that class based on its name plus the Serializer
suffix. In this case it'd be UserSerializer
.
The Overture::Serializer
produces JSON:API object. And it's based in the Netflix/fast_jsonapi serializer. So as long as you conform with its interface, you can create your own too.
If you have a serializer that does not conforms to the naming convention, you can specify what serializer to use in the json()
helper method:
get '/admin/users' do
users = User.all
json users, serializer: AdminUserSerializer
end
As mentioned in the beginning, you don't need to use all these components. You could for example only use the Overture::Interactor
class, or the Overture::PasswordDigester
module in your application.