Last Updated September 8, 1999 - Jack Harich - Go Back
Conceptual Integrity
Conceptual integrity is the rationale behind an architecture and the user's mental model. It lives in the minds of the architects, but can be lightly summarized by the key goals and rules used:
- Follow the principles of Structured Assembly.
- Implement the grand vision of the UHR Technology Grid one bite at a time.
- Provide a very small set of rigorous, standard mechanisms for system behavior.
- Only use mechanisms that follow sound principles.
- Use approaches that map naturally to a user's mental model.
- Keep the kernel as small, dumb and extensible as possible.
- Allow any portion of the system to be easily customized, including the kernel.
- Allow tools to do anything imaginable to a system.
- Always build your tools with your tools, ie with UHR.
- Assume that eventually end users will define 90% of all systems.
- Assume that after that systems will define 99% of all systems.
- Sprinkle simplicity everywhere. :-)
Ultra High Level Model
As discussed before, we passionately believe "Architecture is a system's high level structure, which consists of components, their responsibilities and their relationships." Following the advice of Ivar Jacobson to limit a system's top partitions to no more than four, here we go....
We worked hard on this. It shows the result of the "what have we learned" musings in the Narrative. The diagram has that "certain feel" that tells the designer he's on the right track in spades. It resolves a ton of competing requirements. It's better than the current BA because at last we have a very small number of top partitions, instead of a Class Model with its hodgepodge of classes and areas. Starting from these three partitions will lead to a better design. It is now a managable task to derive a Class Model that has a high success probablity. Our next step could be called "Low Level Architecture".
A MessageSender is used by a container or part to send Messages. This allows removing the MessageRouter from parts, and idea from Steve. It makes the parts more independent.
As you can see, we will be using Messages for much inter-system and inter-partition work. This gives the architecture more consistency and fewer different mechanisms, ie simplicity!
Scalable Partitioning
As we created this diagram, we noticed the emergent property of scalable partitioning. These days all non-trivial systems are partitioned. The problem most encounter is whatever worked in the small fails to work as well as they scale up in terms of size of partitions, number of partitions, complexity, reuse goals, reliability goals, dynamic behavior, system age, etc. Non-scalablity is the single biggest long term problem facing most development shops. In usually hides itself well, appearing suddenly late in the game....
Partitioning is done with containers and parts. Scalable partitioning is the result of the standard architecture provided by:
- Encapsulating all domain functionality into parts.
- Organizing all systems into a tree of containers and parts.
- Using DK to vary a part or container's behavior.
- Using Messages for Anonymous Collaboration among parts.
Some of the ways scalable partitioning works are:
- Parallel development is much easier due to parts, DK, the loose coupling provided by Messages, and the independence of system tree branches.
- Bigger projects are easier because parallel development is the norm, reuse is widespread, sub projects have greater consistency, and the higher level process of Part Flow (using Core Tech, Part Shop, Assembly) becomes advantageous.
- As the number of partitions grows, the system remains understandable due to the ability to work on one portion of the system tree at a time. You can drill down and navigate with ease. Top down decomposition occurs mentally as well as physically. Gone is the need to understand the whole all at once. A system tree has high inherent understandabiility which is independent of the number of nodes. Large scale design with thousands of partitions becomes very managable.
- Size of partitions tends to have little effect because that occurs in each part, where the increased complexity due to size is hidden from the rest of the system. There will be some effect since the exposed properties will grow some.
- Complexity occurs in two areas - relationships and inside parts. More relationships is no problem because they are anonymous, and the Message structure can handle 1,000 as easily as 10. More complexity in a relationship is also little problem, because a Message cleanly handles many input and output properties, plus we can use Datatrons. More complexity inside parts is no problem, because the rest of the system couldn't care less. Behavior is also more understandable because all needs of parts are exposed.
- Reliablity tends not to decrease as systems scale up, because of the widespread reuse of tested parts, replacement of "glue code" with the kernel, DK and Messages, and ease of automated testing. Note that "part reuse" includes reuse of entire branches, since a container is a part too.
- Dynamic behavior is not nearly so messy because of the ease of adding, removing or replacing (updating) parts in containers. Part behavior can also be changed by changing it's DK. Messsages can be monitored, filtered, re-routed, translated, split, etc with ease for easy dynamic collaboration.
- As system age, maintenance tends not to cause entrophy because of the clean, standard, understandable architecture. Most serious maintenance defects occur due to a change in one area of a system unexpectedly affecting another area. This is much less likely to happen in a UHR system because of extreme part independence.
- As reuse goals rise, there is little need to change anything because you're already doing everything right. :-) There will be a growing emphasis on better part indentification and design for reuse as your experience grows.
Infinite Extensibility
We see Infinite Extensibility as one of our very toughest goals. During work on the above diagram we solved part of this. The problem was how to elegantly allow the "first used" kernel components to be arbitrarily specified. This is a "chicken and egg" problem. A first used component such as a bootstrapper cannot easily replace itself with an alternative, especially if it's embedded in the system it's starting.
Our solution is to pass the "first used" component (perhaps named SystemMgr) to the system it starts, where the SystemMgr becomes a part in the root container. The SystemMgr is specified by whatever is starting the system. DK enters the scenario early, and can specifiy additional custom behavor early on. See the usePart() call in the Start System Use Case.
Start System Use Case
This is often the most defining one in a system. Ours is:
- Get system marker class or such
- Instantiate SystemMgr - Here playing the "bootstrapper" role
- systemMgr.setParamStore(paramStore) - Optional, could set more here
- systemMgr.startSystem(marker, args)
- Use paramStore to get marker Param
- Instantiate root container
- rootContainer.setParam(markerParam)
- rootContainer.usePart("SystemMgr", systemMgr) - Optional
- rootContainer.start() - This causes lots of further action.
usePart() causes a container to use that part instead of instantiating the one specified in container DK. This allows pre-existing life to enter a new life form, necessary for reproduction. Best of all, it eliminates the need for a separate System Mgt partition in the kernel medium level architecture. This is a huge gain, because otherwise there would be 3 and not 2 blue boxes, and we would need SystemMgr DK or some such nonsense.
This idea lead to the sudden idea of making the kernel parts and starting the system in a similar way. See Self Referential Tree.