Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I opt for code repetition or consolidation with api service - JS

I'm working on a large CMS system where a particular module and its submodules take advantage of the same backend API. The endpoint is exactly the same for each submodule aside from its "document type".

So a pattern like this is followed:

api/path/v1/{document-type}

api/path/v1/{document-type}/{id}

api/path/v1/{document-type}/{id}/versions

As time goes on the number of modules that use this API grows and I am left with many, many redundant api services that implement 7 CRUD methods:

getAllXs() {...}
getX(id) {...}
getXVersion(id, versionId) {...}

etc...

with an individual method looking like this

getAllXs() {
    let endpoint = BASE.URL + ENDPOINTS.X;
    let config = ...
    return http.get(endpoint, config)
        .then(response => response.data);
        .catch(...);

}

Where X would be the name of a particular Document Type.

I came to a point where I decided to make a single service and do something like this:

const BASE_URL = window.config.baseUrl + Const.API_ENDPOINT;
const ENDPOINTS = {
  "W": "/v1/W/",
  "X": "/v1/X/",
  "Y": "/v1/Y/",
  "Z": "/v1/Z/",
}

getAllDocuments(docType, config={}) {
  let endpoint = BASE_URL + ENDPOINTS[docType];
  return http.get(endpoint, config)
        .then(response => response.data);
        .catch(...);
}
...other methods

Where a type is specified and a mapped endpoint is used to build the path.

This reduces all of the document api services down to one. Now this is more concise code wise, but obviously now requires an extra parameter and the terminology is more generic:

getAllXs() --> getAllDocuments()

and it's a bit less 'idiot-proof'. What makes me insecure about the current way it is written is that there are 6 modules that use this API and the same 7 methods in each service.

The questions I keep asking myself are:

  • Am I bordering anti-pattern with the dynamic functions?

  • What if I had 10+ modules using the same API?

like image 439
GHOST-34 Avatar asked Jan 24 '23 11:01

GHOST-34


1 Answers

Your question made me think of a common Object Relational Mapping design problem.

There are no single source of truth when it comes to design, but if your recognize an ORM in what you are building and value object oriented design principles, I have some inspiration for you.

Here's an over simplification of my own vanilla ES6 ORM I have used on many projects (reusing your code snippets for relatability). This deisgn is inspired by heavy ORM frameworks I have used in other languages.

class ORM {
   constructor() {
      this.BASEURL = window.config.baseUrl + Const.API_ENDPOINT
      this.config = {foo:bar} // default config
   }

   getAll() {
      let endpoint = this.BASEURL + this.ENDPOINT
      return http.get(endpoint, this.config)
       .then(response => response.data)
       .catch(...)
   }

   get(id) {
      // ...
   }
}

And examples of extension of that class (notice the one with a special configuration)

class XDocuments extends ORM {
   static endpoint = '/XDocument/'

   constuctor() {
      super()
   }

   otherMethod() {
      return 123
   }
}

class YDocuments extends ORM {
   static endpoint = '/YDocument/'

   constuctor() {
      super()
   }

   getAll() {
      this.config = {foo:not_bar}
      super.getAll()
   }
}

Since you are specifically asking if this is bordering anti-patterns. I would suggest reading about SOLID and DRY principles, and ORM designs in general. You will also find code smells about global constants, but this would only be true if you are in a window context. I see you are already on the right path trying to avoid code duplication smell and the shotgun surgery smell. :-)

Good luck and don't hesitate to ask further questions and add additional details in comments!

like image 71
Kad Avatar answered Jan 28 '23 13:01

Kad