Back to Projects

WeatherWise AI: A Case Study in Architectural Refactoring

This project showcases the strategic refactoring of a cloud-native application, transforming a complex microservice proof-of-concept into a robust, maintainable, and performant monolithic service ready for automated deployment.

Screenshot of WeatherWise AI: A Case Study in Architectural Refactoring

Technologies Used

Google Cloud RunGoogle GeminiFastifyTypeScriptJestOpen-Meteo APIGeocode Maps API

The Initial Spark: Questioning the 'As-Is' Architecture

My involvement began with a simple request: to understand the application's architecture. The initial diagrams revealed a system composed of two distinct microservices: a main application backend and a separate Gemini service for generating AI summaries.

While functional, I immediately questioned the validity of this approach. My architectural intuition suggested that for the scale and scope of this project, the added complexity of a microservice architecture was not providing value. It introduced network latency, operational overhead, and a deployment dependency between two services that were, in reality, tightly coupled. I concluded that the architecture was unnecessarily complicated and that a simpler, more direct approach would yield a better result.

The Strategic Pivot: A Case for a Well-Structured Monolith

Based on this analysis, I charted a new course: a significant architectural pivot to refactor the application into a single monolithic service. This decision was driven by first-principles of software design: reducing complexity, improving performance, and lowering costs. The goal was to create a 'to-be' architecture that was lean, efficient, and easier to reason about.

The Execution: A Disciplined, Multi-Stage Refactoring

With a clear architectural goal, I executed a methodical refactoring process, applying senior engineering best practices at each stage.

  1. Service-Oriented Design & The Facade Pattern: I untangled the business logic from the web server by designing and implementing a dedicated service layer, encapsulating all external API interactions (LocationService, WeatherService, GeminiService). These services act as Facades, providing a simple, clean interface to the application while hiding the complex machinery of authentication, network requests, and error handling.
  2. Dependency Injection for Testability: Crucially, the new services were designed to be testable. Instead of creating their own dependencies, dependencies like the HTTP client were injected into their constructors. This decoupling was the key that unlocked the ability to perform comprehensive unit testing.
  3. Test-Driven Cleanup & Verification: With a testable architecture in place, I developed a full suite of unit tests using Jest and axios-mock-adapter. This wasn't just about validation; the testing process itself acted as a quality gate, revealing dead code, unused dependencies, and subtle bugs in the implementation. This iterative cycle of testing and fixing was instrumental in achieving a clean, reliable codebase.
  4. Process Automation & Cleanup: The final touch was to professionalize the deployment process. I analyzed the existing manual PowerShell scripts and the cloudbuild.yaml file. I identified the automated Cloud Build pipeline as the superior, repeatable solution. I updated the Cloud Build configuration to match the new monolithic architecture, and decisively removed the now-obsolete manual scripts, ensuring a clean and unambiguous path to production.

The Final Software Architecture

The result of this process is a codebase with a clear, logical, and maintainable internal structure. It is a monolith, but it is not a 'big ball of mud.' It is a well-structured system with clear boundaries and responsibilities.

Conclusion: More Than Code, A Mindset

This project is a showcase of an engineering mindset that values clarity, simplicity, and robustness over unnecessary complexity. It demonstrates the ability to critically analyze an existing architecture, propose a bold but reasoned alternative, and execute that vision through disciplined, test-driven development and the application of established design patterns.

Architectural Limitations and Future Work

A key principle of senior-level architecture is understanding the trade-offs and limitations of any design. While this application is now robust, tested, and maintainable, it is optimized for clarity and cost-effectiveness as a portfolio piece, not for high-traffic production loads. The following points represent the next logical iteration to make it a truly production-grade system.

  • The Scalability Trap of In-Memory Caching: In a serverless environment like Google Cloud Run, which scales by creating multiple, independent container instances, each instance would have its own isolated cache. This leads to inconsistent performance and low cache-hit ratios under load.
    The Solution: Implement the Strategy Pattern for caching. I would define a CacheStrategy interface and create two implementations: an InMemoryCacheStrategy for local development, and a RedisCacheStrategy for production. The production strategy would connect to a managed, distributed cache like Google Cloud Memorystore for Redis, ensuring all container instances share a single, consistent cache.
  • Brittleness to External Service Failure: The current service layer is optimistic and does not explicitly handle scenarios where a downstream dependency (like the Geocoding or Weather API) becomes slow or unresponsive. This can lead to blocked request threads and cascading failures.
    The Solution: Implement the Circuit Breaker Pattern. By wrapping external API calls in a circuit breaker (e.g., using a library like opossum), the application could detect when a downstream service is failing. It would 'trip the breaker,' failing fast on subsequent requests for a period of time, allowing the dependency to recover and protecting my own application from being dragged down.
  • Undefined Production Secret Management: While the app uses .env files for local development, the process for injecting production secrets (like the GEOCODE_API_KEY) is not codified. This relies on manual configuration in the Cloud Console, which is error-prone and not repeatable.
    The Solution: Use Google Secret Manager. The API key would be stored securely in Secret Manager. The Cloud Run service's identity would be granted the 'Secret Manager Secret Accessor' role, and the cloudbuild.yaml would be updated to securely mount this secret as an environment variable at deployment time. This makes the entire process automated, secure, and defined as code.

The Final Deployed Cloud Architecture

The final artifact is not just a working application; it is a clean, well-documented, fully-tested codebase with a professional, automated deployment pipeline, and a clear, forward-looking roadmap for future enhancement.

The final cloud architecture I deployed is as follows: