Creating a RESTful Service in Chicago Boss

Here’s an end-to-end REST implementation in Chicago Boss. Not sure has anyone a nice concise guide out there so hope this is useful.

Overview

A very simple model (Book[title, author, insertTimestamp, updateTimestamp])

CRUD implementation where

  •             Create is a POST request
  •             Read is a GET request
  •             Update is a PUT request
  •             Delete is a DELETE request

Focus on the service approach, use a WS client (eg. SoapUI) to test

Use local mysql for simplicity

Assumptions

Erlang is installed (I’m using R16B01)

Access to a mysql server where you can create a schema and user. I have one running locally

DDL

As mentioned, we have a very simple model. A book has two attributes, which are user driven, and two meta attributes which the service will manage.


CREATE DATABASE IF NOT EXISTS `bookdb` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `bookdb`;
DROP TABLE IF EXISTS `books`;
--
-- Author: jason o riordan Database: bookdb
-- Date: 10/15/13
-- File: books_ddl.sql
CREATE TABLE `books` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`book_name` varchar(128) DEFAULT NULL,
`author_name` varchar(128) DEFAULT NULL,
`insert_timestamp` timestamp DEFAULT '0000-00-00 00:00:00',
`update_timestamp` timestamp DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

Project Creation

Create a new ChicagoBoss project as follows

git clone https://github.com/evanmiller/ChicagoBoss.git
cd ChicagoBoss

I added the following line to rebar.config dependency section in order to include the cb_admin project

{cb_admin, “.*”, {git, “git://github.com/evanmiller/cb_admin.git”, “HEAD”}}

Now, pull down all ChicagoBoss’s dependencies and create out new project

./rebar get-deps
make app PROJECT=bookRestCatalogue

In the bookRestCatalogue dir I edited the boss.config file – boss.config

Note: port  is set to 8004, mysql connection details are set in db section

Model

Very simple model as outlined in the ddl

%% %%
%% @title book.erl
%% @author jasonoriordan
%% Created on 07. Oct 2013 9:48 AM
%% %%
%% @doc book.erl is a module that represents book model
%% @end
-module(book,[Id, BookName, AuthorName, InsertTimestamp::datetime(), UpdateTimestamp::datetime()]).
-author("jasonoriordan").
-compile(export_all).
%%
%% @doc Validate input, no empty fields are allowed in system
%% @end
validation_tests() ->
[
%% BookName Required
{fun() ->
BookName =/= undefined andalso
length(BookName) =/= 0
end, {book_name, "Book Name required"}},
%% AuthorName Required
{fun() ->
AuthorName =/= undefined andalso
length(AuthorName) =/= 0
end, {author_name, "Author Name required"}}
].

The model and ddl can be verified by hitting cb_admin and going to the model section

Cb admin model section showing Book

REST Contract/Controller

As mentioned we are going to have do CRUD actions on the book entity by have four services exposed. The logic is contained in a controller class, relevant extracts are explained below and full file is available on github

Create – HTTP POST

Pass the bookname and author name as URI arguments


%% Book CREATE
%%
%% Create a new carrier based on the passed params eg. book/save?authorname=jk&bookname=hp
save('POST', []) ->
AuthorName = Req:post_param("authorname"),
BookName = Req:post_param("bookname"),
NewBook = carrier:new(id,AuthorName, BookName, calendar:universal_time(),calendar:universal_time()),
% have record and return the newly created if ok or errors otherwise, in json format
case NewBook:save() of
{ok, SavedBook} ->
{json, SavedBook};
{error, Errors} ->
{json, Errors}
end.

Read – HTTP GET

Read the book names by passing the id

%% BOOK Read
%%
%% Read the bookId from the url eg. book/view/book-7
view('GET', [BookId]) ->
error_logger:info_msg("GET-book/view/~p request recieved~n",[BookId]),
Book = boss_db:find(BookId),
case Book of
[] ->
{output,<<"[]">>,[{"Content-Type","application/json"}]};
_Else ->
{json, Book}
end.

Read all carriers by hitting view_all

%% BOOK Read ALL
%%
%% Return all eg. book/view_all
view_all('GET', []) ->
error_logger:info_msg("GET-book/view_all request revieved~n"),
Books = boss_db:find(book, []),
case Books of
[] ->
{output,<<"[]">>,[{"Content-Type","application/json"}]};
_Else ->
{json, Books}
end.

Update – HTTP PUT

Update the book by passing the  bookname and authorname as URI arguments. Also updates the update_timestamp


%% Book Update
%%
%% Note: can also update by populating id with an actual value and calling 'new'
%% UpdatedBook = book:new(BookName,AuthorName, calendar:universal_time(),calendar:universal_time()),
%% Updates the book based on the passed params eg. book/update?bookid=myid&bookname=cuckoo&authorname=jk
update('PUT', []) ->
BookId = Req:post_param("bookid"),
BookName = Req:post_param("bookname"),
AuthorName = Req:post_param("authorname"),
Book = boss_db:find(BookId),
% update via attribute setting (as we don't want to update create_timestamp)
BookWithUpdatedName = Book:set(book_name,BookName),
BookWithUpdatedAuthor = BookWithUpdatedName:set(author_name,AuthorName),
BookWithUpdateTS = BookWithUpdatedAuthor:set(update_timestamp,calendar:universal_time()),
case BookWithUpdateTS:save() of
{ok, UpdatedBook} ->
{json, UpdatedBook};
{error, Errors} ->
{json, Errors}
end.

Delete – HTTP DELETE

Delete the book entity by passing

%% Book DELETE
%%
%% Delete the specified book from the system eg. book/delete/book-7
delete('DELETE', [BookId]) ->
boss_db:delete(BookId),
case boss_db:counter(BookId) of
0 ->
{output,<<"{ \"Deleted Status\": \"Book Deleted\" }">>,[{"Content-Type","application/json"}]};
_Else ->
{output,<<"error">>,[{"Content-Type","application/json"}]}
end.

Testing

Focus of this post is on service level so have added a SoapUI testsuite in order to test functionality

SoapUI test file is available from Git

Note: be sure to set attributes to Method level for create and update as per screenshot

SoapUI request showing configuration for Method level attributes

 

Resources