Search code examples
node.jsnosqlcouchdbdomain-driven-designclean-architecture

How to implement unique key constraints in CouchDB


I use CouchDB and I would like all my users to have unique emails. I would expect that the database return a status 400 (bad request) when I try to duplicate an email.

But since there is no way to define constraints in CouchDB, I should implement it myself, my question is:

In which layer of my application should this rule stand?

(1) Domain objects layer
I don't really know how to implement it in this layer

(2) Interactor layer
This constraint could be implemented in interactors here since there is the place where business rules stand. But if there is multiple rules for single document it could add unnecessary complexity...

function createUser(userData)  {
  let email = userData.email;
  let exist = await userDB.userExist(email);

  if(exist) {
    // return status 400
  } else {
    // create user
  }    
}

(3) Database gateway layer
The constraint can also be implemented in the database gateway layer. Usually we'll have a gateway for each specific entity. But does that means that external services adapters contains a bit of business logic?

class userDB()  {
  constructor(opts) {
    this.db = opts.db.connect();
  }

  async userExist(email) {
    return await this.db.fetchByView('email', email);
  }

  async create(email) {
    let exist = await this.userExist(data.email);
    if(exist) {
      // throw error
    } else {
      // create the user
    }
  }
}

Solution

  • Unique email address is a very old DDD topic. It relates to the set validation. The simplest (and from my point of view is also the best) is to place a constraint at the database level.

    From what I know, the only whay to create an unique constraint in CouchDB is to use the _id field so you can use this solution. This idea is to put the email in the _id field.

    let exist = await this.userExist(data.email);

    if(exist) { // throw error } else { // create the user }

    This method is not safe for concurrent updates. Two users can be created at the same time. Imagine that for both the requests the this.userExist(data.email) is returning false.