<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexey Balekhov</title>
    <description>The latest articles on DEV Community by Alexey Balekhov (@balekhov).</description>
    <link>https://dev.to/balekhov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3968383%2F89216ca6-a76b-4972-91c4-06dafaeea585.jpg</url>
      <title>DEV Community: Alexey Balekhov</title>
      <link>https://dev.to/balekhov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/balekhov"/>
    <language>en</language>
    <item>
      <title>Super Schema Architecture</title>
      <dc:creator>Alexey Balekhov</dc:creator>
      <pubDate>Fri, 05 Jun 2026 07:44:20 +0000</pubDate>
      <link>https://dev.to/balekhov/super-schema-architecture-5155</link>
      <guid>https://dev.to/balekhov/super-schema-architecture-5155</guid>
      <description>&lt;p&gt;This article describes an approach to application development based on a single, highly detailed format for describing domain entities and contracts. It provides practical examples of how such descriptions can be used and demonstrates how declarative schemas can bring some of the conveniences of low-code platforms into conventional full-code programs.&lt;/p&gt;

&lt;p&gt;The approach is independent of any particular technology stack and is especially useful in heterogeneous systems. For that reason, the examples draw from a variety of programming languages and technologies: Java, Python, TypeScript, REST, GraphQL, and Protocol Buffers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Every program operates on data. Most projects contain numerous declarative descriptions of that data: object-oriented class definitions, database table schemas, GraphQL and protobuf schemas, and so on. Each of these descriptions serves a specific technical purpose.&lt;/p&gt;

&lt;p&gt;A PostgreSQL table schema exists so that the database can store data, while an ORM schema exists so that application code can interact with the database. Although both describe the same information, they are neither equivalent nor interchangeable. An ORM schema may not support defining database constraints, yet it may contain auxiliary metadata that does not exist in the database itself. Nevertheless, the logical connection between the two schemas is obvious. At least column names and types must remain consistent.&lt;/p&gt;

&lt;p&gt;Example. Prisma ORM schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model users {
  id         String   @id @db.Uuid @default(uuid(7))
  email      String   @unique
  birth_date DateTime @db.Date
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;birth_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;birth_date_in_past&lt;/span&gt;
        &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;birth_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Prisma schema cannot express the constraint. However, it contains additional information about the default value for the &lt;code&gt;id&lt;/code&gt; column, which is used by the ORM. Apart from that, the schemas largely duplicate each other.&lt;/p&gt;

&lt;p&gt;Similar reasoning applies to metadata that is less tightly coupled. Consider an OpenAPI specification in the same project. API entities may have nothing in common with database models. The connection between the database and the API is implemented in endpoint code, and that code can be arbitrarily complex. However, when an endpoint merely passes data through without transformations, the API and database schemas may again duplicate metadata.&lt;/p&gt;

&lt;p&gt;OpenAPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/users/{id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
            &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uuid&lt;/span&gt;

      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;User&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
                &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;email&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;birth_date&lt;/span&gt;
                &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uuid&lt;/span&gt;
                  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                  &lt;span class="na"&gt;birth_date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike the ORM example, this schema describes an independent entity. Yet it is not entirely disconnected from the database model. Since data is passed through unchanged in this simplified example, any metadata applicable to the database model is equally applicable to the endpoint response. On the one hand we may not want to expose all of that metadata in the API contract. However, if we do need to duplicate some of it, maintenance becomes a problem. For example, detailed documentation attached to a database column may also need to be provided and kept up to date for API consumers.&lt;/p&gt;

&lt;p&gt;My point is that the practice of completely separating database and API entities is often considered correct only because our tools do not always allow us to express more sophisticated relationships between them. We will return to this later. For now, let us focus on simple CRUD systems where entities at different layers effectively duplicate one another.&lt;/p&gt;

&lt;p&gt;Naturally, the software industry has long recognized the problem of metadata duplication and has produced countless converters and code generators: OpenAPI or JSON Schema to programming language structures and back, XML Schema to XML validation code, even &lt;a href="https://github.com/bitquery/protobuf-sql" rel="noopener noreferrer"&gt;protobuf schemas to SQL&lt;/a&gt;. In my view, these solutions focus on isolated problems rather than the bigger picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSA Level 0: Metadata Consolidation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Validation, Transport, and Storage
&lt;/h3&gt;

&lt;p&gt;Consider the same CRUD system, but now with a web interface. Imagine the simplest possible implementation: a form that directly writes data into the database. What path does a piece of data travel through such an application? Let us examine a single birth date field.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The process starts with a UI form. The date picker stores its value as a JavaScript &lt;code&gt;Date&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;The frontend may validate the value immediately. A birth date must always be in the past.&lt;/li&gt;
&lt;li&gt;The data is converted into a format suitable for API transport. The in-memory &lt;code&gt;Date&lt;/code&gt; object is serialized into a JSON string.&lt;/li&gt;
&lt;li&gt;The backend converts the JSON into a more convenient in-memory representation. The date string becomes a backend language object.&lt;/li&gt;
&lt;li&gt;The data is validated.&lt;/li&gt;
&lt;li&gt;The backend converts the value into a representation suitable for database storage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;React fragment example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setBirthDate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;birthDate&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Birth date must be in the past&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;birth_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"birth_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1990-05-20"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;PatchUserRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;birth_date&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;birth_date&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isBefore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ValidationException&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;birth_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How many distinct forms of metadata exist along this path?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Frontend in-memory representation (&lt;code&gt;Date&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Validation rules.&lt;/li&gt;
&lt;li&gt;JSON representation (formatted string).&lt;/li&gt;
&lt;li&gt;Backend in-memory representation (&lt;code&gt;LocalDate&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Database schema (&lt;code&gt;DATE&lt;/code&gt; column).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At least four different representations are involved. Yet all of them are logically connected. Within a project, we can agree on how dates should be represented in each form and formally define the transformations between them. To do so, we introduce our own &lt;strong&gt;super schema&lt;/strong&gt; format that captures all relevant metadata in one place.&lt;/p&gt;

&lt;p&gt;What should such a super schema look like? It could be YAML, a dedicated DSL, or simply declarations in a general-purpose language. For further examples I will use an imperative pseudocode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User = Model({
  key: 'id',
}, {
  id: Uuid({ autogenerate: 'v7' }),
  email: Unique(Email()),
  birth_date: PlainDate({ forbidFuture: true }),
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact format is not important. Completeness is. From this schema we can automatically generate TypeScript types, validation rules, JSON serialization logic, backend DTOs, and validation code. Since the description is technology-agnostic, we are less dependent on specific frameworks. For example, we could generate Zod schemas and start using them on the frontend without changing the source definition.&lt;/p&gt;

&lt;p&gt;These super schemas become the single source of truth for all derived metadata. They define and synchronize in-memory representations, encoding rules, validation rules, and database storage types. This ensures consistency and type safety throughout the entire data lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Presentation Logic
&lt;/h3&gt;

&lt;p&gt;The information contained in a super schema can also be useful at the presentation layer.&lt;/p&gt;

&lt;p&gt;If a field represents a phone number, we can automatically format it for display (&lt;code&gt;+1 234-567-890&lt;/code&gt;). For input fields, we can automatically apply masks and request a numeric keyboard on mobile devices. This can be achieved by mapping data types to view components.&lt;/p&gt;

&lt;p&gt;Such an approach enables reusable UI abstractions that work for any data described by a schema. For example, forms and data tables can be generated automatically.&lt;/p&gt;

&lt;p&gt;At first glance, super schemas may appear to violate principles such as the Single Responsibility Principle. One declaration influences multiple architectural layers. The inclusion of presentation-related metadata may seem especially surprising.&lt;/p&gt;

&lt;p&gt;In reality, the super schema does not mix architectural layers. It merely groups together facts about a particular type of data. Each layer consumes the information relevant to it.&lt;/p&gt;

&lt;p&gt;The key idea is that a super schema describes data as completely as possible, not how the data should be used. Once we understand the nature of the data, we can leverage that knowledge across many different domains.&lt;/p&gt;




&lt;h2&gt;
  
  
  SSA Level 1: Derived Super Schemas
&lt;/h2&gt;

&lt;p&gt;So far we have considered a degenerate case where information simply flows from one representation to another.&lt;/p&gt;

&lt;p&gt;Even under this limitation, SSA can be useful in large systems. I once worked on the logging subsystem of a streaming platform. To eliminate bugs caused by schema mismatches, we generated Java, Swift, Python, and TypeScript DTOs, protobuf schemas, ClickHouse and Impala schemas and migrations from a single source of truth.&lt;/p&gt;

&lt;p&gt;Let us now move to the general case.&lt;/p&gt;

&lt;p&gt;The fact that a super schema &lt;em&gt;can&lt;/em&gt; be used across all layers does not mean that the &lt;em&gt;same&lt;/em&gt; schema should be used everywhere.&lt;/p&gt;

&lt;p&gt;Real systems transform and enrich data between user input, API calls, and database writes. Therefore, schemas for presentation, transport, and storage may differ. To handle this, we need schema transformation operations that allow one schema to be derived from another.&lt;/p&gt;

&lt;p&gt;At a minimum, we need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Selecting a subset of fields from an existing schema.&lt;/li&gt;
&lt;li&gt;Combining fields from multiple schemas into a new schema.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These operations allow us to express relationships between data models while avoiding duplication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserInApi = Compose(
  Pick(User, ['id', 'email', 'birth_date']),
  Pick(Profile, ['avatar']),
  Object({ age: Integer })
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;UserInApi&lt;/code&gt; is derived from &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Profile&lt;/code&gt; and extended with an additional &lt;code&gt;age&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;Although all of these are super schemas, nothing prevents us from using &lt;code&gt;UserInApi&lt;/code&gt; exclusively at the API layer while using &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Profile&lt;/code&gt; only for persistence and, for example, caching.&lt;/p&gt;




&lt;h2&gt;
  
  
  SSA Level 2: API Contracts
&lt;/h2&gt;

&lt;p&gt;The next logical step is to describe API contracts by combining input schemas, output schemas, and endpoint metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;userPatchEndpoint = createEndpoint({
    method: 'PATCH',
    path: '/users/:userId',
    params: { userId: Uuid() },
    body: Pick(User, ['birth_date']),
    result: UserInApi,
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This abstraction is similar to endpoint definitions in OpenAPI, GraphQL, or gRPC and can be used to generate those specifications. But this information can also be leveraged at runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;perform additional serialization transformations when the transport layer does not support them natively;&lt;/li&gt;
&lt;li&gt;abstract over transport protocols and support multiple protocols simultaneously;&lt;/li&gt;
&lt;li&gt;preserve metadata for purposes beyond transport and validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a frontend application could use such a specification not only to render forms but also to perform requests from generic code.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSA Level 3: Semantic Operations
&lt;/h2&gt;

&lt;p&gt;We can go even further.&lt;/p&gt;

&lt;p&gt;Instead of merely describing endpoints, we can define endpoint categories. For example, a list endpoint accepts filtering, sorting, and pagination parameters and returns a collection of entities with a specific schema. So these abstractions represent contracts at a higher semantic level. They describe not only transport details but also the meaning of an operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;userListEndpoint = createListEndpoint(User)
userUpdateEndpoint = createUpdateEndpoint(User)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This higher-level abstractions can, for example, be used to automate CRUDL workflows throughout the system. On the frontend, generic runtime logic can automatically update caches when a request is made to the update endpoint or build a table with server-side sorting, filtering, and pagination by the list endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ListEndpointTable&lt;/span&gt; &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userListEndpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For simple cases on the backend, we can automatically generate a handler for the endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;handleListEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userListEndpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the same time, developers remain free to drop down to lower levels of abstraction whenever necessary. A list endpoint can still have a completely custom backend implementation or be consumed by custom frontend logic.&lt;/p&gt;

&lt;p&gt;This gives us many of the benefits associated with low-code platforms while preserving full control over application behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;At present, I am not aware of any mature technology designed specifically for describing data independently of a particular use case.&lt;/p&gt;

&lt;p&gt;Most metadata formats support extensions (OpenAPI, Protobuf, GraphQL, and others), but their focus on a specific technology and its type system makes them inconvenient for higher-level abstractions and schema derivation. A more promising candidate may be Microsoft's recently introduced &lt;a href="https://typespec.io/" rel="noopener noreferrer"&gt;TypeSpec&lt;/a&gt;. However, it remains heavily focused on network APIs.&lt;/p&gt;

&lt;p&gt;As a result, one obvious challenge is implementing metadata transformations yourself. While code generators are usually straightforward, early-stage projects may lack the resources to build them, and adopting SSA in mature systems can require significant effort.&lt;/p&gt;

&lt;p&gt;In large organizations, the benefits may not be immediately obvious to individual developers. Metadata duplication is often scattered across different parts of a system and therefore does not appear to be a single problem. SSA may seem relevant only to CRUD applications and admin panels. However, in real systems, data spreads across many surfaces: internal services, integrations, analytical pipelines, and more. The cost of propagating knowledge about data grows because it generates communication overhead. In such environments, having a single source of truth and a rigorous description of all data flows may be the most valuable consequence of SSA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I tried to keep this article grounded in practical problems commonly encountered in mainstream software development. These problems are often treated as unavoidable and their impact tends to be underestimated.&lt;/p&gt;

&lt;p&gt;At the same time, I wanted to demonstrate how a more declarative approach can unlock possibilities we might not otherwise consider. The description of SSA Level 3 only scratches the surface; many potential applications remain outside the scope of this article.&lt;/p&gt;

&lt;p&gt;I believe that the availability and completeness of metadata represent an important step forward in software engineering practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Know your data.&lt;/strong&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;I am actively seeking a senior or lead software engineering role. If you think my experience could be a good fit for your team, feel free to reach out.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>architecture</category>
      <category>api</category>
      <category>database</category>
    </item>
  </channel>
</rss>
