OR Mapper / Migrating from sqlite to cblite [android]

58 views
Skip to first unread message

Sascha Lüdecke

unread,
May 12, 2014, 11:32:05 AM5/12/14
to mobile-c...@googlegroups.com

Hi everyone,

 

I want to migrate an Android application from SQLite to CBLite and I want to keep my business objects to avoid a major rewrite.

 

Right now the app uses ORMlite to map business objects to database tables and all UI and business logic code is built on top of the business objects.

 

I have not found any useable OR mapper for CBLite, am I missing something?

 

How do you work with data, do you all use the getProperties()->copy->edit->putProperties() approach as seen below to set individual properties?

 

Map<String, Object> curProperties = document.getProperties();

Map<String, Object> newProperties = new HashMap<String, Object>();
newProperties.putAll(curProperties);
boolean checked = ((Boolean) newProperties.get("check")).booleanValue();
newProperties.put("check", !checked);
document.putProperties(newProperties);

 

Best regards,

Sascha

signature.asc

Matt Quinn

unread,
May 12, 2014, 6:05:04 PM5/12/14
to mobile-c...@googlegroups.com
On Mon, May 12, 2014 at 05:32:05PM +0200, Sascha Lüdecke wrote:
> I have not found any useable OR mapper for CBLite, am I missing something?

I believe this is the most recent discussion on this topic:
https://groups.google.com/d/topic/mobile-couchbase/QmkamYMKwNU/discussion

My understanding is that the situation is the same now (i.e., there
is no supported ORM). The Couchbase folks would have to jump in about
where it might be in the roadmap.

> How do you work with data, do you all use the getProperties()->copy->edit-
> >putProperties() approach as seen below to set individual properties?
>
> Map<String, Object> curProperties = document.getProperties();
> Map<String, Object> newProperties = new HashMap<String, Object>();
> newProperties.putAll(curProperties);
> boolean checked = ((Boolean) newProperties.get("check")).booleanValue();
> newProperties.put("check", !checked);
> document.putProperties(newProperties);

I'm using what CBL gives you natively for saving changes, yeah. For new
docs, I do something similar to your code (but in that case there's no
map copying: just fill map->save). For updating documents, I use
Document.update() instead: it streamlines handling update conflicts, and
lets you bail on making the update if you see another change that
obsoletes it came in first.

In my experience using CBL without an ORM isn't too much of a pain, but
- our app has been designed around that, and
- our data model has changed enough over time that we have
ORM-unfriendly warts to handle anyway.
I totally understand the case for one though.

Sascha Lüdecke

unread,
May 13, 2014, 4:59:52 AM5/13/14
to mobile-c...@googlegroups.com

On Monday 12 May 2014 18:05:04 Matt Quinn wrote:

> I'm using what CBL gives you natively for saving changes, yeah. For new
> docs, I do something similar to your code (but in that case there's no
> map copying: just fill map->save). For updating documents, I use
> Document.update() instead: it streamlines handling update conflicts, and
> lets you bail on making the update if you see another change that
> obsoletes it came in first.

Do have a code sample you can share for using the Document.update()?

> In my experience using CBL without an ORM isn't too much of a pain, but
> - our app has been designed around that, and
> - our data model has changed enough over time that we have
> ORM-unfriendly warts to handle anyway.
> I totally understand the case for one though.

So far ORMlite saves me the whole interaction with the database and casting /
converting classes. Given the right annotations, I can simply call:

// GET
Date birthday = aUser.getBirthday();

// SET
aUser.setBirthday(new Date());

// PERSIST
dbHelper.save(aUser);

In CBL it would look like this (which I could wrap it into a static methods of
a class "User" just like the ToDoLite example does):

// GET
// toString() or a class cast?
String dateString = aDocument.getProperty(ATTRIBUTE_BIRTHDAY).toString();
DateFormat formatter = new SimpleDateFormat("d-MMM-yyyy,HH:mm:ss aaa");
Date birthday = formatter.parse(dateString);

// SET
Map<String, Object> curProperties = aDocument.getProperties();
Map<String, Object> newProperties = new HashMap<String, Object>();
newProperties.putAll(curProperties);
newProperties.put(ATTRIBUTE_BIRTHDAY, formatter.format(new Date()));
aDocument.putProperties(newProperties);

// PERSIST
// already done by using putProperties

Is that how you do it?

Regards,
Sascha
signature.asc

Matt Quinn

unread,
May 13, 2014, 9:37:28 AM5/13/14
to mobile-c...@googlegroups.com
On Tue, May 13, 2014 at 10:59:52AM +0200, Sascha Lüdecke wrote:
> Do have a code sample you can share for using the Document.update()?

Sure. For example, imagine an app that tracks a list of tasks, and the
user has marked a task complete.

public boolean completeTask(String taskId) {
Document doc = mDatabase.getExistingDocument(taskId);
SavedRevision rev = doc.update(new Document.DocumentUpdater() {
@Override
public boolean update(UnsavedRevision newRevision) {
Map<String, Object> props = newRevision.getProperties();
if (DocUtils.getBoolean(props, "completed")) {
// This task has already been marked completed by
// another user -- skip the update.
return false;
}
props.put("completed", true);
props.put("updatedBy", mUserId);
props.put("updatedAt", DocUtils.nowDateString());
return true;
}
});
return (rev != null);
}

(where DocUtils is a homegrown collection of helpers for stuff we have
to do all the time, like convert types into/out of documents).

Obviously, it's much longer than the ORMLite example, although it does a
bit more work:
- In the simplest case, it just updates the document.
- If we get an update conflict because another user marked the task
completed too, we can skip our update.
- Otherwise, if another user made some unrelated change, we'll go ahead
with our update to the "completed" property.

Usually, I've got one class that handles both DB -> domain object and
domain object -> DB translation for each type of document in the system,
so all the DB-related code for each type is in one place. It's not too
different from what ToDo-Lite is doing with static methods, but I prefer
to keep them instance methods on a second class as an easier seam for
testing. Just personal preference.

Matt Quinn

unread,
May 13, 2014, 9:51:41 AM5/13/14
to mobile-c...@googlegroups.com
On Mon, May 12, 2014 at 06:05:04PM -0400, Matt Quinn wrote:
> On Mon, May 12, 2014 at 05:32:05PM +0200, Sascha Lüdecke wrote:
> > I have not found any useable OR mapper for CBLite, am I missing something?
>
> I believe this is the most recent discussion on this topic:
> https://groups.google.com/d/topic/mobile-couchbase/QmkamYMKwNU/discussion

From that discussion, Traun wrote:
> I know Jackson has some object mapping features that will make your
> life easier converting from POJOs <-> JSON Documents, and that's
> already being included as a core dependency on Couchbase Lite.

Has anyone given this a try? Since the public CBL API works with maps, I
can only picture this working if you did two conversions every time (Map
-> JSON -> POJO for reads, POJO -> JSON -> Map for writes). Feels
expensive, but maybe it's okay? Or is there a way to skip the
intermediate conversions?

Sascha Lüdecke

unread,
May 13, 2014, 12:54:09 PM5/13/14
to mobile-c...@googlegroups.com

On Tuesday 13 May 2014 09:37:28 Matt Quinn wrote:
> Sure. For example, imagine an app that tracks a list of tasks, and the
> user has marked a task complete.
>
> public boolean completeTask(String taskId) {
> Document doc = mDatabase.getExistingDocument(taskId);
> SavedRevision rev = doc.update(new Document.DocumentUpdater() {
> @Override
> public boolean update(UnsavedRevision newRevision) {
> Map<String, Object> props = newRevision.getProperties();
> if (DocUtils.getBoolean(props, "completed")) {
> // This task has already been marked completed by
> // another user -- skip the update.
> return false;
> }
> props.put("completed", true);
> props.put("updatedBy", mUserId);
> props.put("updatedAt", DocUtils.nowDateString());
> return true;
> }
> });
> return (rev != null);
> }
>
> (where DocUtils is a homegrown collection of helpers for stuff we have
> to do all the time, like convert types into/out of documents).

Thank you very much!

Using something like your DocUtils his is exactly the conclusion I have come
to. I will move away from 'proper' domain / business objects in terms of
'static typing' and move to a 'dynamically typed' approach: using Document
objects all around and assuming that the content is what I expect. The
DocUtils will help me doing basic type conversions and the domain specific
classes - just like the static ones in ToDo-lite and your translation objects
- will help me keeping my code readable.

One last question arises: if I have a couple of fields changed, e.g. after the
user edited bis name and birthday and such on a UI screen - how do you avoid
creating revisions for every minor change? Or to put it otherwise: to you
have a pattern / best practice to collate changes while using the helper
classes?

Regads,
Sascha



Jens Alfke

unread,
May 13, 2014, 1:14:01 PM5/13/14
to mobile-c...@googlegroups.com
If someone’s interested in creating a Java model class, take a look at the implementation of the iOS/Mac CBLModel class. Some of it is Objective-C specific, but there’s also a lot of code that interacts with the Document and Database objects that should be applicable to other platforms.

—Jens

Matt Quinn

unread,
May 13, 2014, 1:48:55 PM5/13/14
to mobile-c...@googlegroups.com
On Tue, May 13, 2014 at 06:54:09PM +0200, Sascha Lüdecke wrote:
> Using something like your DocUtils his is exactly the conclusion I
> have come to. I will move away from 'proper' domain / business
> objects in terms of 'static typing' and move to a 'dynamically typed'
> approach: using Document objects all around and assuming that the
> content is what I expect. The DocUtils will help me doing basic type
> conversions and the domain specific classes - just like the static
> ones in ToDo-lite and your translation objects - will help me keeping
> my code readable.

Sorry, I might have been a bit unclear due to the example I chose, but I
actually still do use POJOs (domain objects or view models). For the
same example, in a screen that has a list of tasks, each with a checkbox
to mark them completed, we would have (somewhat simplified):

- an AsyncTaskLoader that queries a view and maps Document objects to
Task objects
- a ListAdapter that displays Task objects in a list
- a click handler on the checkbox that calls into
TaskDataStore.completeTask (the example method above)

So, as far as the activity is concerned, it's only working with Task
objects. The translation of Document -> Task and Task -> Document
happens at the edges. I try to limit the catch-all DocUtils class to
things that are common to all of our documents, like type conversion,
date parsing/formatting, provenance tracking conventions, etc.

> One last question arises: if I have a couple of fields changed, e.g.
> after the user edited bis name and birthday and such on a UI screen -
> how do you avoid creating revisions for every minor change? Or to put
> it otherwise: to you have a pattern / best practice to collate changes
> while using the helper classes?

Similar to the above, on a screen where you can edit a task, we would:

- get the task document, and map to a Task object
- populate the form using the Task's properties
- modify the Task's properties as the form's values change
- call out to e.g. TaskDataStore.save(mTask) in onPause

So a user may make many changes to the task document, but it only gets
persisted when they leave the screen (so only one revision gets
created). Of course you could trigger the save using an explicit "save"
button, or whatever makes sense for your app.

...Having said all that, I make no claims that I'm doing anything the
"right" way -- they're just the patterns I've settled into over time as
our app has grown. I don't want to discourage you from trying things out
however you see fit. (And, for simpler cases, I still skip all the type
conversion stuff and just use a plain Document, as you've suggested).

There isn't much out there by way of conventions for CBL Android yet,
and I'm glad to have discussions about how people are approaching coding
with it. So, don't be afraid to keep asking! Hopefully other CBLA users
chime in about what they're doing too :)
Reply all
Reply to author
Forward
0 new messages