Technical Articles & Tutorials

The Missing Architectural Layer in Web Development

How web frameworks evolved without the message-passing architecture that desktop applications embraced.

Published: April 8, 2025

A critical but rarely discussed gap exists in how web architecture evolved compared to desktop application architecture. This gap helps explain many of the challenges and "bolt-on" solutions we see in modern web development.

The Message-Passing Foundation

Desktop GUI applications, particularly on Windows, were designed from the beginning with an event-driven, message-passing architecture:

Windows GUI Message Processing
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        case WM_PAINT:
            // Handle paint message
            return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

This architecture established several fundamental patterns:

  • Event-Driven Design: Components respond to messages rather than being directly called
  • Asynchronous Communication: Messages are queued and processed in event loops
  • Loose Coupling: Components don't need to know about each other, only about message types
  • State Management: State changes are triggered by and communicated through messages

The Web's Request-Response Origins

By contrast, early web frameworks originated from a fundamentally different model:

Traditional Web Request Handling
# Django View
def product_page(request, product_id):
    product = get_object_or_404(Product, id=product_id)
    return render(request, 'products/detail.html', {'product': product})

# Flask Route
@app.route('/products/')
def product_page(product_id):
    product = Product.query.get_or_404(product_id)
    return render_template('products/detail.html', product=product)

This request-response paradigm:

  • Is Synchronous: Each request is handled directly, synchronously returning a response
  • Lacks Event Propagation: No built-in way for one component to broadcast events to others
  • Is Stateless by Default: HTTP's statelessness required workarounds like sessions
  • Has No Message Queue: No built-in concept of pending messages or events

Consequences of the Missing Layer

This architectural gap has led to numerous challenges in web development:

The "Bolted-On" Ecosystem

  • Background Job Systems: Sidekiq, Celery, Bull - retrofitting asynchronous processing
  • Message Brokers: RabbitMQ, Kafka, Redis Pub/Sub added as separate systems
  • WebSockets Libraries: Socket.io, ActionCable, Channels added for real-time features
  • State Management Libraries: Redux, MobX, Vuex created to manage client-side state

These additions are often treated as specialized tools rather than fundamental architectural components, leading to increased complexity and integration challenges.

Framework Attempts to Fill the Gap

Django's "Service Layer"

Django's community has attempted to introduce service layer patterns, but these are primarily code organization strategies rather than true message architectures:

class OrderService:
    @staticmethod
    def create_order(user, cart_items):
        # Business logic here
        order = Order.objects.create(user=user)
        for item in cart_items:
            OrderItem.objects.create(order=order, product=item.product, quantity=item.quantity)
        
        # Direct function call, not message passing
        PaymentService.process_payment(order)
        EmailService.send_order_confirmation(order)
        
        return order

This is still fundamentally procedural and synchronous, not event-driven.

JavaScript Frameworks and Events

Modern JavaScript frameworks have moved closer to event-driven models:

// React Component with Event Emitter
import EventEmitter from './eventEmitter';

function ProductPage({ product }) {
  const handleAddToCart = () => {
    // Emit an event instead of direct method call
    EventEmitter.emit('cart:add', { 
      productId: product.id, 
      quantity: 1 
    });
  };

  return (
    

{product.name}

); } // Elsewhere in the application EventEmitter.on('cart:add', (item) => { // Handle the event cartStore.addItem(item); // Maybe trigger other events EventEmitter.emit('notification:show', { message: `Added ${item.quantity} item(s) to cart` }); });

While this improves decoupling, it's still typically implemented as an addon rather than as a core architectural element.

More Holistic Approaches

Java's Enterprise Messaging

Java EE incorporated messaging as a core architectural component:

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
    @ActivationConfigProperty(propertyName = "destination", propertyValue = "OrderQueue")
})
public class OrderProcessor implements MessageListener {
    @Inject
    private OrderService orderService;
    
    @Override
    public void onMessage(Message message) {
        try {
            TextMessage textMessage = (TextMessage) message;
            String json = textMessage.getText();
            Order order = new Gson().fromJson(json, Order.class);
            
            orderService.processOrder(order);
        } catch (JMSException e) {
            // Handle exception
        }
    }
}

Actor Model in Elixir/Phoenix

Elixir's Phoenix framework, built on the Erlang VM, embraces a comprehensive message-passing architecture:

# GenServer (actor) definition
defmodule OrderProcessor do
  use GenServer

  # API
  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def process_order(order) do
    GenServer.cast(__MODULE__, {:process, order})
  end
  
  # Server callbacks
  def init(state) do
    {:ok, state}
  end
  
  def handle_cast({:process, order}, state) do
    # Process the order
    # Send messages to other processes
    PaymentProcessor.charge(order.payment_details)
    EmailSender.send_confirmation(order.customer_email, order)
    {:noreply, state}
  end
end

This approach makes message-passing fundamental to the architecture, not an afterthought.

Modern Architectural Patterns Addressing the Gap

CQRS (Command Query Responsibility Segregation)

CQRS formalizes the separation of commands (which change state) from queries (which read state), often using a message-based approach:

// Command handler
class CreateOrderCommandHandler {
  constructor(orderRepository, eventBus) {
    this.orderRepository = orderRepository;
    this.eventBus = eventBus;
  }
  
  async handle(command) {
    const { userId, items } = command;
    
    // Create and save order
    const order = new Order(userId, items);
    await this.orderRepository.save(order);
    
    // Publish event
    this.eventBus.publish(new OrderCreatedEvent(order.id, userId, items));
    
    return order.id;
  }
}

Event Sourcing

Event Sourcing takes this further by modeling the entire system around events:

// Event-sourced aggregate
class Order {
  constructor(id) {
    this.id = id;
    this.items = [];
    this.status = 'draft';
    this.events = [];
  }
  
  addItem(productId, quantity, price) {
    if (this.status !== 'draft') {
      throw new Error('Cannot add items to non-draft orders');
    }
    
    const event = new ItemAddedEvent(this.id, productId, quantity, price);
    this.applyEvent(event);
    return event;
  }
  
  applyEvent(event) {
    if (event instanceof ItemAddedEvent) {
      this.items.push({
        productId: event.productId,
        quantity: event.quantity,
        price: event.price
      });
    }
    
    this.events.push(event);
  }
}

Why This Matters

The architectural gap in web frameworks has significant implications:

  • Increased Complexity: Developers must integrate multiple systems to achieve what could be a unified architecture
  • Distributed System Challenges: Microservices require robust message-passing that isn't built into most frameworks
  • Real-time Expectations: Users now expect real-time updates and interactions
  • Reactive Requirements: Modern UIs need to react to state changes from multiple sources

The Future: Unified Architectures

We're seeing movement toward more unified architectures that incorporate message-passing as a fundamental concept:

  • Reactive Frameworks: RxJS, Akka, and similar libraries building reactive foundations
  • WebAssembly: Enabling non-web architectures to run in browsers
  • GraphQL Subscriptions: Adding real-time capabilities to data fetching
  • Service Mesh: Infrastructure-level solutions for service-to-service communication

Conclusion

This architectural gap explains why modern web applications often feel like collections of loosely integrated components rather than cohesive systems. The industry continues to evolve toward more event-driven, message-passing architectures, but still largely treats these as add-ons rather than core architectural elements.

As web applications become more complex and interactive, embracing message-passing as a fundamental architectural concept—as desktop applications did decades ago—seems increasingly necessary.

What's Your Architecture?

How have you addressed the messaging gap in your web applications? Have you built systems with events and messages at their core, or do you integrate separate solutions? Let me know in the comments or contact me directly.

About

Why fear those copying you, if you are doing good they will do the same to the world.

Archives

  1. AI & Automation
  2. AI Filtering for Web Content
  3. Web Fundamentals & Infrastructure
  4. Reclaiming Connection: Decentralized Social Networks
  5. Web Economics & Discovery
  6. The Broken Discovery Machine
  7. Evolution of Web Links
  8. Code & Frameworks
  9. Breaking the Tech Debt Avoidance Loop
  10. Evolution of Scaling & High Availability
  11. Evolution of Configuration & Environment
  12. Evolution of API Support
  13. Evolution of Browser & Client Support
  14. Evolution of Deployment & DevOps
  15. Evolution of Real-time Capabilities
  16. The Visual Basic Gap in Web Development
  17. Evolution of Testing & Monitoring
  18. Evolution of Internationalization & Localization
  19. Evolution of Form Processing
  20. Evolution of Security
  21. Evolution of Caching
  22. Evolution of Data Management
  23. Evolution of Response Generation
  24. Evolution of Request Routing & Handling
  25. Evolution of Session & State Management
  26. Web Framework Responsibilities
  27. Evolution of Internet Clients
  28. Evolution of Web Deployment
  29. The Missing Architectural Layer in Web Development
  30. Development Velocity Gap: WordPress vs. Modern Frameworks
  31. Data & Storage
  32. Evolution of Web Data Storage
  33. Information Management
  34. Managing Tasks Effectively: A Complete System
  35. Managing Appointments: Designing a Calendar System
  36. Building a Personal Knowledge Base
  37. Contact Management in the Digital Age
  38. Project Management for Individuals
  39. The Art of Response: Communicating with Purpose
  40. Strategic Deferral: Purposeful Postponement
  41. The Art of Delegation: Amplifying Impact
  42. Taking Action: Guide to Decisive Execution
  43. The Art of Deletion: Digital Decluttering
  44. Digital Filing: A Clutter-Free Life
  45. Managing Incoming Information
  46. Cloud & Infrastructure
  47. AWS Lightsail versus EC2
  48. WordPress on AWS Lightsail
  49. Migrating from Heroku to Dokku
  50. Storage & Media
  51. Vultr Object Storage on Django Wagtail
  52. Live Video Streaming with Nginx
  53. YI 4k Live Streaming
  54. Tools & Connectivity
  55. Multi Connection VPN
  56. Email Forms with AWS Lambda
  57. Static Sites with Hexo

Optimize Your Website!

Is your WordPress site running slowly? I offer a comprehensive service that includes needs assessments and performance optimizations. Get your site running at its best!

Check Out My Fiverr Gig!

Elsewhere

  1. YouTube
  2. Twitter
  3. GitHub