
Colgué un saco de boxeo en mi sótano, pegué una foto de un gerente típico y metí el altavoz dentro para reproducir frases que me enojan. Por ejemplo, una pera dice: “Las empresas no necesitan su código perfecto. Necesita resolver el problema para que la ganancia cubra los costos. Si necesitas govnokod para esto, entonces habrá govnokod ". Y estoy empezando a abrazarme.
Recientemente, agregué una nota a la pera: "Los tipos son difíciles e innecesarios". En este momento, golpeé tan fuerte que mi mano casi se rompe. Porque suficiente de mi parte. Hace un par de meses, experimenté uno de los casos más atroces de mi carrera.
Mi amigo Antokha me pidió que ayudara con una solución para una corporación grande y grande. Acepté, y nos sumergimos en el abismo sin fondo del absurdo corporativo, la crisis, la guerra con colegas incomprensibles y todo tipo de injusticias. No podemos decir nada, así que hablaremos sobre tipos para que esa basura nunca se repita con nadie.
1
- (Antokha rcanedu ) Se me encargó el desarrollo de una herramienta de plataforma interna, una biblioteca para expresar entidades de software en forma de modelos orientados a objetos y para el trabajo uniforme con servicios API. Es decir, una herramienta para interactuar con datos que viajan desde la fuente a la pantalla y viceversa.
De esta manera, una gran cantidad de formateo, conversión, cálculo y transformación. Enormes estructuras, jerarquías complejas, numerosas conexiones de todo con todo. En tales redes es muy fácil perderse. Usted ve datos y no sabe qué se puede hacer y qué no se puede hacer. Dedique mucho tiempo a resolverlo. Esto es muy adictivo. Solo una buena descripción de los tipos resuelve el problema.
Debido a la falta de tipeo adecuado en muchas soluciones, se hace más difícil lograr el mismo comportamiento del programa en tiempo de ejecución y en tiempo de compilación. Los tipos deben dar plena confianza de que todo es igual y que también sucederá durante la ejecución. Las pruebas pueden hacer lo mismo. Es mejor confiar en ambos. Pero si elige entre tipos y pruebas, los tipos son mucho más confiables y más baratos.
Cuando haces un frente, hay dos fuentes de problemas: el usuario y el backend. Todo es simple para el usuario: hay muchas bibliotecas y marcos convenientes que se resumen a partir de las E / S del usuario (reaccionar, angular, vue y otros).
Hay otra historia en interacción con el backend. Sus especies son numerosas, y las realizaciones son oscuridad. No puede definir un enfoque estándar para describir datos. Debido a esto, se inventaron muletas como "normalización de la estructura de datos", cuando todos los datos entrantes se reducen a una estructura rígida, y si algo sale mal, comienzan las excepciones o la operación anormal. Esto debería acelerar y simplificar el desarrollo, pero de hecho requiere mucha documentación, diagramas UML, descripción de características.
Surgió un problema arquitectónico en la interacción de las partes del cliente y el servidor porque la interfaz había madurado. Se convirtió en un negocio completo, y no solo un diseño en la parte superior del back-end. Anteriormente, solo los desarrolladores del lado del servidor pedían la interacción cliente-servidor. Ahora los frentes y las espaldas se ven obligados a negociar y trabajar en estrecha colaboración. Necesitamos una herramienta que nos permita estructurar el trabajo con los servicios de origen de datos API, para evitar la pérdida de la verdad de los datos y, al mismo tiempo, simplificar las transformaciones adicionales. Este problema debe ser abordado por toda la comunidad.
- (Phil) Si eres un back-end, tienes muchas soluciones para adultos y prácticas de modelado de datos para la aplicación. Por ejemplo, en C # presenta una clase de modelo de datos. Toma algo, algo de EntityFramework, le proporciona los atributos con los que cubre sus modelos. Le dices a Lib cómo llegar a la base. Luego usa su interfaz para manipular estos datos. Esta cosa se llama ORM.
En el front-end, no decidimos la mejor manera de hacerlo: buscamos, intentamos, escribimos artículos ridículos, luego refutamos todo, comenzamos de nuevo y aún no llegaremos a una sola decisión.
- (Antoha) Todo lo que escribí antes tenía un gran problema: especialización estrecha. Cada vez que la biblioteca se desarrolló desde cero y cada vez que se agudizó para un tipo de interacción cliente-servidor. Todo esto sucede debido a la falta de mecanografía adecuada.
Creo que sin la escritura estática es difícil imaginar una biblioteca universal para trabajar con API y expresiones de dominio. Habrá mucha reflexión, contendrá una gran cantidad de documentación, estará rodeado de diferentes aplicaciones con una indicación de prácticas para una u otra forma. Esto no es una simplificación.
Una buena herramienta universal de este tipo debería proporcionar una imagen completa de los datos en cualquier segmento, de modo que siempre sepa exactamente cómo trabajar con estos datos y por qué es necesario.
- (Phil) Necesitamos una biblioteca que nos permita proporcionar una descripción detallada y control de cada entidad, para obtener datos para esta entidad de diferentes recursos con una interfaz diferente, desde la API REST y json-rpc a graphQL y NQL. Lo que permitirá mantener la creciente base de código y estructura en rigor y orden. Simple e intuitivo de usar. Proporcionar una descripción completa y precisa del estado de las entidades en cualquier momento. Queremos abstraer los módulos de nuestros usuarios de la capa de datos tanto como sea posible.
En primer lugar, miramos el existente. No nos gustó nada. Por alguna razón, todas las bibliotecas para trabajar con datos están hechas en js o con un montón de salidas. Estos estropean todo. Hacen la vista gorda a los desarrolladores, diciendo que dicha biblioteca no lo ayudará mucho, que no podrá navegar por los tipos de sus datos, no podrá expresar sus conexiones. Las cosas empeoran aún más cuando se utilizan múltiples API de diferentes tipos o es heterogéneo.
Todas estas bibliotecas no estaban suficientemente protegidas por tipos. Por lo tanto, crean más problemas de los que resuelven. Es más fácil no usarlos, sino tomar decisiones específicas de su dominio.
Por lo tanto, en lugar de la lib de mente estrecha en la que se encontraba la tarea, decidimos hacer una mucho más poderosa y abstracta, adecuada para todo. Y creíamos increíblemente en nuestra corrección, porque solo así se crean cosas realmente buenas.
2
- (Antoha) Como sucede a menudo, estaba esperando todo tipo de acceso para poder ingresar al repositorio de la compañía. Y esto puede durar varias semanas. En mi caso, solo tomó uno. En este momento, según mi experiencia en la creación de bibliotecas similares, descompuse la tarea, estimé la línea de tiempo.
El problema es que antes, como todos los demás, tomé decisiones muy especializadas. Trabajar en una herramienta universal conllevaba problemas adicionales. El sistema de tipos salió extremadamente complejo, y ni yo ni nadie más tenía experiencia en el diseño de esto. Peor aún, los desarrolladores a mi alrededor no tenían idea de la escritura estática en absoluto.
Pero comencé a hacer lo que pensaba que era correcto. En el Daily, te dije lo que estaba haciendo y por qué, pero, en principio, nadie me entendió. Mis preguntas al equipo sobre el problema siempre han quedado sin respuesta. Era como si yo no existiera. Amigo que hace algo muy complicado e incomprensible.
Comprendí que javaScript no funcionará aquí de ninguna manera. Necesitaba un PL con un modelo de escritura potente, excelente interacción con javaScript, tener una gran comunidad y un ecosistema serio.
- (Phil) He esperado mucho tiempo a que Antoha entienda el encanto de typeScript.
- (Antoha) Pero hay una serie de problemas que te vuelven loco. Hay tipeo, pero todavía no tengo una correspondencia perfecta entre la ejecución del programa y la ejecución por parte del usuario. La mecanografía parece complicada al principio. El tiene que soportar. Siempre quieres tomar y lanzar algún objeto o cualquier otro. Poco a poco se profundiza en el lenguaje, en su sistema de tipos, y aquí comienza a suceder algo interesante. Se vuelve mágico. Todo puede ser mecanografiado.
- (Phil) Por primera vez en nuestras vidas, nos juntamos y comenzamos algo.
El primer paso es que el usuario describa el esquema de datos. Nos preguntamos cómo se ve. Algo como esto:
type CustomerSchema = {
id: number;
name: string;
}
const Customer = new Model<CustomerSchema>(‘Customer’);
, , . id, , , .
, . , , -. , , .
, . — - , . , , . : , , . . :
/**
name: String
String - js -: StringConstructor
*/
const customerSchema = Schema.create({
id: Number,
name: String,
});
. , , . , Number String . : , . :
const customerSchema = Schema.create({
id: 1,
name: 2,
});
. `Schema.create` , . `if (!(property instanceof String)) throw new Error(« , »)`. .
-, , -, . , .
, , .
. , Schema.create.
:
//
type Map<T> = {
[key: string]: T | Map<T>;
};
/**
,
,
.
*/
type NumberType = Template<NumberConstructor, number, 'number'>;
type StringType = Template<StringConstructor, string, 'string'>;
type SymbolType = Template<SymbolConstructor, symbol, 'symbol'>;
type BooleanType = Template<BooleanConstructor, boolean, 'boolean'>;
type DateType = Template<DateConstructor, Date, 'date'>;
interface ArrayType extends Array<ExtractTypeValues<Types>> {};
type Types = {
Number: NumberType;
String: StringType;
Symbol: SymbolType;
Boolean: BooleanType;
Date: DateType;
Array: ArrayType;
};
//
type MapTypes= Map<ApplyTemplate<Types>>;
// -
type Declaration = ExtractInputTypes<MapTypes>;
interface Schema<...> {
// , .
create: <T extends Declaration>(
declaration: ConvertInstanceTypesToConstructorTypes<T>
) => Schema<T>;
};
, . , , , , .
,type CustomerSchema = {
id: number;
name: string;
};
const customerSchema: CustomerSchema = Schema.create({
id: Number,
name: String,
});
. , , .
. any. — any, 100%, , .
, , , . . `Schema.create` . 99% , . , . — ! , .
. , , , . . :
const customerSchema = Schema.create({
id: Number,
name: String,
});
// vscode : id, name
Schema.raw(customerSchema).
//
// .
Schema.raw(customerSchema).id;
// .
Schema.raw(customerSchema).neId;
. :
const customerSchema = Schema.create({
id: Number,
name: String,
});
if (true) {
customerSchema.add({gender: String});
}
// ,
// gender.
// ,
// .
Schema.raw(customerSchema).
, , , . gender, , (, , this !). - . , , .
, . , , . , . .
:
const customerSchema = Schema.create({
id: Number,
name: String,
});
// customerSchema.add({gender: String});
// , .
// :
const customerWithGenderSchema = customerSchema.add({gender: String});
// .
// :
Schema.raw(customerWithGenderSchema).
// id, name, gender
//
Schema.raw(customerSchema).
// id, name
, , . , .
:
const customerSchema = Schema.create({
id: Number,
name: String,
});
const repository = RepositoryManager.create(openApiDriver, {
// config
});
const Customer = Model.create(repository, customerSchema);
Customer.getAll().first().
// ide , id, name gender.
// ,
Customer.getAll().first().age;
// . , ,
// .
getAll .
:
type MapSchemaToDriver<S extends Schema, D extends Driver> =
InferSchemaDeclaration<S> extends SchemaDeclaration
? InferDriverMethods<D> extends DriverTemplate<IMRReader, IMRWriter>
? Repository<InferSchemaDeclaration<S>, InferDriverMethods<D>>
: never
: never;
interface Repository<D extends Driver, S extends Schema> {
get: <T extends DSLTerm>(dsl: ParseDSLTerm<T>) => MapSchemaToDriver<S, D> extends RepositoryTemplate ? ApplyDSLTerm<MapSchemaToDriver<S, D>, T> : Error<’type’, ‘Type error’>;
}
, :
«, B, A, , , , A. . - ».
. .
«» , , . , .
. , . :
« , *--.*, . , id . - , , ».
.
2.5
, , , . .
, — , , , . . , , .
, . , , , , . , , .
ODM ORM — IMR (Isomorphic Model Representation)
, , API . , . , select-where , .
— , , .
. . . , , , . , , , , .
, — , — , - .
.
3
— () , , . , , , . . , , , .
- , , « ». , . , — . , , ,
, .
. , , . , , , . . . , . , , , , .
— () , , . . , , , .
, , . . , .
— () , . , , . . , . - , , , , .
, - . , . , .
, , . , . .
4
— () , — . ! ?! , , , , , ODM/ORM - , . , . , , «- , ».
, , . . , , . .
, , - , . — , .
, . -:
/*
.
— .
.
*/
import { View } from '@view';
import { Logger } from '@utils';
// — , .
// .
import { Robot } from '@domain/models';
// ,
//
function foo() {
Robot.fetch({
location: {
area: 2,
free: true,
key: 'f49a6712', // - compile-time checked
}
})
.then(View.Grid.display)
.catch(Logger.error);
}
, . , — js/ts . , . - - , , , — Result .
. - Logger.Error, .
/*
:
,
- .
-
.
:
*/
import { Schema, Model } from 'imr';
import { Logger } from '@utils';
import { View } from '@view';
import { robotSchema } from '@domain/schemas';
import { batteryStationService } from '@domain/infrastructure/services/batteryStation';
import { Robot } from '@domain/models';
import { batteryNode } from '../services/nodeNames';
//
// , , ,
// , «»
const robotSchemaWithBattery =
robotSchema
.add('battery', Schema.union('li-ion', 'silicon'))
.remove('speed');
// ,
// :
function foo(nodeName) {
// -: -,
if (nodeName === batteryNode) {
// ,
const CustomerRobot = Model.create(robotSchemaWithBattery, batteryStationService);
CustomerRobot
// .
// , , 'li-notIon'
.fetch({ filter: { battery: 'li-ion' } })
// .
// , , , .
// , ,
// , .
.then(View.Grid.display)
.catch(Logger.error)
} else {
Robot
.fetch()
.then(View.Grid.display)
.catch(Logger.error)
}
}
5
— () , , , - , . , - , , .
.
, , — . . , — , , . . , , — , .
, : . . .
— . , .
: rcanedu, arttom