CMS sin cabeza. ¿Por qué estoy escribiendo el mío?

Hola a todos!

Este reciente artículo me impulsó a escribir esta publicación (la vi ayer).

Vuelva a contar las características principales de Headless / content-first / api-first, etc. No seré CMS, el material está lleno y probablemente muchos ya estén familiarizados con esta tendencia. Y quiero decirles por qué y cómo escribo mi sistema, por qué no pude elegir entre los existentes, qué pienso sobre otros sistemas que he encontrado antes y qué perspectivas veo para todo esto. La ficción será voluminosa (para el material en dos años), pero intentaré escribir más interesante y útil. A quién le importa, por favor, debajo del gato.

En general, la historia es muy larga y trataré de contarla primero. Ya sea para aclarar cuáles son las verdaderas razones para crear este motor por su cuenta, o simplemente porque sin esto será difícil en el terreno explicar por qué lo estoy haciendo de esta manera, y no de alguna manera.

Pero, para empezar, escribiré brevemente personalmente los principales criterios de selección para el Headless-CMS moderno, por qué aún no podía elegir una solución preparada para mí. Solo para que la gente no se detenga a leer muchas hayas, sin entender lo que eventualmente se contará.

Brevemente: quería que todo estuviera en un solo lugar: tanto la parte posterior como la frontal (y no esto o lo otro), y la API GraphQL, y que la base de datos sea administrada y mucho más, incluido el botón "Make Beautiful". No he encontrado esto. Yo tampoco he hecho esto todavía, pero en general ha resultado bastante y, lo más importante, me permite hacer proyectos reales.

Y así, mi enfoque difícilmente puede llamarse científico y justificado. El hecho es que generalmente escribo algo muy a menudo. Me gusta programar aquí. Y hace dos años (y antes de eso otros 8 años) me senté en el MODX CMF (bajo el cual también inventé muchas de mis muletas). Y durante tres años comenzamos un proyecto a gran escala, en virtud del cual, me pareció, podía usar MODX. Pero resultó que no podía ... La razón principal era que era una startup sin ningún requisito técnico, con un montón de ideas que cambiaban y se complementaban todos los días (y varias veces al día). Y ahora, cada vez que, bajo una nueva idea, era necesario agregar alguna entidad nueva, registrar / cambiar campos para los existentes, crear / eliminar / cambiar relaciones entre estas entidades (respectivamente, con un cambio en la estructura de la base de datos), tengo en algún momento Comenzó a tomar varias horas para cambiar estas entidades. De hecho, además del hecho de que era necesario registrar estos cambios en el esquema, era necesario cambiar la base de datos (casi manualmente), actualizar la API, reescribir el código del programa, etc., etc. En consecuencia, el frente tuvo que actualizarse bajo todo esto. Como resultado, decidí que deberíamos buscar algo nuevo, más conveniente, que de alguna manera simplificara todo esto. Aclararé una vez más que en ese momento yo era un backend de php, así que no se sorprenda ni se ría de que comencé a descubrir varios constructores front-end, menos procesadores, npm, etc. etc. Pero de todos modos, gradualmente en nuestro proyecto apareció un frente en react + less, una API en GraphQL y un servidor en express.

Pero no todo era tan color de rosa como parecería a muchos ahora. Déjame recordarte, esto fue hace más de dos años. Si ha estado en la web moderna de JS por menos de dos años, le aconsejo que lea este artículo: N razones para usar la aplicación Create React (habr). Demasiado perezoso, en resumen: con el advenimiento de los scripts de reacción, no puede molestarse en configurar el paquete web, etc. Todo esto pasa a un segundo plano. Los tipos buenos ya han configurado el paquete web para que la mayoría de los proyectos reactivos estén casi garantizados para trabajar en él, y el desarrollador final se centró directamente en la programación del producto final, en lugar de configurar un montón de dependencias, cargadores, etc. Pero esto es más tarde. Y antes de eso, solo tenía que configurar este paquete web, seguir la actualización del montón de todo lo que voló con él para ponerse al día, etc. etc. Pero esto es solo una parte del trabajo, solo esencialmente el frente. Y también necesitas un servidor. Y también necesitas una API. Y también necesita SSR (representación del lado del servidor), que, por cierto, react-script todavía no proporciona, por lo que sé. En general, todo era mucho más complicado que ahora, no había mucho, y todos se paralizaron lo mejor que pudieron. ¿Y cómo me mude entonces?

Solo imagina:

  • Configuración nativa del paquete web por separado para el frente y el servidor.
  • Implementación propia de SSR, para que async funcione normalmente con react-server, y los estilos inmediatamente listos lleguen, y se indexen normalmente, y se proporcionen los estados del servidor para las páginas no encontradas.
  • No redux. Bueno, no me gustó redux de inmediato. Me gustó la idea de usar mi flujo de reacción nativo (aunque tuve que reescribirlo un poco para mí).
  • Esquemas y solucionadores GraphQL prescritos manualmente, sin despliegue automático de la base de datos (el servidor API se utilizó como medio para un sitio MODX).
  • Sin react-apollo / apollo-client, etc. Todo se escribe de forma independiente con solicitudes a través de fetch, repositorios en un navegador basado en flujo personalizado.

Como resultado: hasta ahora, una de las primeras versiones de este tiene un proyecto con una asistencia de más de 500, y en la temporada (invierno) 1000-1700 estudiantes únicos por día. Tiempo de actividad 2 meses. Esto se debe a que reinicié manualmente el servidor después de una actualización preventiva de software. Y antes de este reinicio, el tiempo de actividad era de más de 6 meses. Pero lo más interesante es el consumo de memoria. Actualmente hay casi 700 megabytes de proceso js. Sí, sí, también me estoy riendo contigo :) Por supuesto, esto es mucho. Y antes de eso hice un poco de prevención y mejoré este indicador. Anteriormente, había un total de 1000M + por proceso ... Sin embargo, funcionó y fue bastante tolerable. Y antes de que Google cambiara los algoritmos de PageSpeed ​​Insights en noviembre, el sitio tenía una métrica de rendimiento 97/100. Prueba

Una conclusión intermedia basada en este proyecto basada en un sistema que se desarrolló aún más sin este proyecto (el proyecto se quedó atrás):

Pros

  1. La API del proyecto se ha vuelto más flexible mediante el uso de GraphQL, y la cantidad de solicitudes del servidor se ha reducido significativamente.
  2. El proyecto tiene acceso a una gran cantidad de componentes en npm.
  3. La gestión de proyectos se ha vuelto más transparente mediante el uso de dependencias, git, etc.
  4. Las secuencias de comandos y los estilos incorporados son ciertamente más agradables que un montón de secuencias de comandos separadas en sitios antiguos, cuando no sabes qué puedes eliminar de este zoológico sin consecuencias (y a menudo ves varias versiones de un error en un sitio).
  5. El sitio se ha vuelto más interactivo, las páginas funcionan sin reiniciar, volver a las páginas vistas anteriormente no requiere llamadas repetidas al servidor.
  6. La edición de datos se realiza directamente en la página, según el principio de "editar lo que ves y dónde ves", sin ningún panel de administración separado.

Contras (principalmente para el desarrollador)

  1. Todo es muy complicado. De verdad. Simplemente no es realista conectar a un desarrollador externo al proyecto. Yo mismo apenas podía entender qué y cómo funciona y de dónde crecen mis piernas. Si nos fijamos en la página 3 de las ventajas, donde se dice acerca de la transparencia, entonces la transparencia solo está en que si enganchas algo en algún lugar, puedes ver de inmediato lo que está roto (los scripts no se compilan, etc.), sino por commits y diffs Puedes encontrar dónde se enganchó eso. Bueno, si lograste agregar algo nuevo y funciona, al menos entiendes claramente que sí, que todo salió bien. Pero en general sigue siendo un infierno infernal.
  2. Dificultades con el almacenamiento en caché. Más tarde, descubrí apollo-client por mí mismo. Y antes de eso, como dije, escribí mis almacenamientos basados ​​en flujo. Debido a estos almacenamientos, era posible obtener los datos necesarios para renderizar desde diferentes componentes, pero el volumen de caché en el lado del cliente era muy grande (cada conjunto de entidades típicas tenía su propio repositorio). Como resultado, fue difícil verificar si el objeto se solicitó antes o no (es decir, si vale la pena hacer una solicitud al servidor para encontrarlo), si todos los datos relacionados están disponibles, etc.
  3. Dificultades con los esquemas, la estructura de la base de datos y los solucionadores (funciones API para recibir / modificar datos). Como dije, escribí esquemas manualmente, y también resolvers. En lo que resolvió, intenté proporcionar almacenamiento en caché y procesamiento de solicitudes anidadas y otras sutilezas. En ese momento tuve que profundizar en la esencia y el código del programa GraphQL. Lo bueno es que generalmente entiendo bastante bien cómo funciona GraphQL, cuáles son sus ventajas y desventajas, y cómo cocinarlo mejor. La desventaja es que, por supuesto, no puede escribir todas esas comodidades y bollos escritos por comandos como Apolo en uno. Como resultado, cuando descubrí Apollo, por supuesto, con gran placer comencé a usar sus componentes (pero principalmente en el frente, te diré por qué a continuación).

En general, este proyecto que utiliza tecnologías obsoletas es personalmente mío al 100%, por lo que puedo permitirme abandonarlo hasta tiempos mejores. Pero hay otros proyectos para los que tuve que ir más allá y desarrollar la plataforma. Y varias veces tuve que reescribir todo desde cero. Además, hablaré con más detalle sobre las tareas individuales que encontré y qué soluciones desarrollé y apliqué como resultado.

Esquema primero. Primero el circuito, y luego todo lo demás

Un sitio (interfaz web, cliente ligero, etc.) es toda la visualización de información (bueno, gestión de la información, si está permitida, y la funcionalidad lo permite). Pero primero, de todos modos, una base de datos (tablas, columnas, etc.). Habiendo encontrado varios enfoques diferentes para trabajar con la base de datos en mi camino, me gustó más el enfoque del primer esquema. Es decir, describe el esquema de entidades y tipos de datos (manualmente o a través de la interfaz), implementa el esquema e inmediatamente tiene los cambios descritos en la base de datos (las tablas / columnas se crean / eliminan, así como las relaciones entre ellas). Dependiendo de la implementación, también generará todas las funciones de resolución necesarias para administrar estos datos. Sobre todo en esta dirección, me gustó el proyecto prisma.io .

Con su permiso, ya que incluso en el centro no he visto un solo artículo sobre el prisma, llamaré la atención un poco sobre ellos, ya que el proyecto es realmente muy interesante, y sin ellos no hubiera tenido una plataforma que me hiciera tan feliz. . En realidad, es por eso que llamé a mi plataforma prisma-cms, porque prisma.io juega un papel muy importante en ella.

En realidad, prisma.io es un proyecto SaaS, pero con una gran advertencia: ponen casi todo lo que hacen en un github. Es decir, puede usar sus servidores por una tarifa muy razonable (y configurar su propia base de datos y API en cuestión de minutos), o puede implementar completamente todo en casa. En este caso, el prisma debe dividirse lógicamente en dos partes separadas importantes:

  1. Prisma-server, es decir, el servidor donde también gira la base de datos.
  2. Prisma-cliente. Esencialmente también es un servidor, pero en relación con la fuente de datos (servidor prisma) es un cliente.

Ahora intentaré explicar esta situación confusa. En general, la esencia del prisma es que al usar un único punto final de API, puede trabajar con diferentes fuentes de datos. Sí, aquí cualquiera dirá que a todos se les ocurrió GraphQL y que no se necesita prisma aquí. En general, todos tendrán razón, pero hay un punto serio: GraphQL solo define los principios y el trabajo general, pero por sí solo, no proporciona trabajo con las fuentes de datos finales listas para usar. Él dice: "Puede crear una API para describir qué solicitudes pueden enviar los usuarios, pero la forma en que maneja estas solicitudes depende de usted". Y el prisma también, por supuesto, usa GraphQL (por cierto, y muchas otras cosas, incluidos varios productos apollo). Pero el prisma más a esto solo proporciona trabajo con la base de datos. Es decir, al describir el esquema y su despliegue, las tablas y columnas necesarias (así como las relaciones entre ellas) se crearán inmediatamente en la base de datos especificada, e incluso generarán de inmediato todas las funciones CRUD necesarias. Es decir, con un prisma, no solo obtienes un servidor GraphQL, sino una API de trabajo completa que te permite trabajar inmediatamente con la base de datos. Por lo tanto, Prisma-server proporciona una base de datos e interacción con ella, y prisma-client le permite escribir sus resoluciones y enviar solicitudes a prisma-server (o en otro lugar, incluso para algunos servidores prisma). Y resulta que solo puede implementar prisma-client por su cuenta (y SaaS prisma.io se usará como prisma-server), y puede implementar prisma-server por su cuenta, y generalmente no depende de un prisma de ninguna manera, eso es todo el tuyo

Aquí he elegido un prisma para mí, como base de mi plataforma. Pero luego tuve que hacerla girar para obtener una plataforma completa.

1. Combinar esquemas


En ese momento, el prisma no podía combinar circuitos. Es decir, la tarea es la siguiente:

Tiene un modelo de usuario descrito en un módulo.

type User { id: ID! @unique username: String! @unique email: String @unique } 

y en otro módulo

 type User { id: ID! @unique username: String! @unique firstname: String lastname: String } 

Como parte de un proyecto, desea combinar estos dos esquemas automáticamente para obtener el resultado

 type User { id: ID! @unique username: String! @unique email: String @unique firstname: String lastname: String } 

Pero entonces este prisma no pudo hacer. Resultó implementar esto usando la biblioteca merge-graphql-schemas .

Trabaja con un servidor prisma arbitrario.


En el prisma, la configuración se escribe en un archivo de configuración especial. Si desea cambiar la dirección del servidor de prisma utilizado, debe editar el archivo. Un poco, no agradable. Quería hacer posible la URL para especificar en el comando, por ejemplo endpoint = http: // endpoint-address yarn deploy (inicio de hilo). Eso fue asesinado durante varios días ... Pero ahora puedes usar un proyecto de prisma para cualquier número de puntos finales. Por cierto, hasta ahora prisma-cms funciona fácilmente incluso con una base de datos local, incluso con servidores de prisma SaaS.

Módulos / Complementos


Esto generalmente no fue suficiente. Como dije, la tarea principal del prisma es proporcionar trabajo con varias bases de datos. Y hacen un excelente trabajo de esto. Ya admiten trabajar con MySQL, PostgreSQL, Amazon RDS y MongoDB, varios tipos más de fuentes en camino. Pero no proporcionan ninguna infraestructura modular. Hasta ahora no hay mercado o algo así. Solo hay unos pocos espacios en blanco típicos. Pero no puede elegir dos o tres de varios espacios en blanco e instalar en un proyecto. Tendremos que elegir uno. Quería que fuera posible instalar un número diferente de módulos en el proyecto final, y que al implementar los circuitos y los resolvers se alegraran y obtuvieran un proyecto único con la funcionalidad total. Y aunque todavía no hay una interfaz gráfica, ya hay más de dos docenas de módulos de trabajo y componentes que se pueden combinar en el proyecto final. Aquí inmediatamente decidiré un poco sobre las definiciones personales: un módulo es lo que está instalado en la parte posterior (expandiendo la base de datos y la API), y un componente es lo que está instalado en la parte frontal (para agregar varios elementos de interfaz). Hasta ahora, no hay una interfaz gráfica para conectar módulos, pero no es difícil para mí escribir de esta manera (esto no se hace a menudo):

  constructor(options = {}) { super(options); this.mergeModules([ LogModule, MailModule, UploadModule, SocietyModule, EthereumModule, WebrtcModule, UserModule, RouterModule, ]); } 

Después de agregar nuevos módulos, es suficiente simplemente realizar una implementación con un comando nuevamente y eso es todo, aquí ya tenemos nuevas tablas / columnas y funcionalidad aumentada.

5 frontal, sensible a los cambios en el backend


Esto no fue suficiente en absoluto. Esto será seguido por una digresión. El hecho es que todos los CMS que vi por primera vez en la API dicen "Somos increíbles para proporcionar la API, y tú atornillas el frente que deseas". Esto es lo que "atornillan lo que quieras" en realidad significa "molesta como quieras". Exactamente lo mismo que los marcos de la interfaz de usuario dicen, "mira qué botones geniales somos y hacemos todo eso, y confúndete con el back-end". Eso siempre mataba. Solo quería encontrar un CMS completo escrito en javascript, usando GraphQL y proporcionando tanto atrás como adelante. Pero no encontré uno así. Realmente quería que los cambios de API se percibieran de inmediato en el frente. Y para esto, se completaron varios subpasos:

5.1 Generando fragmentos de API


En el frente, los fragmentos del archivo de esquema se registran en las solicitudes. Cuando la API se reconstruye en el servidor, también se genera un nuevo archivo JS con fragmentos de API. Y en las solicitudes se escribe así:

 const { UserNoNestingFragment, EthAccountNoNestingFragment, NotificationTypeNoNestingFragment, BatchPayloadNoNestingFragment, } = queryFragments; const userFragment = ` fragment user on User { ...UserNoNesting EthAccounts{ ...EthAccountNoNesting } NotificationTypes{ ...NotificationTypeNoNesting } } ${UserNoNestingFragment} ${EthAccountNoNestingFragment} ${NotificationTypeNoNestingFragment} `; const usersConnection = ` query usersConnection ( $where: UserWhereInput $orderBy: UserOrderByInput $skip: Int $after: String $before: String $first: Int $last: Int ){ objectsConnection: usersConnection ( where: $where orderBy: $orderBy skip: $skip after: $after before: $before first: $first last: $last ){ aggregate{ count } edges{ node{ ...user } } } } ${userFragment} `; 

5.2 Un contexto para todos los componentes


React 16.3 presenta una nueva API de contexto . Lo hice para que en los componentes secundarios en cualquier nivel pueda acceder a un solo contexto sin enumerar los tipos deseados del contexto, sino simplemente especificando static contextType = PrismaCmsContext y obteniendo todos los encantos a través de este-> contexto (incluido el cliente API, el esquema , solicitudes, etc.).

5.3 filtros dinámicos


También realmente quería hacerlo. GraphQL le permite crear consultas complejas con una estructura anidada. También quería que los filtros fueran dinámicos, formados a partir del esquema API, y que nos permitieran crear condiciones anidadas. Esto es lo que sucedió:


5.4 Creador de sitios web


Y finalmente, lo que me faltaba era un editor de sitio externo, es decir, un diseñador. Quería que el servidor tuviera solo un mínimo de acciones para realizar, y todo el diseño final debería hacerse en el frente (incluida la configuración de enrutamiento, generación de selecciones, etc.). Este es un tema para un artículo separado, porque entre otras cosas, también escribí mi editor de wysiwyg crutchy para él en contenido puro Editable, y hay muchas sutilezas. Si recupero mis derechos y quién estará interesado, escribiré un artículo por separado.

Bueno, finalmente, un breve video de demostración del diseñador en acción. Todavía bastante crudo, pero me gusta.


Terminaré con eso. Todavía no he escrito mucho, lo que me gustaría escribir, pero han pasado muchas cosas. Estaré encantado de comentar.

PD: todos los códigos fuente, incluidos los códigos fuente del sitio en sí, están aquí .

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


All Articles