Skip to main content

Command Palette

Search for a command to run...

How I Built a Zero-Trust Boundary Engine for Flutter Clean Architecture: Introducing Zuq CLI ⚡

Updated
6 min read
How I Built a Zero-Trust Boundary Engine for Flutter Clean Architecture: Introducing Zuq CLI ⚡
M

I am Software Engineer(Mobile) who is particularly enthusiastic about leveraging modern technologies to create user-friendly and scalable applications. I thrive in collaborative environments and enjoy working with diverse teams to deliver exceptional software solutions. Follow along as I share my knowledge, insights, and experiences on Hashnode. I look forward to connecting with fellow developers, learning from the community, and contributing to the growth of the tech industry.

Picture this: It's 11:45 PM. The production deployment is in fifteen minutes, and a critical auth bug just reared its head. In a caffeinated rush, a developer bypasses the domain repository interfaces, imports auth_api_repository_impl.dart directly into a UI widget, patches the bug, and pushes.

The build passes, tests run successfully, and the hotfix is live. Disaster averted, right?

Fast forward six months. Your simple Flutter app has grown into a large product. But compile times have tripled, widgets are tightly coupled to database drivers, and refactoring a single REST endpoint requires changes in seven different folders. What started as a pristine "Clean Architecture" codebase has slowly rotted into a spaghetti-infused monolith.

This is the erosion problem. Clean Architecture is relatively easy to set up on Day 1, but it is notoriously difficult to maintain as a team grows.

To solve this, I built Zuq CLI, a flexible, boundary-enforcing scaffold and audit tool for Flutter Clean Architecture.

In this post, we’ll dive into why I built it, how it works under the hood (including a topological dependency resolver and static import analysis), and how you can use it to build bulletproof Flutter apps.

The Problem: The High Cost of Broken Boundaries

Clean Architecture separates code into three distinct layers:

  1. Domain: The core business logic (Entities, Use Cases, Repositories interfaces). It must be pure Dart and completely agnostic of UI and databases.

  2. Data: The data sources, API clients, and repository implementations.

  3. Presentation: The UI widgets and state management (BLoC, Riverpod, etc.).

These layers must adhere to a strict Dependency Rule: dependencies can only point inward.

Presentation → Domain ← Data
        └─────── ✕ ──┘
           (No direct imports!)

In the real world, developers make mistakes. A misplaced import, such as import 'package:app/features/auth/data/models/user_model.dart' in a presentation widget, silently breaks the boundary. Because Dart doesn't prevent this compile-time import, these violations accumulate unnoticed until the codebase becomes a tangled web of circular dependencies.

I wanted a tool that would:

  • Scaffold clean slices automatically.

  • Prevent me from adding modules out-of-order.

  • Statically audit imports in CI to fail the build if a boundary is violated.

Welp, here comes Zuq and its prowess:

1. Scaffolding in Seconds: zuq create

Bootstrapping a Flutter project with clean architecture is usually a chore of folder creation (my lazy self found this cumbersome and boring) and boilerplate copying.

With Zuq, you can bootstrap a production-ready template in a single command, configured for your favorite state management and routing solution (Currently supports go_router and auto_route).

dart pub global activate zuq_cli
zuq create my_fintech_app --state bloc --router go_router

If you don't supply the flags, Zuq prompts you interactively.

2. Once you scaffold your first feature with zuq add feature auth, the generated folder structure isolates concerns cleanly:


lib/features/auth/
  data/
    models/
    repositories/
  domain/
    entities/
    repositories/
    usecases/
  presentation/
    pages/
    state/ <- Pre-populated with selected state boilerplate 
    widgets/

3. Smart Module Installation with Topological Resolution

Adding a core module, like networking or storage, often requires adding a package to pubspec.yaml, writing config singletons and ensuring dependencies don't loop.

Zuq handles this with zuq add module <module_name>. But the secret sauce is its Topological Dependency Resolver.

Suppose you want to install the networking module. In Zuq, the dependency graph looks like this:

  • networking depends on storage (for auth tokens) and analytics (for logging telemetry).

  • storage and analytics have no dependencies.

When you run:

zuq add module networking

Zuq doesn't just download package dependencies. It parses the dependency graph, runs a Depth-First Search (DFS) topological sort with cycle detection, and resolves the correct install order:

Resolved installation order: analytics → storage → networking

Under the Hood: The Resolver

The CLI builds an internal graph of modules and uses a recursion stack check to find circular dependencies before starting any write operations:

void dfs(String node) {
  if (recursionStack.contains(node)) {
    throw CircularDependencyException('Circular dependency detected: $node');
  }
  if (visited.contains(node)) return;

  recursionStack.add(node);
  final deps = _graph[node] ?? [];
  for (final dep in deps) {
    dfs(dep);
  }

  recursionStack.remove(node);
  visited.add(node);
  resolved.add(node); // Post-order collection
}

Once resolved, Zuq executes code injection templates (using mason under the hood) and installs the required pub dependencies sequentially.

4. The Boundary Guard: zuq doctor

This is the core differentiator of Zuq. It is a static analyzer that audits folder boundaries. It answers the question: Are my layer boundaries being respected?

When you run:

zuq doctor

Zuq scans every Dart file in your lib/features directory, ignores comments, parses all import statements, and canonicalizes relative paths to project-level imports. It then validates imports against these three absolute rules:

Source Layer Target Layer Allowed? Rationale
Domain Presentation Banned Core business logic should never know about UI layouts.
Domain Data Banned Domain must be database/network agnostic.
Presentation Data Banned Widgets must depend on Usecase interfaces, not API clients or models.
Data Presentation Banned Data repository implementations have no business with visual controls.

How It Looks When Someone Violates a Boundary

If a developer tries to sneak a data import into a presentation widget:

$ zuq doctor
Auditing project architectural boundaries for my_app...

Violation in lib/features/auth/presentation/pages/login_page.dart:12
  Layer: presentation → Target Layer: data
  Import: package:my_app/features/auth/data/datasources/auth_remote_source.dart
  Reason: The Presentation layer should not depend directly on the Data layer (use domain/repositories instead).

✗ Architecture audit failed: 1 violation found

By exiting with code 1, it is custom-built to integrate directly into Git pre-commit hooks or your continuous integration pipeline.

5. Zero-Trust Architecture in CI

To enforce this boundary verification automatically across your team, you can add zuq doctor to your GitHub Actions. It runs in milliseconds because it does static analysis on text imports rather than spinning up heavy compiler services:

name: Architecture Audit

on: [push, pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: subosito/flutter-action@v2
        with:
          channel: stable
      - name: Install Zuq CLI
        run: dart pub global activate zuq_cli
      - name: Run boundary audit
        run: zuq doctor

If any developer tries to push a PR with direct boundary violations, the workflow fails, preventing bad practices from ever merging into main.

How to Get Started

Zuq is open-source and active on Pub.dev.

To give it a spin:

  1. Install Zuq:

    dart pub global activate zuq_cli
    
  2. Create your project:

    zuq create my_awesome_app --state riverpod --router auto_route
    
  3. Generate your first feature slice:

    zuq add feature user_profile
    
  4. Audit and enjoy a clean codebase:

    zuq doctor
    

I built Zuq to turn Clean Architecture from a guide in a book into something your CI pipeline can actually enforce. Try it on your next Flutter project and let me know what you think!

Have features you'd like to see, or custom templates? Check out the source code at GitHub - Morizuq/zuq_cli. Contributions, stars, and feedback are always welcome! 🚀