POST request flow
Hi at all, I have some problems to understand the flow for a POST request. Please, can you help me? :) This is a example:
-module(esh_rest_api_data).
-export([
init/1,
allowed_methods/2,
content_types_accepted/2,
create_path/2,
from_json/2,
post_is_create/2
]).
-include_lib("webmachine/include/webmachine.hrl").
% @doc Initialize the resource
-spec init(list()) -> {ok, term()}.
init([]) ->
{ok, undefined}.
% @doc Support retrieval and creation of data
allowed_methods(ReqData, Context) ->
{['POST'], ReqData,Context}.
% @doc Allow POST request to create data
post_is_create(ReqData, Context) ->
{true, ReqData, Context}.
% @doc Accept only application/json content.
content_types_accepted(ReqData, Context) ->
{[{"application/json", from_json}], ReqData, Context}.
% @doc Attempt to create the data if possible.
create_path(ReqData, Context) ->
% NOTE: simulate a creation ???
Resource = "/api/data/" ++ integer_to_list(1),
{Resource, ReqData, Context}.
from_json(ReqData, _Context) ->
Data = mochijson2:decode(wrq:req_body(ReqData)),
DataObject = mochijson2:encode({struct, [{data, Data}]}),
myfunction:save_in_db(DataObject),
{true, ReqData, Context}.
The flow seems to be:
post_is_create => create_path => content_types_accepted => from_json
This is a POST, that it means that I want create a new resource. But the resource is readed by the body after the creation_path function.
My question is: How can I generate the correct path (and know the id of the resource) without save it before in the db (with from_json)?
Thanks :D
Please, can you help me?
If you want to pass around an ID you assign in create_path that should go into the Context (or State) that's passed around in the various functions.
For example, you could have something like:
-record(state, { next_id = 0 }).
init([]) ->
NextId = get_id_from_databass(),
{ok, #state{ next_id = NextId }}.
%...
create_path(ReqData, Context = #state{ next_id = NextId }) ->
{"/api/data/" ++ integer_to_list(NextId), ReqData, Context}.
@mrallen1 thanks for your reply! I'll try! :D
I didn't understand that init() is called for each request! :)
@mrallen1 Normally, I pass the data to the db that it'll store it and generate the ID for me in one single step. If I understand correctly, with this solution, I need to make 2 calls:
- reserve the id (without know if the client request generate a new store call in the db)
- save the data
Does exist a solution to make a single query to the db?
@hachreak It's also very typical -- if your application does not require sequential identifiers -- to use UUIDs or otherwise random-enough numbers. However, reserving the ID is usually no problem, especially for PostgreSQL.
For an example, see https://github.com/basho/giddyup/blob/erlang/src/giddyup_sql.erl#L107-L109 Even if you don't end up committing that record, pgsql will never give that ID twice.
Here's an example usage from within a resource: https://github.com/basho/giddyup/blob/erlang/src/giddyup_wm_test_result.erl#L38-L44
Sure you can make a single call to your database if you don't rely on the database to assign the ID to your resource. In that case, you'd need a UUID or some other "id provisioning" service to generate one for you.
I find this frustrating. I was interested in webmachine because it seemed like a way to build a web app by answering a simple series of questions in callbacks. However, due to the order that these callbacks are called, this becomes unnecessarily awkward.
Couldn't webmachine be factored differently to call create_path at the end instead of the beginning? At the end you will have fetched enough context to be able to determine the path of the item that was just created. Before that, you may not know if you're even creating something. What if it's an upsert?
call create_path at the end instead of the beginning?
I'm just trying to clean up a bit around here … but I really like this question, so I'm going to bloviate for a moment. I'm not going to say that such a refactoring couldn't happen, but maybe I can explain why it hasn't happened yet.
In short, we haven't attempted to refactor where create_path is called, because we believe resource module functions should be written in such a way that it doesn't matter when they're called (or how many times, for that matter). We call this referential transparency. Given the request and the application context, the function should be able to answer the question, regardless of whether or not other module functions were called first. If you want to get even more strict about it, given the same request and the same application context, we believe the function should produce the same answer.
We moved in that direction because we were excited about things like property-based testing. It's much harder to define the properties of a whole resource than the properties of a single function, so we did what we could to enable testing of functions, to verify details underneath more general whole-resource tests.
The structure of our more complicated modules usually ended up with a second set of functions wrapping computing and caching resource details. Like Sean and Jade mentioned, this might mean writing a function like the following, so that any function could use the generated path without worrying whether create_path had been called.
generated_path(#context{gen_path=undefined}=Ctx) ->
Path = "/foo/"++generate_uuid(),
{Path, Ctx#context{gen_path=Path}};
generated_path(Ctx) ->
{Ctx#context.gen_path, Ctx}.
I remember it being a real "aha moment" for me, when I shifted from thinking about the order of resource calls that would get me to a particular response, to thinking about answering questions about my resource, and letting Webmachine decide what the response should be. It's not a perspective that always fits with the practicalities of app development, but like other models, it points toward a sort of ideal that can help limit where the complexity of special cases creeps in, in my experience.
(Closing this for now, but not opposed to hearing future discussion about relocation of some resource calls.)