<?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: Suman Binnala</title>
    <description>The latest articles on DEV Community by Suman Binnala (@suman_binnala_17).</description>
    <link>https://dev.to/suman_binnala_17</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4155673%2F22175bce-2308-4152-891d-3a3456896ead.png</url>
      <title>DEV Community: Suman Binnala</title>
      <link>https://dev.to/suman_binnala_17</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/suman_binnala_17"/>
    <language>en</language>
    <item>
      <title>How Three Developers Built One Monolith Without Git Merge Hell</title>
      <dc:creator>Suman Binnala</dc:creator>
      <pubDate>Thu, 01 Oct 2026 19:03:17 +0000</pubDate>
      <link>https://dev.to/suman_binnala_17/how-three-developers-built-one-monolith-without-git-merge-hell-25g3</link>
      <guid>https://dev.to/suman_binnala_17/how-three-developers-built-one-monolith-without-git-merge-hell-25g3</guid>
      <description>&lt;p&gt;&lt;em&gt;Over four intense days from Saturday, September 26 to Tuesday, September 29, the biggest bottleneck wasn't typing speed—it was integration paralysis on deadline day. Here is how we architected ZenZone so three teammates could build in parallel.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The most common way hackathon teams fail isn't running out of ideas; it’s merging when the clock runs down.&lt;/p&gt;

&lt;p&gt;Teammate A renames a database column. Teammate B changes an API response shape. Teammate C spends four hours trying to get Webpack to compile Teammate A's code. By the final stretch, everyone is stuck in merge conflicts, the database volume has to be wiped, and the frontend is rendering blank white screens.&lt;/p&gt;

&lt;p&gt;When our three-person team kicked off building &lt;strong&gt;ZenZone&lt;/strong&gt; on &lt;strong&gt;Saturday, September 26&lt;/strong&gt;, we made an explicit rule on day one: &lt;strong&gt;We are not building microservices to escape coordination, and we are not touching each other's files until integration.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead, we designed a monolithic architecture specifically partitioned so three people could work in parallel without blocking each other. Here is the blueprint of how we divided the work, the integration collision we had to solve, and the rules that kept us sane through our final submission on &lt;strong&gt;Tuesday, September 29&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. The Division of Responsibilities
&lt;/h3&gt;

&lt;p&gt;Rather than splitting work by feature ("you build teams, I’ll build judging"), which causes people to touch the database, backend, and frontend simultaneously, we split by &lt;strong&gt;architectural domain&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│ PERSON 1: Core Infra, Auth &amp;amp; Normalization                  │
│ • Docker orchestration &amp;amp; Postgres 16 baseline               │
│ • Flyway V1 (Users/Roles) &amp;amp; V2 (Audit Logs)                 │
│ • Spring Security JWT stateless filter                      │
│ • Z-Score normalization math engine                         │
└──────────────────────────────┬──────────────────────────────┘
                               │
┌──────────────────────────────┼──────────────────────────────┐
│ PERSON 2: Submissions, Gallery &amp;amp; Judging                    │
│ • Flyway V3 (Events/Teams), V4 (Submissions), V5 (Rubrics)  │
│ • Submission lifecycle &amp;amp; pre-deadline versioning            │
│ • Judge assignment queue &amp;amp; Conflict of Interest (COI)       │
│ • CSV export service                                        │
└──────────────────────────────┼──────────────────────────────┘
                               │
┌──────────────────────────────┴──────────────────────────────┐
│ PERSON 3: Zero-Build Frontend SPA &amp;amp; Routing                 │
│ • Nginx Alpine static container (Port 3000)                 │
│ • Vanilla ES6 Single-Page Application (Zero npm build step) │
│ • Client API wrapper &amp;amp; state store (authStore.js)           │
│ • Role-based view guards &amp;amp; responsive layout                │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation gave each person a clean, isolated boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Person 1&lt;/strong&gt; set up the database and core security framework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Person 2&lt;/strong&gt; built the core hackathon business logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Person 3&lt;/strong&gt; built the user-facing interface.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. The Contract-First Buffer (&lt;code&gt;docs/API-CONTRACT.md&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;How does Person 3 build a frontend on Sunday morning when Person 2 hasn't finished implementing the judging endpoints?&lt;/p&gt;

&lt;p&gt;Before writing a single line of backend logic, Person 1 and Person 2 wrote &lt;code&gt;docs/API-CONTRACT.md&lt;/code&gt;. It defined the exact HTTP method, route, request payload, and response envelope for every endpoint in the platform.&lt;/p&gt;

&lt;p&gt;Crucially, we agreed on a &lt;strong&gt;universal response envelope&lt;/strong&gt;:&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;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="err"&gt;...&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;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;Because Person 3 had the exact contract from day one, the frontend API client (&lt;code&gt;frontend/src/api/client.js&lt;/code&gt;) was written with transparent unwrapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Person 3 was able to build the entire Judging View, Gallery, and Submission forms without waiting for Person 2's backend services to be deployed.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Numbered Database Migration Ownership
&lt;/h3&gt;

&lt;p&gt;The fastest way to destroy a relational database during a hackathon is having two developers create conflicting migrations. If both create &lt;code&gt;V3__something.sql&lt;/code&gt; with different tables, Flyway crashes on boot and checksums fail.&lt;/p&gt;

&lt;p&gt;In our recorded &lt;code&gt;implementation_plan.md&lt;/code&gt;, we established strict migration version partitioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Person 1 owned &lt;code&gt;V1__init_schema.sql&lt;/code&gt; and &lt;code&gt;V2__audit_log.sql&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Person 2 was strictly assigned &lt;code&gt;V3&lt;/code&gt; onwards&lt;/strong&gt; (&lt;code&gt;V3__events_tracks_teams.sql&lt;/code&gt;, &lt;code&gt;V4__submissions_gallery.sql&lt;/code&gt;, &lt;code&gt;V5__judging_rubric_coi.sql&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nobody was allowed to edit an existing migration file once committed. If a schema needed a change, it had to be an additive migration (&lt;code&gt;V6&lt;/code&gt;, &lt;code&gt;V7&lt;/code&gt;, etc.). Because of this rule, our Docker container ran &lt;strong&gt;13 migrations incrementally from Saturday to Tuesday&lt;/strong&gt; without a single migration collision or corrupted checksum.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. The Real Integration Challenge: BIGINT vs. UUID
&lt;/h3&gt;

&lt;p&gt;Even with clean boundaries, integration always produces a surprise. Ours happened on Monday when merging Person 2's submissions and judging schema into Person 1's infrastructure.&lt;/p&gt;

&lt;p&gt;As documented in our integration log (&lt;code&gt;walkthrough.md&lt;/code&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Rather than performing a blind text replacement of Person 2's UUID &lt;code&gt;VARCHAR(36)&lt;/code&gt; schema, we adapted Person 2's data models to align with Person 1's relational &lt;code&gt;BIGSERIAL&lt;/code&gt;/&lt;code&gt;BIGINT&lt;/code&gt; database schema."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Person 2 had modeled IDs as strings (&lt;code&gt;VARCHAR(36)&lt;/code&gt;) because the organizer's &lt;code&gt;fixtures.json&lt;/code&gt; used string identifiers like &lt;code&gt;prj_01&lt;/code&gt; and &lt;code&gt;evt_01&lt;/code&gt;. But Person 1’s users, events, and audit tables were already using PostgreSQL auto-incrementing &lt;code&gt;BIGINT&lt;/code&gt; primary keys with foreign key constraints.&lt;/p&gt;

&lt;p&gt;If we blindly merged, foreign keys between &lt;code&gt;submissions.created_by&lt;/code&gt; and &lt;code&gt;users.id&lt;/code&gt; would mismatch on type. &lt;/p&gt;

&lt;p&gt;Instead of blowing away Person 1's migrations and restarting the database volume, we solved it architecturally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We kept &lt;code&gt;BIGINT&lt;/code&gt; across all database foreign keys for query speed and relational integrity.&lt;/li&gt;
&lt;li&gt;We stored the original fixture strings in an indexed metadata column (&lt;code&gt;content_hash = "fixture:prj_XX"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;DataSeeder.java&lt;/code&gt;, we wrote an in-memory translation map (&lt;code&gt;Map&amp;lt;String, Long&amp;gt;&lt;/code&gt;) that resolved fixture strings to generated &lt;code&gt;BIGINT&lt;/code&gt; IDs during cold boot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zero database volumes had to be deleted. All previous authentication tokens and seeded records continued working seamlessly.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Decoupling the Frontend: The Zero-Build Bet
&lt;/h3&gt;

&lt;p&gt;Person 3's frontend architecture was our biggest time-saver across the four days. By choosing Vanilla ES6 modules served by Nginx Alpine instead of React or Next.js:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No build step&lt;/strong&gt;: When Person 2 updated a backend route, Person 3 didn't have to wait 2 minutes for a bundle to compile. A browser refresh loaded the raw &lt;code&gt;.js&lt;/code&gt; files immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant Docker boots&lt;/strong&gt;: The frontend container image was 25 MB and started in under a second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No npm dependency hell&lt;/strong&gt;: None of the three developers spent time troubleshooting Node version mismatches or peer dependency errors on their laptops.&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>architecture</category>
      <category>git</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
