lundi 10 octobre 2022

Tipps for defining an architecture to abstract over and unify different CRUD APIs

I am currently trying to build a library to abstract over the different CRUD Web APIs of our business units. I cant give an actual example, but here is the base problem:

We have N different APIs that all share more or less the same resources. There is for example a resource to create a user. For one API X, the resource is called "user", for another API Y its "users" and they all require different fields for a Create Request. X requires only a name and an address as string while Y wants name as a string but address as an object consisting of street, city and country.

My first intention was to use the Adapter pattern and define my own class User along with the API specific interfaces.

interface XUser {
   name: string;
   address: string;

   CREATE() : XUserWithId
}

interface YUser {
   name: string;
   address: {
      street: string;
      city: string;
      country: string
   };
   
   CREATE() : YUserWithId
}

interface IUser {
   name: string;
   street: string;
   city: string;
   country: string;
}

class User implements IUser {}

Then I wanted to write my Adapters to make my User class behave like the API specific objects:

class XUserAdapter implements XUser {
   constructor(user: IUser) {
      this.name = user.name;
      this.address = [user.street, user.city, user.country].join(", ");
   }
}


class YUserAdapter implements YUser {
   constructor(user: IUser){
      this.name = user.name;
      this.address = {
         street: user.street;
         city: user.city;
         country: user.country;
      }
   }
}

CREATEing a User Resource in APIs X and Y now boils down to creating one user object, putting it into the adapters and calling the adapters CREATE functions:

users: User[] = [];

tom = new User(name="Tom", street="Foostreet", city="Bartown", country="USA");

xuser = new XUserAdapter(tom);
yuser = new YUserAdapter(tom);

xuser = xuser.CREATE();
yuser = yuser.CREATE();

My Problem is that obviously calling CREATE will hand me back platform specific user objects that i want to be in the shape of my own User class. This would require two more adapters to "translate" to the opposite direction:

class UserFromYUser implements IUser {
   constructor(yUser: YUser) {
      ...
   }
}

Question: Am I missing the obvious? Is creating one adapter per platform specific resource really the way to go? I Read about the two-way adapter pattern in GoF but it was just a short hint. Do you guys have any other idea on how one could tackle this problem and how to split the code but maintain consistency across API interfaces?

Thank you in advance.

Aucun commentaire:

Enregistrer un commentaire