After several articles examining individual pieces of my C++ event-planning model, it is time to move to the object that brings almost everything together: the Document.
This is one of the most important architectural ideas in the application, and it is also one that has been in the back of my mind for many years.
I remember the way older Apple productivity software treated a document as more than a stream of text. You could work inside one document and place different kinds of content into it: text, tables, calculations, graphics, images and other elements. The document was the thing the user understood. The technical implementation of the individual elements was secondary.
Before writing this article I went back and researched that memory, and it turns out that I was remembering several related Apple ideas.
AppleWorks was probably the clearest historical example. It brought word processing, spreadsheets, databases, drawing, painting and presentations into one integrated application. Apple later described the ability to combine text, spreadsheets, images and movies in a single document. Early versions of iWork continued part of this philosophy: Pages and Keynote could contain spreadsheet-like tables, charts, photographs and other objects rather than forcing every kind of information into a separate application boundary.
There is also a deeper architectural idea underneath macOS itself: the document-based application.
That idea is surprisingly close to what I am reconstructing here.
The file is not the Document
This distinction is fundamental.
It is tempting to think of a document as a file:
event.plan
│
└── "the document"
Architecturally, however, that is backwards.
The file is a persistent representation of the Document.
While the application is running, the Document is a live network of objects and relationships in memory. Guests can be added. Groups can change. Tables can move. Seating can be rearranged. Images can be loaded. Floor plans can be edited. Relationships can be created and removed.
When the user saves, that living model is serialised into a persistent representation.
SAVE
Live Document ─────────────────► File
▲ │
│ │
└──────────── LOAD ────────────┘
This is very close to Apple's traditional definition of a document-based application: the document object manages the information currently in memory, while the file is how that information is stored outside the running application.
For this application, the Document is essentially the event
That leads to the way I now think about this particular Document.
From the user's perspective, the Document is effectively the event being planned.
It is not merely a container for a seating chart. It is the point at which the major domains of the application come together.
Event Document
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
People Seating Floor Plan
│ │ │
┌───┴───┐ ┌────┴────┐ ┌────┴─────┐
│ │ │ │ │ │
Guests Groups Tables Seats Layouts Graphics
/ Images
The actual architecture is more detailed than this diagram, but conceptually this is the role of the Document.
It gives all those things a common context.
A Guest makes sense independently as a model object, but in the application that Guest belongs to a particular event. A Table is a graphical and seating object, but it exists on the floor plan of a particular event. A Group has members, but those members come from the event's Guest population.
The Document is the boundary that says: all of these things belong to this piece of work.
This is similar to the old AppleWorks idea
The historical comparison is useful because AppleWorks approached productivity from the user's perspective rather than from the internal type system.
A user might be writing a document but need a spreadsheet calculation inside it. That did not necessarily mean leaving the conceptual document behind and thinking in terms of a completely unrelated file.
The document remained the context.
My Event Document follows the same broad philosophy, although the domain is completely different.
Instead of this:
Text document ├── text ├── table ├── spreadsheet-like data ├── chart └── image
I have:
Event Document ├── Guests ├── Groups ├── seating information ├── Tables and Seats ├── floor-plan objects ├── images └── event-level configuration
The important similarity is not the content type. It is the architectural role of the Document as the coherent unit of user work.
The Document builds on the Seating model
One particularly interesting part of the reconstructed hierarchy is that the Document does not merely contain some unrelated seating helper.
The Document builds directly on the application's Seating abstraction.
This tells me something important about the original domain model: seating is not an optional external tool applied to a document. It is one of the fundamental capabilities of an event document.
Seating Model
│
│ provides fundamental
│ seating behaviour
▼
Event Document
│
├── adds people
├── adds groups
├── adds floor plans
├── adds assets
└── coordinates the complete event
This also explains why seating operations appear throughout the application rather than being isolated inside one dialog.
The Document itself understands that the event has a seating state.
Guests belong to the Document context
The Guest class described earlier contains rich individual state: names, RSVP status, meal choices, contact information, custom values, Group membership and Table relationships.
But the Document provides the wider population in which those Guests exist.
The reconstructed call relationships show document-level operations for adding Guests, finding Guests belonging to Groups and resolving Guest references after data has been loaded.
This is exactly the kind of work I would expect from the central model.
Event Document
│
├── Guest
├── Guest
├── Guest
├── Guest
└── Guest
The Guest knows about itself.
The Document knows about the population.
That difference in scope is important.
Groups need the Document as coordinator
The Group article exposed a bidirectional relationship.
A Group knows which Guests belong to it, while a Guest can refer back to its Group.
Relationships like that need coordination.
If somebody moves from one Group to another, several pieces of the model may need to remain consistent:
Old Group │ └── remove Guest Guest │ └── change Group reference New Group │ └── add Guest
The Document is the natural location for operations that span several domain objects because it has visibility of the complete event.
This does not mean every tiny operation should eventually remain inside one enormous Document class. But it does mean that cross-object invariants need a place where the complete model is visible.
The floor plan is part of the same Document
The Document also maintains the application's floor-plan context.
The current reconstruction includes a collection of floor-plan scenes associated with the Document.
That is important because a floor plan is not a detached illustration.
It is another representation of the same event.
Event Document
│
├── people model
│
├── seating model
│
└── floor-plan model
│
├── tables
├── walls
├── shapes
├── text
├── images
└── other visual objects
This is one of the strongest examples of why the Document metaphor works so well.
The Guest list and the floor plan may be presented through completely different screens, but they are not separate pieces of user work.
They are two views into the same event.
Images are Document resources too
The reconstructed Document also maintains image resources.
There is evidence of separate image collections used for event or room imagery and for Guest-related icons.
This is another parallel with old document-oriented productivity software.
An image inserted into a Pages or AppleWorks document was not conceptually a separate project. It became part of the document.
The same principle applies here.
A floor-plan background image, a room photograph or a Guest-specific visual asset exists because it contributes to this Event Document.
Event Document
│
├── domain objects
├── seating
├── floor plans
│
└── resources
├── event / room images
└── Guest images / icons
Keeping those resources associated with the Document also gives persistence code a clear answer to an important question:
What belongs to this event when it is saved?
Persistent identity becomes essential at this level
The Object Identifier discussed near the beginning of this series becomes particularly important once the Document contains many interconnected objects.
Inside memory, one object can hold a pointer or reference to another.
A saved file cannot rely on a memory address surviving until next week.
So relationships need persistent identities.
While running:
Guest ─────────────► Group object
When saved:
Guest ─────────────► Group identifier
When loaded again:
Group identifier
│
▼
resolve reference
│
▼
Guest ─────────────► Group object
This is why the Document has reference-resolution responsibilities during loading.
The file reconstructs objects first. The relationships between those objects then have to be re-established.
A document containing a graph of related objects is fundamentally more interesting than a file containing independent rows.
Saving means serialising an object graph
The current persistence path exports the complete plan to XML.
That is another area where the word Document becomes useful.
The XML is not the architecture itself. It is the serialised representation of the architecture.
Event Document
│
│ serialise
▼
XML
│
│ parse
▼
Event Document
The important engineering requirement is therefore not merely “write XML”.
It is:
preserve enough information to reconstruct the complete event model and all of its relevant relationships.
That includes identity, Guests, Groups, seating, floor-plan elements, images and the relationships connecting them.
A Document can have several views without becoming several documents
This is another area where Apple's document architecture provides a useful conceptual analogy.
A document is the model. A window or view is a way of looking at that model.
My application has the same requirement.
The user may look at:
- a Guest list;
- a Group-oriented view;
- a table plan;
- a floor plan;
- an affinity-oriented view;
- a dashboard or summary.
Those do not need to be separate Documents.
They are different projections of the same Document state.
Event Document
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Guest View Seating View Floor Plan View
│ │ │
└───────────────┼───────────────┘
│
same underlying
model
If a Guest's name changes in one view, another view should not need its own independent copy of that Guest.
Both should observe the same underlying event model.
Modification state belongs naturally at Document level
A document-oriented application also needs to answer another apparently simple question:
Has anything changed since the user last saved?
This question cannot sensibly belong only to Guests, Tables or Floor Plans.
Changing any meaningful part of the event can make the Document dirty.
Guest changed ───────┐ Group changed ───────┤ Table moved ─────────┤ Seating changed ─────┼──► Document modified Floor plan changed ──┤ Image changed ───────┘
The Document is therefore the natural unit for change tracking and save decisions.
Again, this mirrors the traditional desktop-document model: the application tells the user that the document has unsaved changes, not that twelve individual C++ objects have unsaved changes.
This is an aggregate boundary
In modern domain terminology, I would describe much of this as an aggregate boundary.
The Event Document establishes the scope inside which a large number of related objects form one coherent model.
I do not mean that the Document should eventually implement every operation itself.
That distinction matters.
A mature architecture can still delegate specialised work to dedicated components:
- seating algorithms can live in seating services or solver objects;
- XML persistence can live in import/export code;
- graphics can remain inside scene and view classes;
- Guest formatting can remain with Guest-related logic;
- Group contact data can remain in its own value object.
The Document does not need to perform every calculation.
It needs to establish which model those calculations are operating on.
The danger of the central Document
The same architectural importance that makes the Document useful also makes it dangerous.
Central classes tend to attract code.
Whenever a developer needs access to Guests, Groups, Tables, images or seating, the Document is nearby. Over many years, convenience can turn the central model into a class that knows too much.
This is something I am paying particular attention to during reconstruction.
I want to preserve the Document as the coherent root of the event while identifying responsibilities that belong elsewhere.
Good: Document ├── owns / coordinates model ├── exposes coherent operations └── delegates specialised work Dangerous: Document ├── owns everything ├── draws everything ├── imports everything ├── exports everything ├── calculates everything ├── formats everything └── knows every UI detail
The reconstruction is therefore not just an exercise in recovering functions. It is an opportunity to identify the architectural boundary that should remain and the accidental responsibilities that can later be moved.
Why I still like the Document metaphor
Modern software often talks about projects, workspaces, cloud resources, boards and databases.
There is nothing wrong with those concepts, but I still like the clarity of the traditional desktop Document.
The user creates something.
That thing opens.
They work on it.
They save it.
They can make another one without confusing the two.
Everything that belongs to the first stays with the first.
Everything that belongs to the second stays with the second.
For an event-planning application, that mental model is particularly natural.
Application
│
├── Event Document: Wedding
│ ├── Guests
│ ├── Groups
│ ├── Tables
│ ├── Seating
│ └── Floor Plan
│
└── Event Document: Conference Dinner
├── Guests
├── Groups
├── Tables
├── Seating
└── Floor Plan
The application is not the event.
The Document is.
Putting the Document architecture together
At this point in the reconstruction, my conceptual view is becoming:
Event Document
│
├── event population
│ ├── Guests
│ │ └── individual contact information
│ │
│ └── Groups
│ └── shared contact information
│
├── seating model
│ ├── Tables
│ ├── Seats
│ ├── assignments
│ └── seating solutions
│
├── relationships
│ ├── Guest ↔ Group
│ ├── Guest ↔ Table
│ └── Guest ↔ Guest affinity
│
├── physical representation
│ ├── floor-plan scenes
│ ├── Tables
│ ├── walls
│ ├── shapes
│ ├── text
│ └── images
│
├── resources
│ ├── floor-plan / room images
│ └── Guest images or icons
│
├── document state
│ ├── object identities
│ ├── modification state
│ └── event-level settings
│
└── persistence boundary
├── load
├── resolve relationships
└── save
Some of those responsibilities are implemented directly by the Document and some are delegated to collaborating objects. That distinction will become clearer as I continue reconstructing the remaining classes.
But the overall architectural role is already clear.
From individual classes to one coherent application model
The earlier articles in this series examined very small pieces: an Object Identifier, Guest Contact and Group Contact. Then came larger business objects such as Guest and Group.
The Document is where those earlier pieces begin to make sense as one system.
The Identifier allows relationships to survive persistence.
The Guest represents an individual attendee.
The Group gives several Guests shared context.
The contact objects separate personal and shared communication information.
The seating model relates people to physical places.
The floor-plan model gives those places spatial meaning.
And the Document says that all of these things belong to the same event.
That is why I find the old Apple document model such a useful analogy.
The technology has changed enormously. The application domain is completely different. But the underlying idea remains elegant: create one coherent Document, allow different kinds of information to live inside it, and make that Document the unit the user opens, edits, saves and understands.
For this project, that is exactly the role I want the Event Document to play.
Comments
Post a Comment