Escrevemos o código mais útil em nossa vida, mas jogamos no lixo. Com a gente



Pendurei um saco de pancadas no porão, enfiei uma foto de um gerente típico e enfiei o alto-falante dentro para tocar frases que me irritavam. Por exemplo, uma pêra diz: “Os negócios não precisam do seu código perfeito. Ele precisa resolver o problema para que o lucro cubra os custos. Se você precisar de govnokod para isso, haverá govnokod. E estou começando a me abraçar.

Recentemente, adicionei uma nota à pêra: "Os tipos são difíceis e desnecessários". Nesse momento, bati com tanta força que minha mão quase quebra. Porque chega de mim. Há alguns meses, experimentei um dos casos mais flagrantes da minha carreira.

Meu amigo Antokha me pediu para ajudar com uma solução para uma grande e grande corporação. Concordei e mergulhávamos no abismo sem fundo do absurdo corporativo, da trituração, da guerra com colegas incompreensíveis e de todo tipo de injustiça. Como não podemos dizer nada, falamos sobre tipos para que esse lixo nunca se repita com ninguém.

1


- (Antokha rcanedu ) Fui encarregado do desenvolvimento de uma ferramenta de plataforma interna - uma biblioteca para expressar entidades de software na forma de modelos orientados a objetos e para o trabalho uniforme com serviços de API. Ou seja, uma ferramenta para interagir com dados que trafegam da fonte até a tela e vice-versa.

Dessa forma, uma enorme quantidade de formatação, conversão, cálculo e transformação. Estruturas enormes, hierarquias complexas, inúmeras conexões de tudo com tudo. Nessas redes, é muito fácil se perder. Você vê dados e não sabe o que pode ser feito e o que não pode ser feito. Gaste muito tempo para descobrir isso. Isso é muito viciante. Apenas uma boa descrição dos tipos resolve o problema.

Devido à falta de digitação adequada em muitas soluções, fica mais difícil obter o mesmo comportamento do programa em tempo de execução e tempo de compilação. Os tipos devem dar total confiança de que tudo é igual e também acontecerá durante a execução. Os testes podem fazer o mesmo. É melhor confiar em ambos. Mas se você escolher entre tipos e testes - os tipos são muito mais confiáveis ​​e mais baratos.

Quando você cria uma frente, existem duas fontes de problemas - o usuário e o back-end. Tudo é simples com o usuário - existem muitas bibliotecas e estruturas convenientes abstraindo da E / S do usuário (reagir, angular, vue e outras).

Há outra história em interação com o back-end. Suas espécies são numerosas e as realizações são trevas. Você não pode definir uma abordagem padrão para descrever dados. Por esse motivo, muletas como “normalização da estrutura de dados” foram inventadas, quando todos os dados recebidos são reduzidos a uma estrutura rígida e, se algo der errado, começam as exceções ou operações anormais. Isso deve acelerar e simplificar o desenvolvimento, mas na verdade exige muita documentação, diagramas UML, descrição dos recursos.

Um problema de arquitetura na interação das partes do cliente e do servidor surgiu porque o frontend amadureceu. Tornou-se um negócio completo e não apenas um layout no topo do back-end. Anteriormente, apenas os desenvolvedores do lado do servidor solicitavam a interação cliente-servidor. Agora, as frentes e as costas são forçadas a negociar e trabalhar em estreita colaboração. Precisamos de uma ferramenta que permita estruturar o trabalho com os serviços de fonte de dados da API, para evitar a perda da verdade dos dados e, ao mesmo tempo, simplificar outras transformações. Esse problema deve ser resolvido por toda a comunidade.

- (Phil) Se você é um back-end, tem muitas soluções para adultos e práticas de modelagem de dados para o aplicativo. Por exemplo, em C #, você apresenta uma classe de modelo de dados. Você pega alguma coisa, algum EntityFramework, ele fornece os atributos com os quais você reveste seus modelos. Você diz a Lib como chegar à base. Então você usa sua interface para manipular esses dados. Essa coisa é chamada ORM.

No front-end, não decidimos qual a melhor forma de fazer isso - pesquisamos, tentamos, escrevemos artigos ridículos, depois refutamos tudo, começamos do zero e ainda não tomamos uma única decisão.

- (Antoha) Tudo o que escrevi antes tinha um grande problema - especialização restrita. Cada vez que a biblioteca foi desenvolvida do zero e cada vez foi aprimorada para um tipo de interação cliente-servidor. Tudo isso acontece devido à falta de digitação adequada.

Acredito que, sem a digitação estática, é difícil imaginar uma biblioteca universal para trabalhar com APIs e expressões de domínio. Haverá muita reflexão, conterá uma enorme quantidade de documentação, será cercado por diferentes aplicativos com uma indicação de práticas para uma ou outra forma. Isso não é uma simplificação.

Uma boa ferramenta universal desse tipo deve fornecer uma imagem completa dos dados em qualquer fatia, para que você sempre saiba exatamente como trabalhar com esses dados e por que eles são necessários.

- (Phil) Precisamos de uma lib que nos permita fornecer uma descrição e controle detalhados de cada entidade, para obter dados para essa entidade a partir de diferentes recursos com uma interface diferente, da API REST e json-rpc ao graphQL e NQL. O que permitirá manter a crescente base e estrutura do código em ordem e rigor. Simples e intuitivo de usar. Fornecer uma descrição completa e precisa do estado das entidades a qualquer momento. Queremos abstrair os módulos de nossos usuários da camada de dados, tanto quanto possível.

Primeiro, analisamos o existente. Nós não gostamos de nada. Por alguma razão, todas as bibliotecas para trabalhar com dados são feitas em js ou com muitas delas. Estes estragam tudo. Eles ignoram os desenvolvedores, dizendo que essa biblioteca não ajudará muito, que você não poderá navegar pelos tipos de dados, e que não poderá expressar suas conexões. As coisas ficam ainda piores quando várias APIs de tipos diferentes são usadas ou são heterogêneas.

Todas essas bibliotecas não foram suficientemente protegidas por tipos. Portanto, eles criam mais problemas do que resolvem. É mais fácil não usá-los, mas tomar suas decisões específicas de domínio.

Portanto, em vez da linguagem simplista em que a tarefa se baseava, decidimos criar uma muito mais poderosa e abstrata - adequada para tudo.E acreditamos incrivelmente em nossa correção, porque somente dessa maneira coisas realmente boas são criadas.



2


- (Antoha) Como sempre acontece, eu estava esperando por todos os tipos de acesso para poder entrar no repositório da empresa. E isso pode durar várias semanas. No meu caso, levou apenas um. Nesse momento, com base na minha experiência na criação de bibliotecas semelhantes, decompus a tarefa e estimei a linha do tempo.

O problema é que antes, como todo mundo, eu tomava decisões altamente especializadas. O trabalho em uma ferramenta universal acarretou problemas adicionais. O sistema de tipos saiu extremamente complexo, e nem eu nem mais ninguém tinha experiência em projetar isso. Pior, os desenvolvedores ao meu redor não tinham ideia da digitação estática.

Mas comecei a fazer o que achava certo. No Daily, contei o que estava fazendo e por quê, mas, em princípio, ninguém me entendeu. Minhas perguntas à equipe sobre o problema sempre permaneceram sem resposta. Era como se eu não existisse. Cara que faz algo muito complicado e incompreensível.

Eu entendi que o javaScript não funcionará aqui de forma alguma. Eu precisava de um PL com um modelo de digitação poderoso, excelente interação com javaScript, ter uma grande comunidade e um ecossistema sério.

- (Phil) Estou esperando há muito tempo que Antoha entenda o charme do typeScript.

- (Antoha) Mas há vários problemas que o deixam louco. Há digitação, mas ainda não tenho uma correspondência perfeita entre a execução do programa e a execução pelo usuário. O texto datilografado parece complicado a princípio. Ele tem que suportar. Você sempre quer pegar e jogar algum objeto ou qualquer outro. Gradualmente, você mergulha na linguagem, em seu sistema de tipos, e aqui uma coisa interessante começa a acontecer. Torna-se mágico. Tudo pode ser digitado.

- (Phil) Pela primeira vez em nossas vidas, nos reunimos e começamos algo.

O primeiro passo é o usuário descrever o esquema de dados. Nós nos perguntamos como fica. Algo assim:

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

Source: https://habr.com/ru/post/pt454774/


All Articles