Open alacret opened 3 years ago
What you think of a generic class class that takes a object an exposed a method to read deep properties?
const user = {
role: {
id: 123,
name: admin
}
}
const daoUser = new DAO(user)
daoUser.getProperty('role.id') // the property path string could be mapped with typescript without loosing the type safety
We can use typescript literal types
What you think of a generic class class that takes a object an exposed a method to read deep properties?
const user = { role: { id: 123, name: admin } } const daoUser = new DAO(user) daoUser.getProperty('role.id') // the property path string could be mapped with typescript without loosing the type safety
We can use typescript literal types
@jesusrodrz The purpose is to encapsulate how properties of the object are accessed. This encapsulation is not only for null checking or undefined, it can be for more complex evaluations, example:
class Company {
...
getHeadCount = () => {
if (this.company.offices === null)
return 0;
if (this.company.offices.lenght === 0)
return 0;
return this.company.offices.reduce(a,b => a + b);
}
}
What you think of a generic class class that takes a object an exposed a method to read deep properties?
const user = { role: { id: 123, name: admin } } const daoUser = new DAO(user) daoUser.getProperty('role.id') // the property path string could be mapped with typescript without loosing the type safety
We can use typescript literal types
this is daoUser.getProperty('role.id')
not the same as doing this role.id
The main thing here is that if later you need to make a change in the role.id object, you don't have to change it in each view
I've manage to create a function that takes input object and a object with getters functions and returns a object with the getters functions passed ad params
type Getters<Type> = {
[Property in keyof Type]: () => Type[Property];
};
type InputGetters<T, Type> = {
[Property in keyof Type]: (data: T) => Type[Property];
};
/**
* @param object - Object Input.
* @param getters - Getters functions.
* @returns Mappen object.
*/
export function createDataAccessObject<
U extends Exclude<Record<string, unknown>, 'set'>,
T = unknown
>(
object: T,
getters: InputGetters<T, U>,
): Getters<U> & { set: (data: T) => void } {
let state = object;
const keys = Object.keys(getters) as (keyof U)[];
const functions = keys.reduce(
(prev, current) => ({
...prev,
[current]: () => getters[current](state),
}),
{},
) as Getters<U>;
return {
...functions,
set: (data) => {
state = data;
},
};
}
const userGraphql = {
role: {
id: 123,
},
tenant: {
id: 123,
name: 'Main Tenant',
},
};
const user = createDataAccessObject(userGraphql, {
getRoleId: (data) => data.role.id,
getTenantId: (data)=>data.tenant.id
});
user.getRoleId(); // returns role id
user.getTenantId(); // returns tenant id
we can't use classes because it doesn't allow dynamic methods
@alacret @kikeztw 👆🏽
The problem I see from this is that we cannot use it in the shared. The first parameter does not make much sense, we need to declare the methos that will be used to access the properties in the shared and then in the front or in the backend is that we will pass the data
I think the best thing is to use the classes since we can use decorator in the methods, we could create a couple of utils for certain methods of certain classes
function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("first(): called");
};
}
function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): called");
};
}
class ExampleClass {
@first()
@second()
method() {}
}
Refactoring the first approach we could manage to define the model in the shared project then use it on the frontend or cloud functions
type Getters<Type> = {
[Property in keyof Type]: () => Type[Property];
};
type InputGetters<T, Type> = {
[Property in keyof Type]: (data: T) => Type[Property];
};
/**
* @param getters - Getters functions.
* @returns Mappen object.
*/
export function createDataAccessObject<
T,
U extends Exclude<Record<string, unknown>, 'set'>
>(getters: InputGetters<T, U>): (data: T) => Getters<U> {
return (state: T) => {
const keys = Object.keys(getters) as (keyof U)[];
const functions = keys.reduce(
(prev, current) => ({
...prev,
[current]: () => getters[current](state),
}),
{},
) as Getters<U>;
return functions;
};
}
Usage in share
type UserModel = {
id: string;
name: string;
role: {
id: string;
name: string;
};
tenant: {
id: string;
name: string;
};
};
type UserGetters = { getRoleId: string; getTenantId: string;}
export const getUser = createDataAccessObject<UserModel,UserGetters>({
getRoleId: (data) => data.role.id,
getTenantId: (data) => data.tenant.id,
});
Usage in front or cloud functions
import { getUser } from 'shared-package'
const userGraphql = {
role: {
id: 123,
},
tenant: {
id: 123,
name: 'Main Tenant',
},
};
const user = getUser(userGraphql)
user.getRoleId(); // returns role id
user.getTenantId(); // returns tenant id
How this is better than the class approach?
The purpose of this convention is to establish a new way of managing Domain Objects or Business Objects in the code. Currently, across the application, we use the Graphql types for managing data types of Business Objects. This approach has multiple problems:
Solution
The proposed solution involves the use of Classes instead of Type alias to manage Business Objects, for example, Users, Company, CompanyUser, Plan, etc
Taking into consideration the following aspects:
Additional considerations to implement this pattern:
Example: