Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I work on a large Django application that uses CouchDB as a database and couchdbkit for mapping CouchDB documents to objects in Python, similar to Django's default ORM. It has dozens of model classes and a hundred or two CouchDB views.

The application allows users to register a "domain", which gives them a unique URL containing the domain name that gives them access to a project whose data has no overlap with the data of other domains. Each document that is part of a domain has its domain property set to that domain's name.

As far as relationships between the documents go, all domains are effectively mutually exclusive subsets of the data, except for a few edge cases (some users can be members of more than one domain, and there are some administrative reports that include all domains, etc.).

The code is full of explicit references to the domain name, and I'm wondering if it would be worth the added complexity to abstract this out. I'd also like to know if there's a name for the sort of bound property approach I'm taking here.

Basically, I have something like this in mind:

Before

in models.py

class User(Document):
    domain = StringProperty()


class Group(Document):
    domain = StringProperty()
    name = StringProperty()
    user_ids = StringListProperty()

    # method that returns related document set
    def users(self):
        return [User.get(id) for id in self.user_ids]

    # method that queries a couch view optimized for a specific lookup
    @classmethod
    def by_name(cls, domain, name):
        # the view method is provided by couchdbkit and handles
        # wrapping json CouchDB results as Python objects, and 
        # can take various parameters modifying behavior
        return cls.view('groups/by_name', key=[domain, name])

    # method that creates a related document
    def get_new_user(self):
        user = User(domain=self.domain)
        user.save()
        self.user_ids.append(user._id)
        return user

in views.py:

from models import User, Group

# there are tons of views like this, (request, domain, ...)
def create_new_user_in_group(request, domain, group_name):
    group = Group.by_name(domain, group_name)[0]
    user = User(domain=domain)
    user.save()
    group.user_ids.append(user._id)
    group.save()

in group/by_name/map.js:

function (doc) {
    if (doc.doc_type == "Group") {
        emit([doc.domain, doc.name], null);
    }
}

After

models.py

class DomainDocument(Document):
    domain = StringProperty()

    @classmethod
    def domain_view(cls, *args, **kwargs):
        kwargs['key'] = [cls.domain.default] + kwargs['key']
        return super(DomainDocument, cls).view(*args, **kwargs)

    @classmethod
    def get(cls, *args, **kwargs, validate_domain=True):
        ret = super(DomainDocument, cls).get(*args, **kwargs)

        if validate_domain and ret.domain != cls.domain.default:
            raise Exception()

        return ret

    def models(self):
        # a mapping of all models in the application. accessing one returns the equivalent of

        class BoundUser(User):
            domain = StringProperty(default=self.domain)


class User(DomainDocument):
    pass

class Group(DomainDocument):
    name = StringProperty()
    user_ids = StringListProperty()

    def users(self):
        return [self.models.User.get(id) for id in self.user_ids]

    @classmethod
    def by_name(cls, name):
        return cls.domain_view('groups/by_name', key=[name])

    def get_new_user(self):
        user = self.models.User()
        user.save()

views.py

@domain_view  # decorator that sets request.models to the same sort of object that is returned by DomainDocument.models and removes the domain argument from the URL router
def create_new_user_in_group(request, group_name):
    group = request.models.Group.by_name(group_name)
    user = request.models.User()
    user.save()
    group.user_ids.append(user._id)
    group.save()

(Might be better to leave the abstraction leaky here in order to avoid having to deal with a couchapp-style //! include of a wrapper for emit that prepends doc.domain to the key or some other similar solution.)

function (doc) {
    if (doc.doc_type == "Group") {
        emit([doc.name], null);
    }
}

Pros and Cons

So what are the pros and cons of this?

Pros:

  • DRYer
  • prevents you from creating related documents but forgetting to set the domain.
  • prevents you from accidentally writing a django view -> couch view execution path that leads to a security breach
  • doesn't prevent you from accessing underlying self.domain and normal Document.view() method
  • potentially gets rid of the need for a lot of sanity checks verifying whether two documents whose domains we expect to be equal are.

Cons:

  • adds some complexity
  • hides what's really happening
  • requires no model modules to have classes with the same name, or you would need to add sub-attributes to self.models for modules. However, requiring project-wide unique class names for models should actually be fine because they correspond to the doc_type property couchdbkit uses to decide which class to instantiate them as, which should be unique.
  • removes explicit dependency documentation (from group.models import Group)
share|improve this question
    
That's a nice analysis. But what is your question? –  adhominem Jun 25 '13 at 16:22
add comment

1 Answer

This is really a question that only you can answer, over-abstraction is in the eye of the beholder. The below is my humble opinion.

To me Domain is an entity. It's an entity because it has identity that is critical to the lifecycle of the Domain concept. As such abstracting it out of other contexts is exactly the thing to do, but as its own first class object rather than as part of another class that you still pass around. Furthermore at first glance it would appear to me that Domain is an aggregate entity.

Document in my opinion exists in the same bounded context as Domain, but is a separate aggregate that will be referenced by ID only e.g. a document must be associated with a domain but since there is no need for transactional consistency between the two it's OK to have them as separate aggregates.

share|improve this answer
    
I should have added that we do indeed have a Domain first class object / document type, and this is added to request on every logged-in request, we just reference the domain/string ID most of the time. –  mwhite Jul 11 '13 at 23:21
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.