Skip to content

Latest commit

 

History

History
71 lines (49 loc) · 5.07 KB

28.04.2024-adr-secondary_key_based_storage.md

File metadata and controls

71 lines (49 loc) · 5.07 KB

Secondary Key-based Storage

Date: 28/04/2024

Context and Problem Definition

Sometimes we may want to store an object instance in MongoDB without relying on the existence of an id field. For example, consider the case of an application that receives from an external service an object instance that is semantically equivalent to a domain object but which type specifies a different primary key. If we assume that both object types share a common (set of) field values that uniquely identify a stored document, we could enable secondary key-based domain object storage.

It is possible, however, that the given object instance may include values for only a subset of domain object fields. Hence, the given object instance may specify partial contents of the domain object, which may be sufficient to support the update of a stored document, but possibly insufficient to create a new document without violating any domain object validation rule.

This ADR explores a couple of approaches to enable secondary key-based object instance storage using the save operation.

Explored Options

Infer the secondary key from the object instance to store

The idea consists of inferring the secondary key from the definition of the Mongoose schema related to the type of the object instance to store. The target secondary key could be identified by a new property (e.g., sk) included in one or more schema field definitions, since the Mongoose Schema type does not currently include it in its API. Here is an example of how the schema of a Book domain object could look like:

export const BookSchema: Schema<Book> = extendSchema(
  BaseSchema,
  {
    title: { type: String, required: true },
    description: { type: String, required: false },
    isbn: { type: String, required: true, unique: true, sk: true },
  }
);

As a side note, one may think that the schema unique property is ideal for this purpose, but Mongoose schemas may specify multiple unique fields or they may only be specified for some domain subtype schemas, just to mention a couple of possible issues. Thus, the unique property is not ideal for the inference of secondary keys.

The advantage of this approach is simplicity; it requires some modification on the save operation implementation details and leveraging Mongoose the Schema type to support secondary key specification while the signature and return type of save remain as is.

However, and as mentioned earlier, the save operation must support the update of partial contents of a domain object. This means that the given object can be of type Object, making it impossible to infer its domain object type. This, in turn, makes it impossible to determine the schema associated to the type. Hence, it is also impossible to identify those schema fields acting as secondary keys. This approach is only possible to store instances of a given domain object type that specify valid (although not necessarily complete) contents.

Declare the secondary key in a new save operation option

The idea behind this alternative approach is to let custom repository clients to explicitly define secondary keys in a new sk property to be included in the options parameter of the save operation. Here is how it would look like in some BookRepository client code extract:

bookRepository.save(entity, {sk: ['isbn']})

Contrarily to the first approach, this one allows partial content storage under the following conditions:

  • the given object instance must include a value for each field composing the secondary key (e.g., isbn in the previous example)
  • in the event of single entity matching, the given object instance is updated
  • in the event of multiple entity matching, an exception must be thrown
  • in the event of no entity matching, the given object instance is inserted iff its contents are valid (although not necessarily complete)

An obvious drawback compared to the first approach is that this one requires some save operation signature modification. But the main drawback is that it introduces some persistence logic leak into application/domain logic, since repository clients are forced to understand persistence semantics i.e., they must define the secondary key themselves. This fact alone invalidates the approach.

Conclusion

None of the two explored approaches is good enough to enable secondary key-based partial object storage. Therefore, we suggest that anyone willing perform some secondary key-based data storage implements an adhoc solution based on the following pseudocode:

save(entity, secondaryKeyValues) {
  const document = this.entityModel.findOne(<secondary key value-based criteria>);
  if (document) { // Mongoose-based entity update
    document.set(entity);
    const updatedDocument = document.save();
    return this.instantiateFrom(updatedDocument);
  }
  else { // Monguito-based entity insertion
    return this.insert(entity);
  }
}

Beware that the previous pseudocode does not include the required logic to support some monguito features such as polymorphic domain models, transactional logic or operation audition for sample simplicity purposes.