-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
AaltoApps runs in Amazon EC2 cloud server instance running Ubuntu Linux 11.10. The server instance uses EBS data storage for the files. Usually an EC2 instance loses all it's data when shut down with no disk space. EBS storage costs a bit more but is required to secure the data (logs, application files, configurations) if the instance is shut down. The project is written using Ruby language and Ruby on Rails web framework. The Ubuntu instance is modified by installing a HTTP server (Nginx), Rails application server (Passenger), Ruby interpreter (ruby 1.9.2), Rails framework (Rails 3.1.1), a database server (PostgreSQL 9.1) and a set of required libraries. Database and application files are saved in a separate EBS file system for easier backup possibilities and to be able to run the database in a separate server in the future.
The login process and user data is handled by ASI service running outside the main application. AaltoApps uses REST (Representational State Transfer) calls to ASI service for authentication and saves only the ASI id in it's own database, so the software can link user to his/her projects, comments and ratings.
The overall high level architecture is described in the following image:
The server has a static IP address and it receives HTTP and HTTPS requests from the client. The server uses Nginx web server as a HTTP(S) server which translates the requests for Passenger application server. Passenger handles the Rails instances which can handle only one request at a time. If all the application stacks are busy Passenger launches more instances.
The Rails stack is almost a pure MVC (model-view-controller) application. The requests are captured by a router which breaks them for controllers. E.g. http://domain.com/products/delete/123 routes by default to a controller named "ProductsController" and to an action method named "Delete". The last number represents an id, so the call deletes a product with an id "123".
The controllers are backed up by models which are saved to a database storage. A model can be e.g. a user, a product or something more abstract like a platform or category. Ruby on Rails uses an ORM (object-relational mapper) called ActiveRecord and it transforms the database data and relations to objects to be used by the controllers and views. So the controllers gather and prepare the models as instance variables which are shown in the views.
The views are for representing the data. In AaltoApps the views are mainly HTML5 and CSS3, although they can be anything like JavaScript or HAML syntax. A view gets it's data variables from controllers and render it for the user.
So in summary, user gets an access to the controllers. Controllers get the data as models and display it in views.
The insides of an EC2 instance:
AaltoApps contains the following controllers:
All the controllers inherit the ApplicationController. Contains the getter and setter for current_user and authentication filters to be used in all controllers.
Lists, shows, creates, updates and deletes products added by users. Ability to request approval and approve or block products.
GET: /products?parameters
HTTP GET parameters:
- page (for paging the products, handled automatically by views)
- platform (id of a platform, filters the products)
- tags (comma-separated list of tags, filters the products)
- sort (how the products should be sorted: popularity, created_at, avg_rating, featured)
- q (query search string for product name)
- myapps (show user's own applications)
- approval (show applications with specified approval status (pending, published, blocked)
GET: /products/new
POST: /products
Required HTTP POST parameters:
- category
- name (length > 3 characters)
- description
- url (length > 12 characters)
- platforms
GET: /products/ID, ID = product id as an integer
GET: /products/ID/edit, ID = product id as an integer
PUT: /products/ID, ID = product id as an integer
DELETE: /products/ID, ID = product id as an integer
GET: /products/ID/promote, ID = product id as an integer
GET: /products/ID/demote, ID = product id as an integer
GET: /products/ID/approve, ID = product id as an integer
GET: /products/ID/block, ID = product id as an integer
GET: /products/ID/request_approval, ID = product id as an integer
GET: /products/autocomplete_tags?term=PREFIX, PREFIX = tag prefix
Returns a JSON list of tag names matching the given prefix.
Deletes downloads.
DELETE: /products/P_ID/downloads/DL_ID, P_ID = product id as an integer, DL_ID = download id as an integer
Lists, shows, creates, updates and deletes users in the system.
GET: /users
GET: /users/ID, ID = user id as an integer
GET: /users/new
POST: /users
Notable parameters:
- user[term] should be 1 (accept terms and conditions)
GET: /users/ID/edit, ID = user id as an integer
PUT: /users/ID, ID = user id as an integer
DELETE: /users/ID, ID = user id as an integer
Login and logout.
GET: /sessions
POST: /sessions
HTTP POST parameters:
- username
- password
DELETE: /sessions
Product rating.
POST: /product/ID/ratings, ID = id of a product as an integer
HTTP POST parameters:
- rating (0..5)
Lists, shows, creates, updates and deletes platforms in the system.
GET: /platforms
GET: /platforms/new
GET: /platforms/ID, ID = id of a platform as an integer
GET: /platforms/ID/edit, ID = id of a platform as an integer
POST: /platforms
HTTP POST parameters:
- name
- image_url
PUT: /platforms/ID, ID = id of a platform as an integer
HTTP PUT parameters:
- name
- image_url
DELETE: /platforms/ID, ID = id of a platform as an integer
Lists, shows, creates, updates and deletes categories in the system.
GET: /categories
GET: /categories/new
GET: /categories/ID, ID = id of a category as an integer
GET: /categories/ID/edit, ID = id of a category as an integer
POST: /categories
HTTP POST parameters:
- name
- image_url
PUT: /categories/ID, ID = id of a category as an integer
HTTP PUT parameters:
- name
- image_url
DELETE: /categories/ID, ID = id of a category as an integer
Creates comments in the system.
POST: /products/ID/comments, ID = id of a product as an integer
HTTP POST parameters:
- body
PUT: /comments/ID, ID = id of the comment
HTTP PUT parameters:
- body
DELETE: /comments/ID, ID = id of the comment
Shows static or semi-static pages. The pages are rendered from template files in the controller's view directory.
GET: /pages/ID, ID = page id (template file name without path and extensions)
Models are saved and read from the database through ActiveRecord object-relational mapper. The validations and relations are handled and abstracted in the ActiveRecord stack instead of the database. It is easy to change the database adapter to a NoSQL one from relational database with this approach.
The database schema for the models is created through migrations. Every time we create a model we also create a migration which maps the model attributes to the database, adds indexes etc.
AaltoApps has the following models:
Has one:
- Publisher (User)
- Category
Has many:
- Ratings
- Comments
- Platforms
- Downloads
- Tags (using ActsAsTaggableOn)
Validates:
- The presence of name, description, url and category
- The length of name is at least 3 characters
- The length of url is at least 12 characters
- Approval state in (pending, published, blocked)
- Photo attachment size is under 5 megabytes
- Photo content type is jpeg or png image
Has many:
- Products
Validates:
- The presence and uniqueness of name
- The length of name is at least 3 characters
Has many:
- Products
Validates:
- The presence and uniqueness of name
- The length of name is at least 3 characters
Has many:
- Ratings
- Published (Products)
- Comments
Validates:
- The presence of ASI ID
Has one:
- User
- Product
Validates:
- The presence of rating, user and product
- The rating is a float between 1 and 5, step is 0.5
Has one:
- Product
- Commenter (User)
Validates:
- The presence of commenter and product
- The length of message body is at least 3 characters.
Has one:
- Product
Validates:
- The presence of attached file and title
Handles the communication with ASI server API. Stores the cookie and handles the UserConnections. Login and logout.
The actual REST calls are sent from here.
Defines helper functions for processing Markdown-formatted fields in models.
CanCan library's helper model for user abilities.
The views are generated and handled by the controllers. Each action usually has their own view template. The view is constructed from a layout template with a header and a footer. Every GET action renders a template to the middle part of the layout.
It is not recommended to repeat yourself in the views. Some repeating stuff should be handled as a partials, which are quite like sub-templates that get parameters and render stuff to the actual templates.
Sometimes we need helper functions in the views. Those should be written as helpers which are only available in the views.