Home > front end >  is it expensive to remapping object properties in JS? are there benchmarks or how to benchmark it?
is it expensive to remapping object properties in JS? are there benchmarks or how to benchmark it?

Time:10-12

For sometime I have tightly coupled my object properties in my monolithic projects. There was little DTO. From client -> down to database where there it followed little to no attention to SOLID principles. Dependencies were going inwards, leaving an architecture thats expensive to change and maintain. however, since I've read and research much about it SOLID and clean architecture. I've noticed there is a lot of remapping of objects in order to maintain separation.

example 1: DAL: the renaming of property conventions first_name => firstName.

class UserRepository {
  #pool: Pool;
  #tableName = 'platform_users';
  constructor(pool: Pool) {
    this.#pool = pool;
  }

  async findById(id: string): Promise<UserEntity | null> {
    try {
      const QueryString = `SELECT * FROM ${this.#tableName} WHERE id = $1`;
      const QueryArguments = [id];
      const rawData = await this.#pool.query(QueryString, QueryArguments);
      const data = this.#mapPostResults(rawData);
      return data.length > 0 ? data[0] : null;
    } catch (e) {
      throw new Error('Failed to process query');
    }
  }

 async insertNewUser({
    id,
    username,
    firstName,
    lastName,
    password,
    email,
    verified,
    registeredOn,
    lastLogin,
    description
  }: UserEntity): Promise<{ rowCount: number; id: string }> {
    try {
      const QueryString = `INSERT INTO ${
        this.#tableName
      }(id, first_name, last_name, email, verified, password, registered_on, last_login, description, username) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETUIRNING id`;
      const QueryArguments = [
        id,
        username,
        firstName,
        lastName,
        email,
        password,
        verified,
        registeredOn,
        lastLogin,
        description
      ];
      const rawData = await this.#pool.query(QueryString, QueryArguments);
      return { rowCount: rawData.rowCount, id: rawData.rows[0].id };
    } catch (e) {
      console.log(e);
      throw new Error('Failed to process query');
    }
  }

  #mapPostResults(res: QueryResult): UserEntity[] {
    return res.rows.map((r) => {
      return {
        id: r.id,
        username: r.username,
        firstName: r.first_name,
        lastName: r.last_name,
        email: r.email,
        password: r.password,
        verified: r.verified,
        lastLogin: r.last_login,
        description: r.description,
        registeredOn: r.registered_on
      };
    });
  }
}

example 2: dependency inversion for express's req, and res to our controllers. To establish an interface thats independent of framework (express) - below we see the httpRequest will receive a close 1:1 mapping from express' request structure'.

export default function makeExpressCallback(controller) {
  return (req: Request, res: Response) => {
    const httpRequest = {
      body: req.body,
      query: req.query,
      params: req.params,
      ip: req.ip,
      method: req.method,
      path: req.path,
      session: req.session || {},
      headers: {
        'Content-Type': req.get('Content-Type'),
        Referer: req.get('referer'),
        'User-Agent': req.get('User-Agent')
      }
    };
    controller(httpRequest)
      .then((httpResponse) => {
        if (httpResponse.headers) {
          res.set(httpResponse.headers);
        }
        res.type('json');
        res.status(httpResponse.statusCode).send(httpResponse.body);
      })
      .catch((e) => res.status(500).send({ error: 'An unkown error occurred.' }));
  };
}

// index.ts

router.post('/user', bodyParser.json(), makeExpressCallback(createUserAccount));

These example goes on to use-case layer, for mapping of their response and request...

“REQUEST AND RESPONSE MODELS Use cases expect input data, and they produce output data. However, a well-formed use case object should have no inkling about the way that data is communicated to the user, or to any other component. We certainly don’t want the code within the use case class to know about HTML or SQL!

The use case class accepts simple request data structures for its input, and returns simple response data structures as its output. These data structures are not dependent on anything. They do not derive from standard framework interfaces such as HttpRequest and HttpResponse. They know nothing of the web, nor do they share any of the trappings of whatever user interface might be in place.

This lack of dependencies is critical. If the request and response models are not independent, then the use cases that depend on them will be indirectly bound to whatever dependencies the models carry with them.

You might be tempted to have these data structures contain references to Entity objects. You might think this makes sense because the Entities and the request/response models share so much data. Avoid this temptation! The purpose of these two objects is very[…]”

Excerpt From Clean Architecture: A Craftsman's Guide to Software Structure and Design (Robert C. Martin Series) Robert C. Martin This material may be protected by copyright.

so with this, compared against my old way of coding, I did not have to do these mapping because how tightly coupled they were at every layer (or lack of), of-course that is very bad. But I do wonder, are these types of mapping expensive, should we still strive to minimize the best we can, even if tradeoffs significantly outweighs not doing so?

CodePudding user response:

The cost of these mappings is all in the developer time, not in runtime. You are talking about shuffling around a few bytes in memory, taking at most a few microseconds. If you are ever concerned, of course you should benchmark it, just make sure the bench-marking code itself is consistent in its performance impact.

RAM access is incredibly fast, and highly optimized in the Node VM. Generally in this you are passing around references to string objects, not actually making new memory locations. All of this gets subsumed the first time you have to read an extra few bytes from a database or round-trip to the browser (10s of milliseconds instead of a few microseconds).

Unless you are deep-copying large arrays, DTOs will generally be cheap.

  • Related