<?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: Amin Abbasi</title>
    <description>The latest articles on DEV Community by Amin Abbasi (@amin4193).</description>
    <link>https://dev.to/amin4193</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%2F783881%2Fb767847d-7d5e-4185-b2ff-14e3eee3ace3.jpg</url>
      <title>DEV Community: Amin Abbasi</title>
      <link>https://dev.to/amin4193</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amin4193"/>
    <language>en</language>
    <item>
      <title>Escaping "Recursive Hell": Building ERP Hierarchies with ArangoDB Edges</title>
      <dc:creator>Amin Abbasi</dc:creator>
      <pubDate>Sun, 01 Mar 2026 15:26:58 +0000</pubDate>
      <link>https://dev.to/amin4193/escaping-recursive-hell-building-erp-hierarchies-with-arangodb-edges-751</link>
      <guid>https://dev.to/amin4193/escaping-recursive-hell-building-erp-hierarchies-with-arangodb-edges-751</guid>
      <description>&lt;p&gt;This is the story of a mistake I kept making until I stopped fighting my database. When I started designing our ERP’s parts categorization and warehouse tracking, I reached for the "safe" choice: standard document references. It nearly broke the system.&lt;/p&gt;

&lt;p&gt;I’m sharing this pattern not because it’s the only way, but because it’s the shift in thinking that finally made our complex relationships manageable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: When &lt;code&gt;parent_id&lt;/code&gt; Hits a Wall
&lt;/h2&gt;

&lt;p&gt;Initially, I modeled our hierarchy using a simple &lt;code&gt;parent_id&lt;/code&gt; field on each document. It worked for a week. Then the requirements got real:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deep Category Inheritance:&lt;/strong&gt; If "Electronics" is marked as &lt;code&gt;hazardous&lt;/code&gt;, every sub-category three levels down needs to know that—unless an intermediate sub-category overrides it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Warehouse Layouts:&lt;/strong&gt; We needed to map zones, racks, shelves, and bins. A user might ask, "What storage facility is this specific bin in?" requiring a climb up an unpredictable number of levels.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;My first mistake:&lt;/strong&gt; We tried to handle the recursion in TypeScript. We were writing complex loops to walk the tree manually, which was slow, error-prone, and a nightmare to debug.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We were essentially reinventing a graph engine inside our API, and doing a poor job of it."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Shift: Edges as First-Class Citizens
&lt;/h2&gt;

&lt;p&gt;I realized that the relationship &lt;em&gt;is&lt;/em&gt; the data. By moving to ArangoDB &lt;strong&gt;edge collections&lt;/strong&gt;, we separated the "what" (the category) from the "where" (the hierarchy).&lt;/p&gt;

&lt;p&gt;An edge is simply a document with &lt;code&gt;_from&lt;/code&gt; and &lt;code&gt;_to&lt;/code&gt; references. This allows for elegant traversals using AQL's &lt;code&gt;OUTBOUND&lt;/code&gt; or &lt;code&gt;INBOUND&lt;/code&gt; syntax.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthvf21b3mooe84qdo9yj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthvf21b3mooe84qdo9yj.png" alt="Edge Diagram" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Inheritance Trick"
&lt;/h3&gt;

&lt;p&gt;The real win was solving the "property override" problem. By walking the graph &lt;strong&gt;inbound&lt;/strong&gt; (up towards the root), reversing the results, and then collecting them, we let child properties naturally "clobber" parent ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* Get inherited properties with overrides */
LET cat = CONCAT('part_cats/', @categoryKey)

LET props = FLATTEN(
  REVERSE(
    FOR doc IN 0..100 INBOUND cat part_cats_links
      FOR p IN part_cats_props
        FILTER p.cat == doc._key
        RETURN p
  )
)

RETURN (
  FOR p IN props
    COLLECT name = p.name INTO group = p
    RETURN LAST(group[*]) /* The most specific property wins */
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Warehouse Hierarchies: Speed at Scale
&lt;/h2&gt;

&lt;p&gt;We applied the same logic to the warehouse. Relationships between storage facilities, zones, racks, and shelves are stored in &lt;code&gt;w_node_links&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;To find all bins in a Zone:&lt;/strong&gt; One &lt;code&gt;1..100 OUTBOUND&lt;/code&gt; traversal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;To find the Root Facility for a Shelf:&lt;/strong&gt; One &lt;code&gt;1..100 INBOUND&lt;/code&gt; traversal filtered by the storage prefix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even with thousands of nodes, the performance remained snappy because ArangoDB’s engine handles the join-heavy lifting internally rather than shipping raw data to our API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Expert Notes: The "Gotchas" &amp;amp; Trade-offs
&lt;/h2&gt;

&lt;p&gt;While I’m a fan of this approach, it’s not a silver bullet. If you’re going to implement this, keep these expert-level constraints in mind:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cycle Detection
&lt;/h3&gt;

&lt;p&gt;In a graph, it is possible for a user to accidentally make "Category A" a parent of "Category B," while "Category B" is already a parent of "Category A."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Risk:&lt;/strong&gt; Infinite loops in your traversal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Fix:&lt;/strong&gt; Use &lt;code&gt;OPTIONS { uniqueVertices: 'path' }&lt;/code&gt; in your AQL and implement a check in your API to prevent circular links during &lt;code&gt;POST&lt;/code&gt; or &lt;code&gt;PATCH&lt;/code&gt; operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Why not Postgres CTEs?
&lt;/h3&gt;

&lt;p&gt;You could do this in SQL using Recursive Common Table Expressions (CTEs). However, we found that as the logic grew—adding metadata to the &lt;em&gt;links themselves&lt;/em&gt; or handling multiple parentage—the SQL syntax became significantly more difficult to maintain than AQL traversals.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. When NOT to use this
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flat Data:&lt;/strong&gt; If your hierarchy is only ever one level deep, the overhead of an edge collection isn't worth it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static Hierarchies:&lt;/strong&gt; If your tree never changes, Materialized Paths (storing the path as a string like &lt;code&gt;/electronics/parts/ics&lt;/code&gt;) might be faster for simple read-only lookups.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bounded Traversals:&lt;/strong&gt; Always specify a depth (e.g., &lt;code&gt;1..100&lt;/code&gt;) to prevent accidental runaway queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indexing:&lt;/strong&gt; &lt;code&gt;_from&lt;/code&gt; and &lt;code&gt;_to&lt;/code&gt; are indexed by default, but if you filter edges by metadata, ensure you add secondary indexes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Documentation:&lt;/strong&gt; My teammates didn't "get it" until I showed them Mermaid diagrams. In a graph-based system, the diagram is the documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern turned our most complex ERP features into our most stable ones. If you're struggling with deep hierarchies, stop writing loops and start thinking in edges.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Next Step:&lt;/strong&gt; You can explore the full implementation in my &lt;a href="https://github.com/amin-abbasi/arango-edge-patterns" rel="noopener noreferrer"&gt;public repo&lt;/a&gt;. I'd love to hear how you handle property overrides in your own systems!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>erp</category>
      <category>typescript</category>
      <category>arangodb</category>
    </item>
  </channel>
</rss>
