<?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: dumbestprogrammer</title>
    <description>The latest articles on DEV Community by dumbestprogrammer (@dumbestprogrammer).</description>
    <link>https://dev.to/dumbestprogrammer</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%2F3409342%2F242f723a-dc71-4770-a7bb-33e077e10d55.png</url>
      <title>DEV Community: dumbestprogrammer</title>
      <link>https://dev.to/dumbestprogrammer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dumbestprogrammer"/>
    <language>en</language>
    <item>
      <title>Cyclic Dependency in a Microservice Architecture</title>
      <dc:creator>dumbestprogrammer</dc:creator>
      <pubDate>Sun, 03 Aug 2025 17:05:36 +0000</pubDate>
      <link>https://dev.to/dumbestprogrammer/cyclic-dependency-in-a-microservice-architecture-1e6c</link>
      <guid>https://dev.to/dumbestprogrammer/cyclic-dependency-in-a-microservice-architecture-1e6c</guid>
      <description>&lt;p&gt;So I tried to merge two Spring-Boot microservices. &lt;br&gt;
The build exploded with cyclic-dependency errors. &lt;br&gt;
And here’s why and how I fixed it.&lt;/p&gt;

&lt;p&gt;This post here documents the architectural flow, technical choices, &lt;br&gt;
what I attempted, and where I hit a wall. &lt;br&gt;
And eventually, my solution for it - not by brute force, but by rethinking everything.&lt;/p&gt;


&lt;h2&gt;
  
  
  Architecture Overview &amp;amp; Current Working Flow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt; - Java, SpringBoot, REST, Feign, Microservices architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service A - port 8082:&lt;/strong&gt;&lt;br&gt;
This service handles the file inputs and parsing structured information from them, followed by external data enrichment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input Processing (Service-A)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;POST&lt;/strong&gt; &lt;a href="http://localhost:8082/api/upload" rel="noopener noreferrer"&gt;http://localhost:8082/api/upload&lt;/a&gt;&lt;br&gt;
Accepts a structured file input(pdf/text) and returns a session-specific UUID to track the processing pipeline.&lt;/p&gt;

&lt;p&gt;Primary Analysis (Service-A)&lt;br&gt;
&lt;strong&gt;GET&lt;/strong&gt; &lt;a href="http://localhost:8082/api/report/%7BuploadId%7D?force=true" rel="noopener noreferrer"&gt;http://localhost:8082/api/report/{uploadId}?force=true&lt;/a&gt;&lt;br&gt;
Triggers the data extraction and enrichment process, returning a fully processed result (a report).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service B - port 8083:&lt;/strong&gt;&lt;br&gt;
Now this service consumes the enriched output from Service A, runs deeper analyses, and produces a refined result that’s used in the platform’s final output.&lt;/p&gt;

&lt;p&gt;Secondary Analysis (Service-B)&lt;br&gt;
&lt;strong&gt;GET&lt;/strong&gt; &lt;a href="http://localhost:8083/api/trust/report/%7BuploadId%7D" rel="noopener noreferrer"&gt;http://localhost:8083/api/trust/report/{uploadId}&lt;/a&gt;&lt;br&gt;
This service uses a feign client to communicate with service-A, pulls the enriched output from it, analyzes it, and extracts what it needs for its own computations, does the computations, &lt;br&gt;
and returns a different, more refined, insight-driven result (a report of its own which is like an extension to the service-A's report).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each service communicates over HTTP using Feign clients and follows a strict separation of concerns to maintain modularity and scalability. At this point, Service B depended on Service A, but not the other way around ... ensuring a unidirectional and loosely-coupled architecture.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  What I Wanted to Do?
&lt;/h2&gt;

&lt;p&gt;I wanted to enhance the user experience and simplify the frontend integration.&lt;br&gt;
Wanted to merge two separate backend responses into one. &lt;br&gt;
Instead of having the client call two different endpoints, the idea was:&lt;/p&gt;

&lt;p&gt;“&lt;em&gt;Why not have Service A internally call Service B, fetch the secondary analysis result, and return both in a single consolidated response?&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;And being honest, thinking about it ..at first glance this felt like a smart, clean solution.&lt;br&gt;
I mean the flow was extremely straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User requests a report from Service-A&lt;/li&gt;
&lt;li&gt;Service-A does its thing and calls Service-B to get its report.&lt;/li&gt;
&lt;li&gt;Service-A combines both reports&lt;/li&gt;
&lt;li&gt;User receives unified response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But in practice? It &lt;strong&gt;broke&lt;/strong&gt; everything.&lt;br&gt;
As to fetch trust data from Service-B, I created a ReportClient Feign interface in Service-A that would talk to Service-B.&lt;br&gt;
Attempted to inject that client inside the report generation logic in Service-A.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adding Feign Client in Service-A:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@FeignClient(name = "service-b-client", url = "${feign.clients.service-b.url}")
public interface FeignServiceBClient {
    @GetMapping("/api/trust/report/{uploadId}")
    TrustReportDTO getTrustReport(@PathVariable("uploadId") String uploadId);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;And then enhancing the Report Builder:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Transactional
public CombinedReportDTO generate(Session session, boolean force, boolean includeB) {
    CombinedReportDTO report = // existing logic for Service A’s analysis

    if (includeB) {
        TrustReportDTO trustReport = feignServiceBClient.getTrustReport(session.getId());
        report.setSecondaryAnalysis(trustReport);
    }
    return report;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The approach was methodical and followed established patterns for microservice composition, but now I was facing &lt;strong&gt;cyclic-dependency&lt;/strong&gt; errors.&lt;/p&gt;


&lt;h2&gt;
  
  
  Cyclic Dependency Reality Check &amp;amp;
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Technical Deep Dive
&lt;/h2&gt;

&lt;p&gt;Upon implementation, I encountered cyclic dependency warnings in the IDE and module dependency errors during compilation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;java: Annotation processing is not supported for module cycles.&lt;br&gt;
Please ensure that all modules from cycle [service-b, service-a] are excluded from annotation processing&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The root cause became clear:&lt;/strong&gt; &lt;br&gt;
&lt;em&gt;The Existing Architecture Already Had a Cycle.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In simple terms:&lt;/strong&gt;&lt;br&gt;
Service A was now calling Service B&lt;br&gt;
But elsewhere, Service-B was already depending on Service-A (&lt;code&gt;e.g., to fetch analysis output&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; &lt;em&gt;cyclic dependency between modules&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Cycles Break??&lt;/strong&gt;&lt;br&gt;
This wasn’t just a conceptual issue; it was a real, deeply technical roadblock. Understanding why cycles break in Spring Boot and Java helped clarify both architectural and implementation-level decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Bean Wiring Cycles (Spring ApplicationContext)&lt;/strong&gt;&lt;br&gt;
Spring’s &lt;code&gt;ApplicationContext&lt;/code&gt; attempts to resolve all &lt;code&gt;@Autowired&lt;/code&gt;dependencies during startup.&lt;br&gt;
When &lt;code&gt;Service-A&lt;/code&gt; has a &lt;code&gt;Feign client&lt;/code&gt; for &lt;code&gt;Service-B&lt;/code&gt;, and &lt;code&gt;Service-B&lt;/code&gt; also has a &lt;code&gt;Feign client&lt;/code&gt; for &lt;code&gt;Service-A&lt;/code&gt;, Spring is unable to determine a safe initialization order.&lt;br&gt;
This results in a circular dependency exception, halting application startup, even though the services might be logically valid at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Module-Level Dependency Violations (Java Build System)&lt;/strong&gt;&lt;br&gt;
Even if Spring could theoretically resolve things at runtime, Java itself doesn't allow compile-time cycles across modules  (&lt;code&gt;Build-time cycle&lt;/code&gt;).&lt;br&gt;
When both services depend on each other's DTOs, interfaces, or configs, the build system can't resolve the graph.&lt;br&gt;
This breaks the Java Module System and flags cyclic imports, preventing compilation altogether.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Annotation Processing Conflicts (Feign, Lombok, etc.)&lt;/strong&gt;&lt;br&gt;
Tools like Feign, Lombok, and Spring Boot’s auto-configuration all rely on annotation processing at compile-time.&lt;br&gt;
&lt;code&gt;Cyclic references&lt;/code&gt; confuse these processors or cause them to behave unpredictably. &lt;br&gt;
Sometimes even resulting in infinite loops or partially generated code.&lt;br&gt;
Since Feign itself relies on Spring’s annotation processor, this made the situation worse when both services had Feign clients pointing to each other.&lt;/p&gt;


&lt;h2&gt;
  
  
  My Realization
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Microservice Boundaries&lt;/strong&gt;&lt;br&gt;
The fact that users want unified data doesn't mean services should be tightly coupled. &lt;br&gt;
The separation of concerns that led to two services was correct, as both the services, Service-A and Service-B, are actually very different domains with different computational requirements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;(i)&lt;/strong&gt; While to me, it initially seemed very user-friendly to have Service-A call Service-B and then return a single merged report..
I realized this would introduce tight coupling and violate the core principles of microservice architecture. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because Service-B is intentionally designed to consume the output from Service-A, process it independently and return its own specialized analysis.. its own unique report. &lt;br&gt;
And if I reverse this by making Service-A depend on Service-B .. &lt;/p&gt;

&lt;p&gt;It would not only create a compile-time cyclic dependency (due to mutual Feign clients and module cycles)&lt;br&gt;
but it will also destroy the clean one-way flow between services.&lt;/p&gt;

&lt;p&gt;More critically, it would reduce fault tolerance....so if Service-B goes down then Service-A is going to break too, and as a result the user would see no output at all. &lt;br&gt;
With the current design, both services can continue to operate and return partial results independently.&lt;/p&gt;

&lt;p&gt;It made me understand the boundaries between microservices and what &lt;br&gt;
"&lt;strong&gt;one-way&lt;/strong&gt;" actually means in request flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This setup of mine violated a key architectural principle:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;Microservices should follow a one-directional flow&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I knew it, but all this made it extremely clear to me that - One microservice should not call the other and also expect to be called back by it, and also that here the - Flow is One-Way, Not Two-Way :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[User/Frontend] → Service-A → Service-B → returns Info → Service-A returns Final Report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don’t need Service B to send anything back "later" to Service A. It’s a request-response not an event stream or push system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;(ii)&lt;/strong&gt; I also considered merging both services into one, but again, that would completely defeat the purpose of having a microservices architecture in the first place.....&lt;br&gt;
where each service is responsible for a single, focused concern and can evolve or scale independently. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;(iii)&lt;/strong&gt; And lastly, if I tried to "decouple" Service-B from Service-A just so Service-A could take over and call B instead.. &lt;br&gt;
It would force me to duplicate all of Service-A's logic inside Service-B, just to recreate the same output that I was getting before. &lt;br&gt;
which would be not only redundant and error-prone but obviously a clear example of a really bad design. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, trying to return a merged report directly from Service-A breaks modularity, reduces resilience, increases maintenance cost, and discards the very architecture I’m trying to preserve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Synchronous Integration Complexity&lt;/strong&gt;&lt;br&gt;
Attempting synchronous service-to-service communication introduces failure coupling and dependency management complexity that often outweighs the benefits almost all the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Feign&lt;/strong&gt;&lt;br&gt;
At one point, I caught myself thinking, &lt;br&gt;
"&lt;code&gt;Maybe Feign can receive requests too? I mean, it’s everywhere else...&lt;/code&gt;"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoiler:&lt;/strong&gt; It can’t. &lt;br&gt;
Feign sends HTTP requests; it doesn’t receive them. &lt;br&gt;
That’s the controller’s job.&lt;br&gt;
Feign is a declarative REST client.&lt;br&gt;
In simple words, Feign is for calling other services, not receiving from them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Aggregation Layer Responsibility&lt;/strong&gt;&lt;br&gt;
I think in a proper microservice setup, the merging of data shouldn't happen inside the services. &lt;br&gt;
That kind of aggregation feels more like something the frontend or an API gateway should handle.&lt;br&gt;
I feel backend services should remain focused on their core functionalities.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Solution
&lt;/h2&gt;

&lt;p&gt;So instead of breaking or violating my clean architecture ..&lt;br&gt;
&lt;strong&gt;I asked myself :&lt;/strong&gt;&lt;br&gt;
“&lt;code&gt;What is the real user need here?&lt;/code&gt;”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The answer was simple :&lt;/strong&gt;&lt;br&gt;
And it's obvious &lt;code&gt;the user just wants to see both reports for the same upload ID, preferably in the same UI session&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I decided to keep both services fully independent and decoupled.&lt;br&gt;
Let the frontend make two parallel API calls and stitch the results together.&lt;br&gt;
One to get the report-A from Service-A.&lt;br&gt;
Another to get the report-B from Service-B.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Two Independent Endpoints:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;GET&lt;/strong&gt; &lt;code&gt;/api/service-a/report/{uploadId}   # Output from Service-A&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;GET&lt;/strong&gt; &lt;code&gt;/api/service-b/report/{uploadId}   # Output from Service-B&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frontend Implementation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [citationData, trustData] = await Promise.allSettled([
    fetch(`/api/report/${uploadId}`),
    fetch(`/api/trust/report/${uploadId}`)
]);

// Display unified UI with both datasets
renderUnifiedReport(citationData.value, trustData.value);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;UI Design Approaches&lt;/strong&gt;&lt;br&gt;
To present both reports cleanly while keeping the services decoupled, &lt;br&gt;
I explored a few frontend strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Side-by-side panels:&lt;/strong&gt;&lt;br&gt;
Display both the Service-A and Service-B reports together, letting users compare at a glance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tabbed interface:&lt;/strong&gt;&lt;br&gt;
Let users toggle between the two reports, each pulled independently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Progressive loading:&lt;/strong&gt;&lt;br&gt;
Show the Service-A's report instantly, while the Service-B's report loads in the background as it's fetched.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both reports are keyed by the same uploadId, so there's no need for Service A to embed Service B’s output. &lt;br&gt;
The frontend can query each service directly and combine them on the UI side.&lt;/p&gt;

&lt;p&gt;It kept the services decoupled, preserved the architecture I had, and honestly made things easier to debug and scale later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benefits of the Final Architecture
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Zero cyclic dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;services remain completely independent.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Independent scalability&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each service can scale based on its computational needs.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Fault tolerance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;failure in one service doesn't break the other.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Parallel loading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;both reports can be fetched simultaneously for better performance&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Cleaner codebases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no complex aggregation logic.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Just because users want one response doesn’t mean the backend has to force it. &lt;strong&gt;User needs ≠ service design&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keeping microservices independent isn’t just theory — clean separation wins in the long run: better scaling, better isolation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turns out, the frontend’s more powerful than we give it credit for. Parallel fetches, merging responses — totally doable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Technical Constraints are not bad I guess, haha - &lt;br&gt;
That annoying cycle error? It actually pushed me toward a much cleaner architecture.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion: Let It Be Separate
&lt;/h2&gt;

&lt;p&gt;What started as a small integration task turned into a much bigger architectural decision.&lt;br&gt;
The &lt;code&gt;cyclic dependency&lt;/code&gt; wasn’t just an error ..it was a reality check. &lt;br&gt;
It made me realize these services were never meant to be stitched together so tightly in the first place.&lt;/p&gt;

&lt;p&gt;Sure, maybe I could’ve slapped an &lt;code&gt;@Lazy&lt;/code&gt; annotation somewhere and made the error go away. But who knows what kind of mess that would’ve caused later?&lt;/p&gt;

&lt;p&gt;So I stopped forcing it. I let each service do what it does best, and moved the merging logic to the frontend. &lt;br&gt;
Cleaner, simpler, and way more in line with what microservices are supposed to be.&lt;/p&gt;

&lt;p&gt;Looking back, I’m glad it broke. &lt;br&gt;
If it hadn’t, I probably would’ve tightly coupled things that were better off independent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; If your services push back when you try to force them together… maybe listen.&lt;br&gt;
They’re probably already doing their jobs just fine — on their own.&lt;/p&gt;







&lt;p&gt;If you've hit similar issues with service dependencies or Feign loops, &lt;br&gt;
I'd love to hear how you handled it, drop a comment or message me.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>microservices</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
