Brief: EDURange is More Than a CRUD Architecture

From EDURange
Jump to navigationJump to search

Brief: EDURange is More Than a CRUD Architecture

This is a brief explaining my position on adopting message-based, microservice architecture and the model-driven development process. This is the first page of its kind so I'm just guessing at the format. This top level "brief" section is in my own words but contributors should feel free to create other sections or add to the header content (above the "brief" section title) as needed. ~Joe G

The Up-Front Cost We Are Choosing to Pay

The architecture I have been proposing for the next version of EDURange asks the team to pay some real up-front costs. A message-oriented service architecture, append-only observation, explicit domain modeling, and model-driven development are not free. They require shared vocabulary, new learning, more deliberate interfaces, more testing discipline, and a willingness to plan and define software responsibilities before the immediate payoff is always obvious.

I want to acknowledge that directly. This is not the easiest path in the first sprint, and it may feel unfamiliar for our team whose shared experience is largely in cybersecurity contexts and classroom-facing teachable code rather than professional-scale software development techniques, lifecycle management, and architectural strategy. My argument is not that we should adopt heavier engineering practices because they are fashionable or inevitable. My argument is that EDURange specifically has grown to become the kind of project where not acknowledging this cost is likely to incur even greater debt.

Why EDURange Is Not a CRUD Application

EDURange is not just a CRUD application. We are not simply making and mutating records when we manage scenarios, users, assignments, and scores. The code objects in EDURange need to accurately reflect the behavior of real-life systems outside the application. We are building an intelligent tutoring system whose learning environment is a cyber range. That means we are combining two mature application paradigms that are each complex and difficult to engineer individually.

A cyber range tries to recreate meaningful interaction with real computing systems, while mitigating actual lasting harm to the host system. Student behavior is exploratory, ambiguous, distributed over time, and a student’s cognitive state is only partially or indirectly observable - even under perfect conditions. An intelligent tutoring system tries to reason about learning: what the student understands, where they are stuck, what intervention might help, and how instruction should adapt. Neither side of this problem is well represented by a simple record-oriented database design.

The hard part is not just storing data. Another vital responsibility is preserving enough meaning that we can ask better questions later.

What the Previous Architecture Taught Us

Earlier EDURange development has already shown the limits of ad hoc integration. Historically many key features have depended on specific legacy scripts, file formats, and implicit assumptions that are difficult to reproduce or explain to new contributors. Log processing has been constrained by hardcoded relationships among producers and consumers. Log data has existed in multiple places without an obvious canonical version. Data transformations have been destructive: once raw observations are cleaned, summarized, or reshaped for one purpose, later analysis inherits assumptions that may no longer be appropriate.

Adding a new observational capability, analysis method, or hinting strategy for EDURange today is less about the idea itself and more about finding a safe place to splice it into the existing stack.

That is the pain point I want us to take seriously. The previous architecture did not fail because people made wrong choices. It grew around real needs, limited time, and specific feature requests. But those local decisions were not considered in terms of long-term consequences. As a result, they accumulated into a system where change is expensive, historical knowledge is fragile, and developers need special guidance just to run the stack.

Why Not Just Use Existing Middleware?

One reasonable question is: why not solve this with existing middleware?

In some cases, we should. And under the hood of several key modules, we will. For instance - I have no intention of implementing network messaging myself or asking our future devs to maintain it, so the message bus will have an adapter that itself connects to the ZeroMQ messaging middleware for actual transport. The project-facing message bus module is responsible for managing the boilerplate setup of ZeroMQ endpoints and wire formats, giving us a high-level “it just works” functionality while leaving open the option for low-level extension or migration to other transport APIs later.

I am not arguing for rebuilding standard tools out of pride or habit. But the append-only storage and messaging options I have evaluated tend to bring their own lifecycle assumptions, especially around message schemas, version numbers, and server/client reconciliation.

Those concerns are important in many systems, but generally beyond our scale. They assume systems distributed across multiple persistent servers, where it may not be possible to shut the whole system down when updating an individual component - and so components at all times have to be prepared for the other endpoints they work with to begin communicating with a different message schema version. Version management requires special care in data format design, code changes, and binary encoding. And they cost extra per-message space. They risk making the first hard problem for EDURange into tackling a middleware’s conceptual challenges, rather than addressing the design needs and intended uses of EDURange head on.

That matters because our team is largely composed of junior developers working independently with limited supervision. We could adopt a sophisticated middleware stack as our team model for messaging and then say, “message schema changes are senior-review only.” But that creates a brittle social rule around a technical dependency most of the team cannot easily inspect. A smaller wrapper, built around our conventions and MVP needs, gives us a more teachable path into message-oriented architecture and append-only data policy. It lets us adopt the important ideas now - durable observation, explicit message boundaries, replayability, narrow service responsibilities - without forcing the team to manage complexity we do not yet know we need.

This is not a permanent rejection of production middleware. It is a staging decision. If we later need stronger distribution, replication, schema negotiation, or broker-level guarantees, we will be in a better position to adopt those tools because we will already understand our own domain boundaries. With a version built with messages in mind, we would be well positioned to perform an apples-to-apples refactor from one message API to another, without having to restructure the logical organization of our existing parts or change expectations of correct behavior. By implementing the bus as a modular service instead of directly incorporating a middleware API, we have a clearly identified pivot point where all of our message-related code can be extended or changed when new needs emerge.

Why Message-Oriented Architecture Fits the Problem

A message-oriented architecture itself helps promote this future adaptability because it treats activity as something services can observe and respond to without owning each other. Instead of one process consuming a log and deciding what every downstream feature is allowed to see, we can preserve observations and let multiple consumers subscribe to the same events. One service might classify commands. Another might track timing. Another might build a student model. Another might support replay or research analysis views.

These consumers should not need to preempt each other, overwrite each other, or force the capture layer to know every future use case. And because they are loosely connected in this way, they are easier to change, replace, and implement independently.

Why Domain Models Matter

Domain-driven design and model-driven development address a different but related problem: meaning.

A domain model is well suited to serve as the kernel of a complex unit of software because it depends on no outside code or system. It is not the database layer, the UI layer, the web framework, or the message bus. It is the part of the program that implements the logical rules we want the rest of the system to manifest. Its only job is to represent or calculate what is true or known under those logical rules. Once those rules are explicit, we can build layers around them which preserve their properties and truthfulness: persistence, messaging, APIs, user interfaces, tests, simulations, and analysis tools. Those outer layers may be influenced by outside systems, but the core model gives them something stable to serve as a reference for correct behavior.

This matters especially for intelligent tutoring and other AI applications. Model-driven development is not only useful for organizing code. It is useful for observing, interpreting, and predicting the behavior of complex systems or relationships.

What is the student doing? What is the computer system doing? What happened in the learning environment that the student might have observed? What evidence suggests the student understands the underlying concept? Why does a problem have a particular solution? Which actions are equivalent, misleading, partial, or strategically meaningful? Those are modeling questions before they are implementation questions. A desirable solution doesn’t just have implementation steps: there is a concrete and logical reason why that particular implementation is correct, and a definition of the scope or conditions under which the correctness holds.

For EDURange, the relevant concepts may include observations, scenarios, learners, attempts, interventions, hints, policies, evidence, projections, and instructional state. Relevant teachable cybersecurity content changes. Scenario infrastructure dependencies change. Student modeling strategies change. AI tools and services change. Research questions change. UI needs change. If the important concepts only live unnamed as trace evidence in controller functions, database schemas, scripts, or frontend assumptions, then every change risks becoming a cross-stack archaeology project. If the concepts live in explicit models, then we have something stable to discuss, test, revise, and teach.

Why Thin UIs and Plural Analysis Matter

This is also why thin UIs with narrow scopes are appealing. A UI should present a coherent experience, but it should not be where the system’s core reasoning lives. We want different views for different user roles - potentially including students, instructors, content authors, teaching assistants, developers, and perhaps even more. Those views should compose around shared definitions, services and models rather than each inventing its own interpretation of the system.

The same idea applies to data analysis. We should expect plural analysis, not one final pipeline. For a research platform, it is normal to compare models, rerun old data through new methods, test interventions, and ask questions we did not know to ask when the data was first captured. That requires preserved observations, explicit semantic contracts, and a clear distinction between raw evidence and downstream interpretation.

This also has practical value for grant-seeking work. Reproducibility and interpretability are not just academic ideals. They make it easier to explain and demonstrate what we measured, why we measured it, how an intervention was selected, and whether a result can be trusted. A system that preserves observations and exposes its models is easier to evaluate, easier to defend, and easier to extend into new research questions. Increasingly, features like reproducibility and explainability are not simply desired but expected.

Questions the Architecture Should Help Us Ask

The goal is not architectural purity. The goal is to make future work easier to explain, easier to test, and safer to change. A good architecture should help us ask better questions:

  • What observations must be preserved before we interpret them?
  • Which transformations are irreversible, and what assumptions do they commit us to?
  • Which parts of the system need transactional consistency?
  • Which services should be allowed to change independently?
  • Where do we expect multiple competing analysis strategies?
  • Which domain concepts should new contributors learn first?
  • What should remain small and local now, but have a path to generalization and distribution scale later?

These questions are not distractions from implementation. They are part of responsible implementation planning for a system whose purpose is scientific inquiry and adaptive instruction.

A Modest Implementation Strategy

My hope is that we can take a modest, incremental approach: build small core services, keep their responsibilities narrow, test them rigorously, and use them to make the rest of the project easier rather than harder as complexity increases.

The up-front cost is real. But if we choose the boundaries well, that cost should buy us clearer reasoning, better data, more reproducible research, and greater and more sustainable developer productivity. Most of all, the goal is a codebase where the team can safely evolve, share, and try new ideas without irreversibly accumulating assumptions or undoing foundational progress along the way.

Related

New Version Planning