Dec 9, 2025 · 4 min read · Backend Engineering and Architecture
Designing Around Existing System Constraints
The architecture became clearer after we understood the constraints: sensitive data, existing integrations, limited deployment windows and workflows used every day.
The cleanest diagram wasn’t necessarily the architecture we could deliver safely. The enterprise and government systems I worked on at Softcraft had users, integrations, sensitive data, and limited deployment windows. They couldn’t stop while we redesigned them. It was tempting to start with the version we wanted: clean modules, independent services, clear data ownership and modern deployments. Those ideas were useful. They weren’t the starting point. The starting point was everything the system already had to respect.
The Constraints
The platform handled sensitive workflows. It integrated with systems owned by other teams. Some contracts couldn’t change quickly. Historical data needed to remain available, and deployment opportunities were limited because people used the system throughout the day. The team also had delivery commitments. We couldn’t pause product work for months while building an ideal replacement. Once those constraints were explicit, some architecture options stopped being realistic. Others became more valuable. Incremental change mattered because a large cutover carried too much operational risk. Compatibility mattered because dependent systems couldn’t migrate at the same speed. Observability mattered because old and new behavior would coexist during the transition. These constraints weren’t excuses to keep everything as it was. They were information we needed to create a plan that could actually work.
We wrote the important constraints next to the architecture options instead of keeping them as things everyone was expected to remember. For one change, the ability to roll back without losing data mattered more than deploying the new component independently. For another, an external contract fixed the shape of the boundary even though we would have designed it differently from zero. Making these conditions visible helped us explain why two similar parts of the platform didn’t always receive the same solution.
The Migration Period
An architecture diagram usually shows the destination. Production systems also need a path from the current state to that destination. During that path, two versions may run together, data may move gradually and a contract may need to support old and new consumers. The team also has to recover if a migration step fails. Those temporary states can last longer than expected, so I started asking what the system would look like after the first month of migration as well as where we wanted it to end. Architecture also needs to match the organization that will support it. A design with many independently deployed services creates build, monitoring, security and production support work for each one. If the team can’t sustain that work, the design has moved complexity from the code into operations without creating enough value. The same applies to unfamiliar technology. Learning matters, but a critical deadline may not be the right moment to introduce several new systems at once.
One migration needed an old consumer and a new service to use the same information for a period. We considered a large data move, but the deployment window and rollback requirements made smaller synchronization steps safer. That left us with a temporary connection we wouldn’t choose as the final architecture. We monitored it, documented who owned it and included its removal in the plan instead of pretending the transition state would disappear by itself.
The number of teams mattered too. A boundary that looked independent in code could still require frequent coordination if the same people reviewed and deployed both sides. We focused first on areas where separation changed how work could be delivered or operated rather than areas where it only created another repository.
I don’t see team experience as an argument against improvement. I see it as information the plan needs to include. Training, gradual adoption, and operational ownership can be part of the architecture.
The Trade-Offs We Discussed
Words like “scalable,” “flexible” and “maintainable” appeared in many of our discussions, but we always had to make them more specific. On one system, independent deployment mattered because unrelated changes were delaying releases. On another, keeping a workflow in one application reduced operational risk. As we worked through cases like these, I spent less time looking only at the final diagram and more time thinking about the months between the current system and that diagram. Data still had to move, old consumers still needed to work and the team still had normal product work during the migration. Those details usually changed the architecture more than the first drawing did.
We also recorded what would make us reconsider a decision. If traffic reached a certain behavior, if a dependency became a repeated source of incidents or if one team needed to release independently, the trade could change. This kept a compromise from being treated as a permanent principle. It was the decision that matched the current constraints, with enough context for somebody to understand it later.
The final plan had compatibility periods, migration steps and compromises we already knew about. We could also explain what the system would look like after the first month of migration and how the team would recover if one of those steps failed.