@John: I'm using couchDb. Currently, my db-query function is just a wrapper 
around the "request" module. Perhaps I will use the nano module in the 
future.

@Manuel: For small projects, I prefer a central routing module, but for 
bigger projects you are right of course. Thanks for this hint and the mvc 
link.

@Eldar, @greelgorke: I think you are right that the async module shouldn't 
be used for simple functions. I will adjust my project code. The only thing 
that bothers me is the costly error handling

m.get(..., function(err, body) {
  if(err) { return next(err); }
    m.upate(..., function(err, model) {
      if(err) { return next(err); }
...

A lot of same "*if(err)*" lines. So, using the async module for 
_complicated_ (deep nested) cases keeps the code cleaner.

@All: Thanks for your responses, I will optimize my code :)




On Tuesday, October 16, 2012 1:58:28 PM UTC+2, greelgorke wrote:
>
> totally agree about nesting and async is a smell.
>
> Am Dienstag, 16. Oktober 2012 10:58:22 UTC+2 schrieb Eldar:
>>
>> But currently, it is important for me to use only standard (within the 
>>> meaning of established/mainstream) componentes and libraries (like e.g. 
>>> async) that every node programmer knows.
>>
>>
>> No one encourages you to use the-box as is. It's essentials just about 
>> 50-100 lines of code. Each app might want to have it's own optimized 
>> version. But the pattern "hey I want this and that" is very common. 
>> Delegating tasks of getting dependencies to well known infrastructure is 
>> what makes code more simple and declarative. Here we see an idea of 
>> separating control flow from business logic in action.    
>>
>> Can you please point out some more details why you don't find this code 
>>> clean/easy maintainable? And give me some example code for improvements?
>>
>>
>> Without concrete use case it's very hard to do. What you showed just has 
>> some smells for me, but that's all pretty subjective. For example using of 
>> async utils for sequential execution. They are adding their own noise both 
>> in code and runtime. I personally can tolerate up to 5 levels of nesting 
>> with 2 space tabs but can't remember when I had more than 3 levels. At the 
>> same time async.waterfall adds 2 levels just by default. Another thing is 
>>
>> function doSomething () {
>> var data = {}
>> function a () {}
>> function b () {} 
>> a()
>> b()
>> }
>>
>> or
>>
>> function doSomething () {
>> var data = {}
>> a(data)
>> b(data)
>> }
>>
>> function a (data) {
>> // body...
>> }
>>
>> function b (data) {
>> // body...
>> }
>>
>> probably it's better to create object-method for such use case
>>
>> function DoSomething (data) {
>> this.data = data
>> }
>>
>> DoSomething.prototype.a = function  () {
>> // body...
>> }
>>
>> DoSomething.prototype.b = function  () {
>> // body...
>> } 
>>   
>>
>> On Monday, October 15, 2012 11:18:37 AM UTC+4, Mil Werns wrote:
>>>
>>> @Tim:
>>> Thanks, positive feedback is always welcome :)
>>>
>>> @Eldar:
>>> the-box looks very interesting. I am curious to see what in this project 
>>> happens next. But currently, it is important for me to use only standard 
>>> (within the meaning of established/mainstream) componentes and libraries 
>>> (like e.g. async) that every node programmer knows.
>>>
>>> Can you please point out some more details why you don't find this code 
>>> clean/easy maintainable? And give me some example code for improvements?
>>>
>>>
>>> On Sunday, October 14, 2012 11:58:31 AM UTC+2, Mil Werns wrote:
>>>>
>>>> Hi,
>>>>
>>>> I want to implement a REST API with node.js and restify. Because it is 
>>>> my first node.js project, I spent some time to learn how to structure the 
>>>> application to avoid the callback hell. Please have a look to the 
>>>> following 
>>>> solution.
>>>>
>>>>
>>>> *Assumptions*
>>>> - This approach is for writing an end-user application, not a module or 
>>>> a driver in the sense of a public library.
>>>> - REST services are closely related to CRUD, so we have a lot of simple 
>>>> sequential/interdependent operations (get_data + validate_data [+ 
>>>> change_data] + save_data).
>>>>
>>>>
>>>> *Goals*
>>>> *===> Focus is writing maintainable, clean and easy to understand 
>>>> code! <===*
>>>> Conversely, this means...
>>>> - Performance is secondary (scaling horizontally if necessary).
>>>> - Avoid "low level" methods like process.nextTick(), instead use "high 
>>>> level" methods/libs like middleware and control flow modules.
>>>> - Use modules to implement a well known MVC structure.
>>>>
>>>>
>>>> *Code*
>>>> Please ignore the poor logging, the not very useful view tasks etc. in 
>>>> the following example. It should only demonstrate the MVC / callback 
>>>> structure.
>>>>
>>>>
>>>> */* server.js */*
>>>>
>>>> var restify = require("restify");
>>>> var router = require("./router");
>>>> ...
>>>> router.route(server);
>>>> server.listen(...);
>>>>
>>>>
>>>> */* router.js */*
>>>>
>>>> var userController = require("./controllers/user");
>>>> ... // more controllers
>>>>
>>>> exports.route = function route(server) {
>>>>   server.get("/users",
>>>>     [
>>>>       userController.checkRead,
>>>>       userController.read,
>>>>     ]
>>>>   );
>>>>   server.put("/users",
>>>>     [
>>>>       userController.checkUpdate,
>>>>       userController.update,
>>>>     ]
>>>>   );
>>>> ...
>>>> };
>>>>
>>>>
>>>> */* controllers/user.js */*
>>>>
>>>> var restify = require("restify");
>>>> var async = require("async");
>>>> var check = require('validator').check;
>>>> var sanitize = require('validator').sanitize;
>>>> var m = require("../models/user");
>>>> var v = require("../views/user");
>>>> ...
>>>>
>>>> var checkUpdate = function userCheckUpdate(req, res, next) {
>>>>   // validate user input
>>>>   try {
>>>>     check(req.params.id, "id").isInt();
>>>>     check(req.params.forename, "forename").len(1,50);
>>>>     check(req.params.surname, "surname").len(1,50);
>>>>     check(req.params.email, "email").isEmail();
>>>>   } catch(err) {
>>>>     if(err) {
>>>>       return next(new restify.InvalidArgumentError("Invalid 
>>>> arguments"));
>>>>     }
>>>>   }
>>>>   return next();
>>>> };
>>>>
>>>>
>>>> var update = function userUpdate(req, res, next) {
>>>>   // create model
>>>>   var model = {
>>>>     forename: sanitize(req.params.forename).xss(),
>>>>     surname: sanitize(req.params.surname).xss(),
>>>>     email: sanitize(req.params.email).xss(),
>>>>   };
>>>>
>>>>   //*** "sub-methods" part ***
>>>>
>>>>   // _example_ for data check
>>>>   function checkOwner(curData, callback) {
>>>>     if(curData.ownerId !== req.userId) {
>>>>       return callback(new 
>>>> restify.InvalidArgumentError("NotAuthorizedError"));
>>>>     }
>>>>     return callback(null, curData);
>>>>   };
>>>>
>>>>   // _example_ for simple model manipulation
>>>>   function addSimple(model, callback) {
>>>>     model.updateDate = new Date().getTime();
>>>>     return callback(null, model);
>>>>   };
>>>>
>>>>   // _example_ for complex model manipulation
>>>>   function addComplex(model, callback) {
>>>>     // use async again to simulate synchronous/parallel/... flow
>>>>     async.waterfall(
>>>>       [
>>>>         // do something
>>>>       ]
>>>>       function(err, model) {
>>>>         if(err) {
>>>>           return callback(new restify.InternalError());
>>>>         }
>>>>         return callback(null, model);
>>>>       }
>>>>     );
>>>>   };
>>>>
>>>>
>>>>   // *** main part ***
>>>>   // use async to simulate synchronous flow
>>>>   async.waterfall(
>>>>     [
>>>>       function(aNext) { m.get(req.params.id, aNext); },
>>>>       function(curData, aNext) { checkOwner(curData, aNext); },
>>>>       function(curData, aNext) { addSimple(model, aNext); },
>>>>       function(model, aNext) { addComplex(model, aNext); },
>>>>       function(model, aNext) { m.update(model, aNext); },
>>>>       function(model, aNext) { v.update(model, aNext); },
>>>>     ],
>>>>     function(err, model) {
>>>>       if(err) { return next(err); }
>>>>       res.send(200, model);
>>>>     }
>>>>   );
>>>>   return next();
>>>> };
>>>> ...
>>>>
>>>>
>>>> */* models/user.js */*
>>>> ...
>>>> var get = function userGet(id, callback) {
>>>>   var curData = ... // DB query for ID
>>>>   if(err) {
>>>>     console.log("DB error: " + err);
>>>>     return callback(new restify.InternalError());
>>>>   };
>>>>   return callback(null, curData);
>>>> };
>>>>
>>>> var update = ...
>>>> ...
>>>>
>>>>
>>>> */* views/user.js */*
>>>> ...
>>>> var update = function userUpdate(model, callback) {
>>>>   // adjust model for output
>>>>   delete model.updateDate;
>>>>   ...
>>>>   callback(null, model);
>>>> };
>>>>
>>>>
>>>> As you can see, this approach uses the middleware concept + async 
>>>> module to separate the application in small (MVC) pieces that are 
>>>> hopefully 
>>>> easy to understand.
>>>> But as I said before: I'm a node.js newbie :)
>>>>
>>>> *What do you think about this structure?
>>>> Are there any drawbacks?*
>>>>
>>>> Thanks a lot for your help
>>>> Mil
>>>>
>>>>
>>>>

-- 
Job Board: http://jobs.nodejs.org/
Posting guidelines: 
https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en

Reply via email to