Showing posts with label CouchDB. Show all posts
Showing posts with label CouchDB. Show all posts

Thursday, October 10, 2013

JsonMapper application for CouchDB HTTP data handling

There is a neat application of JsonMapper for CouchDB HTTP data conversion. Couch DB is based on json data. So it is good application area to apply jsonMapper between entity object and serialized object. (Here we see an interesting combination of generic type. Namely by defining generic classes, we can create a flow to pass the entity type to the 'doc' field type at runtime. )

In order to extract entities list from json HTTP output, and do some none standard plumbing of attributes(id must be mapped from _id, rev is from _rev), we can use a single fromJson method.

we define following generic class:

class CouchDBFetchData<T> {
  List<CouchDBFetchRowData<T>> _rows;
  
  List<CouchDBFetchRowData<T>> get rows => _rows;
  void set rows( List<CouchDBFetchRowData<T>> rows) { _rows = rows; }
}

class CouchDBFetchRowData<T> {
  T _doc;
  T get doc => _doc;
  void set doc(T doc) { _doc = doc; }
}

This class does not define all json properties, but they are ignored when json is mapped to the entity object.
then we can apply fromJson to the json output. This is the all things we need to get the list of entities. But unfortunately, right now, since there is a bug (as mentioned in previous post), this will fails. but if it is fixed, this should work.

  Future> getAll() {
    return _getData("/$dbName/_all_docs?include_docs=true")
        .then( (json) {
          if (json != "") {
            CouchDBFetchData<T> data = jsonMapper.fromJson(
              new CouchDBFetchData<T>().runtimeType, json, attr_redirect_map: {'id':'_id', 'rev':'_rev'});
            return data.rows.fold([], (list, CouchDBFetchRowData<T> row)=>list..add(row.doc));
          } else {
            return new List();
          }
        }).catchError((_)=>[]);
  }

here is the original inefficient, inelegant approach.

  Future> getAll() {
    return _getData("/$dbName/_all_docs?include_docs=true")
        .then( (json) {
          if (json != "") {
            Map data = parse(json);
            for (var rowData in data["rows"]) {
              Map map = rowData["doc"];
              map['id'] = map['_id'];
              map.remove('_id');
              map['rev'] = map['_rev'];
              map.remove('_rev');
              String json1 = stringify(map); // ugry
              T t = jsonMapper.fromJson(modelType, json1);
              ts.add(t);
          } else {
            return new List();
          }
        }).catchError((_)=>[]);
  }

Tuesday, October 1, 2013

Generic CouchDBDAO class

I cleaned up the CouchDB handling code which essentially provides a http proxy for CouchDB. since CouchDB itself is providing Restful API, this may be seen as unnecessary duplication.



But this is useful to avoid browser restriction(although there a some other techniques in Dart), and this can be used as a template to provide a restful API for other db.

One interesting aspect of current implementation is such DAO class is designed as generic class.Compared to Hibernate/JPA, this approach is very simple.

[web client]
ICouchDbClientDAO<Expense> dao = new CouchDbClientDAO<Expense>(fromMap, fromJson, toJson);

    loadButtom = new ButtonComp(this, "Load", (_) {
      dao.fetchAll().then((List<Expense> es){ table.load(es);});
    });

    newButtom = new ButtonComp(this, "New", (_) {
      Expense e = form.create();
    });
   
    saveButtom = new ButtonComp(this, "Save", (_) {
      if (form.e != null) {
        Expense e = form.save();
        // this part is tricky..
        // since e is fill from InputText, when it is an empty text, it will assign "" instead of null..
        if (e.id == "") e.id = null;
        if (e.rev == "") e.rev = null;
        ((e.id == null)?dao.insert(e):dao.update(e)).then((Expense e0){
          e.id = e0.id;
          e.rev = e0.rev;
          table.addOrUpdate(e);
        });
      }
    });
   
    deleteButtom = new ButtonComp(this, "Delete", (_) {
      Expense e = form.e;
      if (e != null) {
        dao.delete(e).then((ok){
          if (ok) {
            form.delete();
            table.delete(e);
          }
        });
      }
    });
Here, the argument of CouchDbClientDAO's constructor takes fromMap, fromJson, toJson. This is required since I have not provided Json binding library yet, these functions must be defined manually for each entity classes(e.g, Expense).

[server]
void main() {

  // register reflection factory
  initClassMirrorFactory();

  CouchDbDAO<Expense> couchDbDAO = new CouchDbDAO<Expense>(couchdb_host, couchdb_port, dbName, fromMap, toJson);
 
  CouchDbHttpServer server = new CouchDbHttpServer(couchDbDAO, fromJson, toJson);
 
  server.startHttpServer(http_server_host, http_server_port);
}
No other code are required to make such entity class persistent.