What is the most effective way to manage over 100 cases in Typescript without getting overwhelmed?

My aim is to streamline the processing of different types of batches using one program by specifying the batch type in the .env file.

The reason for wanting to process all batches with a single program is to avoid configuring a separate Continuous Deployment (CD) pipeline for each batch type.

Even if the program uses the same image, I desired to execute different operations based on the batch type, which is why I utilized .env. This ensures that only one batch type is processed each time the program runs.

Initially, when there were only a few batch types, I wrote the functions directly.

function oldOne(){
  const batchType = process.env.BATCH_TYPE

  if(batchType === EnumBatchType.AD_KEYWORD_INPUT){
    // perform actions specific to ad keyword input
  }else if(batchType === EnumBatchType.AD_KEYWORD_OUTPUT){
    // perform actions specific to ad keyword output
  }
  ...
  process.exit(0)
}

As the number of messages to handle exceeded 10, I opted to create a class that inherits an interface capable of managing those messages.

This approach proved to be quite satisfying.

export interface Batchable{
    doBatch():Promise<void>; // every batch class includes this function
}
------ InputBatch, OutputBatch, RefineBatch, and so on...
export abstract class InputBatch<T> implements Batchable {
  abstract getDataFromKurve(): Promise<T[]>

  abstract getBodyFromData(datas: T[]): Promise<NexusTagDto>

  abstract updateNexusTag(body: NexusTagDto): Promise<NexusResponseDto>

  async doBatch(): Promise<void> {
    const datas = await this.getDataFromKurve()
    const body = await this.getBodyFromData(datas)
    const res = await this.updateNexusTag(body)
    unifiedLog('batch-finish', JSON.stringify(res), true)
  }
}
------
export class Batch1 extends InputBatch<Something> {...}
export class Batch2 extends InputBatch<Something> {...}
export class Batch3 extends OutputBatch<Something> {...}
export class Batch4 extends RefineBatch<Something> {...}
export class Batch5 extends InputBatch<Something> {...}
export class Batch6 extends OutputBatch<Something> {...}
export class Batch7 extends InputBatch<Something> {...}
export class Batch8 extends InputBatch<Something> {...}

Although each batch has a descriptive name, for security purposes, they are labeled as Batch1, Batch2, etc.

Excluding the switch statements and individual batch codes, the rest of the code is presented below.

export async function batchStart() {
  const batchType = process.env.BATCH_TYPE
  unifiedLog('batch_start', process.env.TARGET_SCHEMA)
  const client = getBatchClient(batchType as EnumBatchType)

  if (client) {
    await client.doBatch()
    process.exit(0)
  }
}

As the number of batch types surpassed 60, the function getBatchClient with just switch-case statements escalated to over 200 lines of code.

Due to company guidelines mandating alphabetical sorting of switch statements, navigating the code has become cumbersome.

Considering the likelihood of an increase in batch types, the long switch statements are no longer feasible.

Contemplating the use of a map with key-value pairs for message-client handling, it becomes apparent that using a single message handler and terminating the program is not optimal.

It seems wasteful to create numerous classes when only one will be utilized.

function useMapCase(batchType: EnumBatchType): Batchable {
  const map = new Map<EnumBatchType, Batchable>([
    [EnumBatchType.BATCH_TYPE1, new Batch1()],
    [EnumBatchType.BATCH_TYPE2, new Batch2()],
    [EnumBatchType.BATCH_TYPE3, new Batch3()],
    [EnumBatchType.BATCH_TYPE4, new Batch4()],
    [EnumBatchType.BATCH_TYPE5, new Batch5()],
    [EnumBatchType.BATCH_TYPE6, new Batch6()]
  ])
  return map.get(batchType)
}

If the number of batches exceeds 200, the map constructor alone could stretch over 200 lines.

Unfortunately, without the authority to alter the layout for gradual setup, the task of managing such large maps becomes overwhelming.

Exploring alternative methods presented in other articles, one such approach is demonstrated below:

function otherWays(batchType: SomeObject): Batchable {
  const { batchType, level1, level2 } = batchType
  try {
    const batchTypeMap = new Map<string, BatchSelector>([
      ['input', new InputBatchSelector()],
      ['output', new OutputBatchSelector()],
      ['refine', new RefineBatchSelector()]
    ])

    const level1Map = batchTypeMap.get(batchType)
    const level2Map = level1Map.get(level1)
    return level2Map.get(level2)
  } catch (e) {
    unifiedError('invaild_type', batchType)
    new SlackClient('Init Batch').sendErrorMessage({
      message: `${JSON.stringify(batchType)} is invalid type (from ${process.env.JOBTAG})`,
      target: EnumNotiTarget.BATCHCODE
    })
    return null
  }
}

Though this approach divides the process into segments and avoids the need to create entire classes, it may involve multiple object instantiations.

For a streamlined and efficient solution, there might need to be an established pattern in the batch type, which unfortunately is not the case here.

Amidst these challenges, the dilemma remains whether to handle hundreds of cases individually or to utilize a map despite the inefficient memory utilization.

Answer №1

Utilizing a map with key-value message-client was considered, but it proved to be ineffective as it only utilizes one message handler causing the program to prematurely terminate.

Some may argue that defining all batch classes is unnecessary if only one is used during each run.

The tedious task of scrolling to find the correct placement for the message is now required.

The core issue seems to revolve around the inconvenience of handling a large list within the switch statement. Whether it's a lengthy switch statement, large initializer on a Map, or any other list-based solution, the challenge remains the same.

If the values in EnumBatchType can be converted to strings, dynamically importing the batch class could be a viable option. This approach minimizes the clutter of a massive list within the source code and allows for clear identification of changes made to specific batches in source control.

One approach could be to have individual batch classes in separate files based on their EnumBatchType equivalent:

For example, instead of:

export class Batch1 extends InputBatch<Something> {/*...*/}
export class Batch2 extends InputBatch<Something> {/*...*/}

You could have Batch1 in a file named after the string equivalent of EnumBatchType.BATCH_TYPE1 (e.g., batches/BatchType1.ts):

export default class Batch1 extends InputBatch<Something> {/*...*/}

...and similarly for Batch2 in batches/BatchType2.ts:

export default class Batch2 extends InputBatch<Something> {/*...*/}

This organization allows for easier access to the correct batch class:

export async function batchStart() {
    const batchType = process.env.BATCH_TYPE;
    const client = await getBatchClient(batchType as EnumBatchType);
  
    if (client) {
        await client.doBatch();
        process.exit(0);
    }
}

// ...

async function getBatchClient(batchType: EnumBatchType): Promise<Batchable> {
    const BatchClass = (await import(`./batches/${batchType}.ts`)).default as new () => Batchable;
    const client = new BatchClass();
    return client;
}

Alternatively, to handle cases where there is no matching batch class, the import error can be managed within getBatchClient:

async function getBatchClient(batchType: EnumBatchType): Promise<Batchable> {
    let importError = true;
    try {
        const BatchClass = (await import(`./batches/${batchType}.ts`)).default as new () => Batchable;
        importError = false;
        const client = new BatchClass();
        return client;
    } catch (error) {
        if (importError) {
            return null;
        }
        throw error;
    }
}

While this approach may introduce the risk of incorrect constructor usage, it simplifies the code by moving the bulk of the list to the file system.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The server has access to an environment variable that is not available on the client, despite being properly prefixed

In my project, I have a file named .env.local that contains three variables: NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY=pk_test_<get-your-own> MAGIC_SECRET_KEY=sk_test_<get-your-own> TOKEN_SECRET=some-secret These variables are printed out in the file ...

Encountering an error of TypeError while attempting to generate a new GraphQL

Currently using Apollo-Server/TypeScript with graphql-tool's makeExecutableSchema() to set up schema/directives. Encountering an error while attempting to add a basic GraphQL Directive: TypeError: Class constructor SchemaDirectiveVisitor cannot be in ...

Creating React components dynamically using the number of objects passed as props

When attempting to create components based on the number specified in an object's files property, I keep encountering an error indicating that the parent component has too many children. If the files property is set to 5, does anyone have a solution ...

"Encountering connectivity issues between NestJs and TypeORM when trying to establish a

My current challenge involves connecting to MySQL. I have set the database connection variables in a .env file located in the root directory, and I am initializing the connection in the app.module.ts file. The problem arises when I attempt to create or run ...

Converting a "String" value to a specific "Type" in Angular 2 using TypeScript

This is my current object, Home points to the import statement for Home component. import { Home } from '../home/home'; arr = [ { name: "Home", root: Home, icon: "calc" } ]; This is what I want to achieve: import { Home } from & ...

Send an API request using an Angular interceptor before making another call

Within my application, there are multiple forms that generate JSON objects with varying structures, including nested objects and arrays at different levels. These forms also support file uploads, storing URLs for downloading, names, and other information w ...

React Router Issue: Component Not Rendering When <nav> Element Is Incomplete

I am currently experiencing an issue with rendering a component in my React TypeScript application using React Router. The problem arises when trying to navigate to the AddTask component by clicking on a link within a <nav> element. Strangely, the co ...

Guide on incorporating external .d.ts files for a module

I'm currently delving into the process of utilizing an external .d.ts file that is not included in the module. My intention is to make use of xlsx, which lacks its own type definitions, and instead incorporate them from the package @types/xlsx. Afte ...

Tips on ending an interval in rxjs once it has started

Implemented a code in an Angular component to retrieve data from a service every 10 seconds on initialization. Now, I need to find a way to stop the interval after a certain period of time such as 5 minutes or when all the necessary data has been collected ...

Starting up various modules in Angular 6 using arrays

Can an array be declared and used to bootstrap multiple components in module.ts? I attempted the following: export var initialComponents = []; initialComponents.push(AppComponent); if(condition) { initialComponents.push(IchFooterComponen ...

Is it possible to validate a template-driven form without using the model-driven approach?

Attempting to validate a template-driven form in Angular without two-way data binding has proved to be challenging. I have successfully implemented validation using [(ngModel)], but running into an error when trying to validate the form without the MODEL p ...

Is there a method to automatically eliminate all unnecessary properties in an Angular project?

In my extensive Angular project, I suspect that there are numerous declared properties in .component.ts that are not being used. Is there a method available to automatically eliminate all unused properties within an Angular project while also taking into ...

Issues with Angular2 causing function to not run as expected

After clicking a button to trigger createPlaylist(), the function fails to execute asd(). I attempted combining everything into one function, but still encountered the same issue. The console.log(resp) statement never logs anything. What could be causing ...

Submitting Angular 4 Form Reset Sends Data to Server

I am facing an issue with my HTML form: <form class="row" name="powerPlantSearchForm" (ngSubmit)="f.valid && searchPowerPlants()" #f="ngForm" novalidate> <div class="form-group col-xs-3" > <label for="powerPlan ...

How can Karma unit tests with Jasmine in a TypeScript Node project accurately measure code coverage, even with external dependencies?

We have a unique situation with the code coverage of our project involving a node dependency. It's important to note that the npm dependency source code is actually part of our project, as we are responsible for its development and publication. Here&a ...

Guide to incorporating a pluck feature into TypeScript code

One task I face frequently is extracting specific properties from an object const obj = {a:1, b: 2, c: 3}; const plucked = pluck(obj, 'a', 'b'); // {a: 1, b:2} Unfortunately, achieving this task with type safety in TypeScript can be c ...

Angular's implementation of a web socket connection

I am facing an issue with my Angular project where the web socket connection only opens upon page reload, and not when initially accessed. My goal is to have the socket start as soon as a user logs in, and close when they log out. Here is the custom socke ...

After updating from angular4 to angular 5, the npm test is failing with the error message "TypeScript compilation cannot find test.ts file"

After upgrading my app from Angular4 to Angular 5 using the steps provided on https://update.angular.io/, I encountered an issue. While I can successfully run ng-serve and ng build without any problems, the npm test command for ng test is failing with the ...

Continue iterating using (forEach, map,...) until the object (children) has no more elements

I need to disable the active status for all elements within my object structure. Here is an example of my object: const obj = { name: 'obj1' , ative: true , children: [ { name: 'obj2' , ative: true , children: ...

Stop the controller from reloading when navigating in Angular2/Ionic2

Initially, I developed my app using tabs. When navigating to a page, the view would load for the first time (fetch data from API and display it), and upon returning to the same page, nothing would reload because the controller did not run again. Recently, ...