The JS class model can be directly coded for getting the code of the model layer of our JavaScript frontend app.

Compared to the unidirectional association app, we have to deal with a number of new technical issues:

We define the derived inverse reference properties, like

`Publisher`

::`/publishedBooks`

, without a check operation and without a set operation.We also have to take care of maintaining the derived inverse reference properties by maintaining the derived (sets of) inverse references that form the collection value of a derived inverse reference property. This requires in particular that

whenever the value of a

**single-valued**master reference property is**initialized or updated**with the help of a setter (such as assigning a reference to a`Publisher`

instance`p`

to`b.publisher`

for a`Book`

instance`b`

), an inverse reference has to be assigned or added to the corresponding value of the derived inverse reference property (such as adding`b`

to`p.publishedBooks`

); when the value of the master reference property is updated and the derived inverse reference property is multi-valued, then the obsolete inverse reference to the previous value of the single-valued master reference property has to be deleted;whenever the value of an optional

**single-valued**master reference property is**unset**(e.g. by assigning`null`

to`b.publisher`

for a`Book`

instance`b`

), the inverse reference has to be removed from the corresponding value of the derived inverse reference property (such as removing`b`

from`p.publishedBooks`

), if the derived inverse reference property is multi-valued, otherwise the corresponding value of the derived inverse reference property has to be unset or updated;whenever a reference is

**added**to the value of a**multi-valued**master reference property with the help of an add method (such as adding an`Author`

reference`a`

to`b.authors`

for a`Book`

instance`b`

), an inverse reference has to be assigned or added to the corresponding value of the derived inverse reference property (such as adding`b`

to`a.authoredBooks`

);whenever a reference is

**removed**from the value of a**multi-valued**master reference property with the help of a`remove`

method (such as removing a reference to an`Author`

instance`a`

from`b.authors`

for a`Book`

instance`b`

), the inverse reference has to be removed from the corresponding value of the derived inverse reference property (such as removing`b`

from`a.authoredBooks`

), if the derived inverse reference property is multi-valued, otherwise the corresponding value of the derived inverse reference property has to be unset or updated;whenever an object with a single reference or with multiple references as the value of a master reference property is

**destroyed**(e.g., when a`Book`

instance`b`

with a single reference`b.publisher`

to a`Publisher`

instance`p`

is destroyed), the derived inverse references have to be removed first (e.g., by removing`b`

from`p.publishedBooks`

).

Notice that when a new object is created with a single reference or with multiple references as the value of a master reference property (e.g., a new

`Book`

instance`b`

with a single reference`b.publisher`

), its setter or add method will be invoked and will take care of creating the derived inverse references.

Code each class of the JS class model as an ES2015 class with implicit getters and setters (or, alternatively, as an ES5 constructor function with explicit setters):

Code the property checks in the form of class-level ('static') methods. Take care that all constraints of a property as specified in the JS class model are properly coded in the property checks.

For each single-valued property, code the specified getter and setter:

In each setter, the corresponding property check is invoked and the property is only set/unset, if the check does not detect any constraint violation.

**If the concerned property is the inverse of a derived reference property (representing a bidirectional association), make sure that the setter also assigns/unsets (or adds/removes) the corresponding inverse reference to/from (the collection value of) the inverse property.**

For each multi-valued property, code its add and remove operations, as well as the specified get/set operations:

Code the add/remove operations as (instance-level) methods that invoke the corresponding property checks.

Code the setter such that it invokes the add operation for each item of the collection to be assigned.

**If the concerned property is the inverse of a derived reference property (representing a bidirectional association), make sure that the add/remove methods also assign/unset (or add/remove) the corresponding inverse reference to/from (the collection value of) the inverse property.**

Write the code of the serialization function

`toString()`

and the object-to-storage conversion function`toRecord()`

. In the object-to-storage conversion of a publisher or author object with`toRecord()`

, the derived properties`publishedBooks`

and`authoredBooks`

are not included since their information is redundant (it is derived from the`publisher`

and`authors`

properties of books).Take care of deletion dependencies in the

`destroy`

method.**Make sure that when an object with a single reference or with multiple references as the value of a master reference property is destroyed, the derived inverse references are unset (or removed) first.**

These steps are discussed in more detail in the following sections.

For instance, the `Publisher`

class from the JS class model is coded in the following way:

`class `**Publisher** {
constructor ({name, address}) {
this.name = name;
this.address = address;
// derived inverse reference property (inverse of Book::publisher)
this._publishedBooks = {}; // initialize as an empty map
}
get name() {...}
static checkName( n) {...}
static checkNameAsId( n) {...}
static checkNameAsIdRef( n) {...}
set name( n) {...}
get address() {...}
static checkAddress( a) {...}
set address( a) {...}
toString() {...}
toRecord() {...}
}

Notice that we have added the (derived) multi-valued reference property `publishedBooks`

, but we do not assign it in the constructor function because it is a read-only property that is assigned implicitly when its inverse master reference property `Book::publisher`

is assigned.

Any setter for a reference property that is coupled to a derived inverse reference property (implementing a bidirectional association), now also needs to assign/add/remove inverse references to/from the corresponding collection value of the inverse reference property. An example of such a setter is `publisher`

in the `Book`

class:

setpublisher( p) { if (!p) { // the publisher reference is to be deleted // delete the inverse reference in Publisher::publishedBooks delete this._publisher.publishedBooks[ this._isbn]; // unset the publisher property delete this._publisher; } else { // p can be an ID reference or an object reference const publisher_id = (typeof p !== "object") ? p : p.name; const constraintViolation = Book.checkPublisher( publisher_id); if (constraintViolation instanceof NoConstraintViolation) { if (this._publisher) { // delete the inverse reference in Publisher::publishedBooksdelete this._publisher.publishedBooks[ this._isbn]; } // create the new publisher reference this._publisher = Publisher.instances[ publisher_id]; // add the new inverse reference to Publisher::publishedBooksthis._publisher.publishedBooks[ this._isbn] = this; } else { throw constraintViolation; } } }

For any multi-valued reference property that is coupled to a derived inverse reference property, both the add and the remove method also have to assign/add/remove corresponding references to/from (the value set of) the inverse property.

For instance, for the multi-valued reference property `Book::authors`

that is coupled to the derived inverse reference property `Author:authoredBooks`

for implementing the bidirectional authorship association between `Book`

and `Author`

, the `Book::addAuthor`

method is coded in the following way:

addAuthor( a) { // a can be an ID reference or an object reference const author_id = (typeof a !== "object") ? parseInt(a) : a.authorId; const validationResult = Book.checkAuthor( author_id); if (author_id && validationResult instanceof NoConstraintViolation) { // add the new author reference this._authors[author_id] = Author.instances[author_id]; // automatically add the derived inverse referencethis._authors[author_id].authoredBooks[this._isbn] = this; } else { throw validationResult; } }

For the remove operation `removeAuthor`

we obtain the following code:

removeAuthor( a) { // a can be an ID reference or an object reference const author_id = (typeof a !== "object") ? parseInt(a) : a.authorId; const validationResult = Book.checkAuthor( author_id); if (validationResult instanceof NoConstraintViolation) { // automatically delete the derived inverse referencedelete this._authors[author_id].authoredBooks[this._isbn]; // delete the author reference delete this._authors[author_id]; } else { throw validationResult; } }

In the object-to-storage conversion of a publisher or author object with `toRecord()`

, the derived properties `publishedBooks`

and `authoredBooks`

are not included since their information is redundant (derived from the `publisher`

and `authors`

properties of books). For instance, the `Author::toRecord`

function is coded in the following way:

toRecord() { var rec = {}; // loop over all Author properties for (let p of Object.keys( this)) { // keep underscore-prefixed properties except "_authoredBooks" if (p.charAt(0) === "_" &&p !== "_authoredBooks") { // remove underscore prefix rec[p.substr(1)] = this[p]; } }; return rec; }

When a `Book`

instance `b`

, with a single reference `b.publisher`

to a `Publisher`

instance `p`

and multiple references `b.authors`

to `Author`

instances, is destroyed, the derived inverse references have to be removed first (e.g., by removing `b`

from `p.publishedBooks)`

:

Book.destroy= function (isbn) { const book = Book.instances[isbn]; if (book) { console.log( book.toString() + " deleted!"); if (book.publisher) { // remove inverse reference from book.publisherdelete book.publisher.publishedBooks[isbn]; } // remove inverse references from all book.authors for (let authorID of Object.keys( book.authors)) {delete book.authors[authorID].authoredBooks[isbn]; } // finally, delete book from Book.instances delete Book.instances[isbn]; } else { console.log(`There is no book with ISBN ${isbn} in the database!`); } };