Right Technology, Wrong Decision: Why a Unified Message Center Can Be the Worst Architecture Call
Beware of Architecture Design Detached from Code and Business: Real Architecture Ability Is Not Drawing Diagrams, but Making Correct Engineering Decisions
The biggest risk in architecture design is not outdated technology choices, but decision-makers who don't understand the current state of the system and still try to use idealized solutions to guide real engineering practice.
1. Why a seemingly reasonable architecture suggestion can become a burden on the project
In software development, we often encounter this scenario.
A business system needs to add message notification functionality, including SMS, in-site messages, and third-party platform notifications.
The development team, based on current requirements, plans to complete message sending through existing third-party service interfaces and make an appropriate encapsulation of the calling logic.
During technical review, someone asks:
“Why not introduce a unified message center? Calling third-party interfaces directly like this, what about future system expansion?”
At first glance, this suggestion sounds quite professional.
From an architecture design perspective, a unified message center does have many advantages:
- Unified management of different message channels, reducing coupling between business and suppliers.
- Supports asynchronous delivery, failure retries, rate limiting, and circuit breaking.
- Centralized management of message templates, sending policies, and notification records.
- Provides unified logging, metrics monitoring, and trace tracking capabilities.
- Provides standardized services for integrating more business systems later.
These advantages are all valid.
But the real question worth discussing is: Has the current system actually reached the stage where a unified message center is needed?
Suppose the project faces the following reality:
- Only two business modules currently need to send notifications.
- The existing system has no unified message center.
- Business notification volume is very low, with no obvious performance bottleneck.
- The development cycle for this requirement is only two weeks.
- The team lacks people to maintain independent messaging infrastructure.
- There is no clear plan for other systems to integrate in the short term.
Then, for this requirement, is building an independent message center actually improving the architecture, or creating extra complexity?
A simple notification feature might turn into:
A whole infrastructure build-out involving business services, message services, message queues, template management, retry mechanisms, configuration centers, monitoring and alerting, plus deployment and operations.
A requirement that could have been completed with reasonable encapsulation gets expanded into a cross-service systems engineering project.
This is a very typical problem in architecture design:
A technical solution being theoretically correct does not mean it is engineering-wise reasonable at the current stage.
A truly good architect should not only answer “is this design advanced?”, but should also answer:
“Why does it need to be designed this way now? What already-existing or reasonably foreseeable problem does it solve?”
2. The most important ability of an architect is not how many design patterns they know
In many technical teams, architecture ability is easily understood as technical breadth.
For example, if someone is familiar with microservices, DDD, CQRS, event-driven architecture, Service Mesh, Kubernetes, message queues, and various distributed architectures, it seems to imply strong system design ability.
But in reality, technical knowledge reserves and architecture decision-making ability cannot be directly equated.
The essence of architecture design is trade-offs under real constraints.
Software architecture often discusses the concept of Trade-off.
Any technical solution has benefits, and it also inevitably carries costs.
For example:
| Architecture approach | Potential benefits | Hidden costs |
|---|---|---|
| Microservices architecture | Independent deployment, team autonomy | Distributed communication, operations complexity, data consistency |
| Message queues | Async decoupling, traffic peak shaving | Message reliability, idempotency handling, failure recovery |
| Unified message center | Channel governance, reusable notification capability | Platform development, migration/onboarding, continuous maintenance |
| Kubernetes | Elastic scheduling, declarative deployment | Platform governance, resource costs, operations barrier |
| DDD | Clear business boundaries, managing complex domains | Modeling costs, learning costs, organizational collaboration |
| Event-driven architecture | Reduces direct call coupling | Event contracts, eventual consistency, observability |
These technologies have no absolute superiority or inferiority in themselves.
What really determines the value of a solution is business scale, system boundaries, team capability, delivery timeline, and long-term maintenance cost.
An internal business system maintained by a few people does not necessarily need a complex microservices system.
A platform processing millions of notifications per day with many business integrators may very well need independent messaging infrastructure.
The maturity of an architecture design is not determined by how many advanced technologies are used, but by whether system complexity matches business complexity.
3. Why long-term detachment from code distorts architectural judgment
First, a clarification:
An architect does not necessarily need to write business code every day.
As responsibilities change, architects may need to spend significant time on cross-team collaboration, technical planning, system governance, and long-term evolution issues.
These are also important architecture work.
What is truly dangerous is not stopping high-frequency coding, but gradually losing technical perception of the real system while continuing to output high-impact technical decisions.
This problem appears mainly in three areas.
3.1 Not understanding actual dependencies makes transformation costs easy to underestimate
The system in a design document is often tidier than the real code.
An architecture diagram may only show three modules:
- User service
- Order service
- Payment service
But in the actual code, there may be many implicit dependencies.
For example, the order service not only calls the user service, but also directly reads the user database; a payment status change may trigger multiple business modules; a shared utility class may contain order calculation, account validation, and third-party interface logic all at once.
Without knowing these facts, proposing “split the order module into an independent microservice” looks reasonable on the surface, but can hit a large number of obstacles in practice.
These include database splitting, transaction boundary adjustments, interface compatibility, historical data migration, and cross-service call governance.
Therefore, the key to deciding whether a module can be split is not whether the architecture diagram draws clean boundaries, but whether the code and data layers actually have boundaries.
Logical module division is not the same as engineering-level independent deployability.
3.2 Not understanding historical constraints turns technical debt into misjudged design errors
Many systems contain implementations that look less than elegant.
For example:
- Some business logic is concentrated in one large Service.
- Multiple modules share the same database table.
- Some interfaces retain outdated parameter formats.
- The system has both old and new authentication mechanisms.
- Some third-party integrations still use synchronous calls.
From a purely technical standpoint, these implementations may have room for improvement.
But they are not necessarily all due to a lack of design ability on the developers' part.
Some are historical compatibility requirements, some come from external system constraints, and some are reasonable compromises made during rapid business growth.
Technical debt is not exactly the same as wrong code.
Sometimes it is an engineering choice where the team actively accepts short-term costs and long-term risks.
What really needs to be managed is:
Why the technical debt was created, how much loss it causes today, when it should be paid off in the future, and whether it is worth paying off at all.
Without understanding the background, demanding that the team immediately refactor all code that does not follow best practices can lead to huge investment with no obvious business return.
3.3 Lacking runtime awareness leads to systems that cannot be operated effectively
System architecture is not a static code structure.
It also includes performance, resource consumption, failure propagation, and recovery capability in the real running environment.
For example, after introducing a message queue, an architect must consider:
- Is duplicate message delivery allowed?
- How do consumers ensure business idempotency?
- How is message backlog monitored?
- How should retries work after consumption failure?
- How are dead-letter messages handled?
- Are there ordering requirements for message processing?
- How does the business degrade when the message system is unavailable?
- How are failure drills and data compensation performed?
If the designer only focuses on “async decoupling” but ignores these issues, the so-called architecture upgrade may just be moving synchronous call problems inside a distributed system.
The reliability of an architecture is not determined by component names, but by system behavior under failure scenarios.
4. How an architect should judge whether to introduce new architecture now
Back to the original message center case.
If I were reviewing this technical solution, I would not directly ask the development team to build an independent message center.
I would prefer to break the question into several layers.
Layer 1: Does the current requirement actually have an architecture bottleneck?
First, clarify the current state.
What is the current message sending volume? How many business modules need notification capability? Is the failure rate beyond what the business can accept? Are third-party suppliers frequently switched?
If there is no obvious problem, then perhaps only a unified interface abstraction is needed.
For example:
interface NotificationProvider {
send(message: NotificationMessage): Promise<SendResult>;
}
Different notification channels implement this interface separately:
class SmsProvider implements NotificationProvider {
async send(message: NotificationMessage) {
// SMS provider adaptation logic
}
}
class EmailProvider implements NotificationProvider {
async send(message: NotificationMessage) {
// Email provider adaptation logic
}
}
The business layer does not directly depend on a specific provider, but depends on a stable notification abstraction.
This controls current complexity while preserving room to replace providers and add channels later.
Note that this example only solves the interface dependency problem; it does not mean reliable delivery, transactional consistency, or message governance have been implemented.
Layer 2: Is there clear evidence for future expansion?
Suppose in the next few months a dozen business systems will integrate, notification volume is expected to grow quickly, and there is a need to centrally manage templates, sending records, and channel policies.
At this point, building a unified message center has a stronger business basis.
But even then, it is not necessarily necessary to immediately create an independent microservice.
The notification capability can first be abstracted into a module and managed uniformly inside the monolith.
As the business grows, it can gradually evolve into an independent service.
This approach is often called progressive architecture evolution.
It emphasizes:
Establish clear boundaries first, then decide deployment boundaries based on actual needs.
Modularization does not equal microservices.
A modular monolith with clear boundaries and controllable dependencies is often easier to maintain than an over-split distributed system at many stages.
Layer 3: Can the benefits of new architecture cover long-term costs?
For an independent message center, what needs to be calculated is not just development hours.
It should also include:
- Construction cost: service development, interface contracts, data design, migration and onboarding.
- Operation cost: servers, databases, messaging infrastructure, and monitoring resources.
- Maintenance cost: version upgrades, failure handling, performance optimization, and security fixes.
- Collaboration cost: integration standards, cross-team communication, and interface compatibility management.
- Opportunity cost: after investing in infrastructure, business requirements may be delayed.
The TCO (Total Cost of Ownership) way of thinking can be introduced here.
Architecture cost is not the cost on launch day; it is continuous investment across the entire lifecycle.
If the long-term benefits of adding architecture cannot cover these costs, the necessity of building it should be re-examined.
5. A truly professional technical review should output a decision, not verbal advice
Many technical reviews are inefficient, not because the team lacks technical ability.
It is because the review process has no clear decision-making mechanism.
Common expressions at review meetings include:
“Redis should be used here.”
“It is better to add a message queue for this scenario.”
“Why not use microservices?”
“This part can be refactored with DDD.”
These suggestions may be valuable, but they are not complete architecture decisions in themselves.
A truly professional review should answer at least five questions:
- Problem: What is the core problem to solve now?
- Context: What business, technical, staffing, and time constraints exist?
- Options: What feasible options are there, instead of only one ideal solution?
- Trade-offs: What are the benefits, risks, and costs of each option?
- Decision: Which option is finally chosen, and why?
In real projects, an ADR (Architecture Decision Record) can be used to record key decisions.
For example:
ADR-001: Notification Capability Construction Plan
Background
The current system has SMS and in-site message notification requirements, short-term business scale is limited, and there is no unified messaging infrastructure.
Candidate options
| Option | Implementation | Advantages | Risks |
|---|---|---|---|
| A | Business calls third-party interfaces directly | Low development cost | Obvious coupling, hard to expand |
| B | Build a unified notification module | Moderate cost, easy to expand | Requires maintaining interface abstraction |
| C | Build an independent message center | Unified governance, cross-system reuse | High development and operations cost |
Decision
Choose Option B at the current stage.
Isolate third-party suppliers through an interface abstraction and centrally manage notification sending logic.
Reason for not choosing Option C for now
There is currently no cross-system reuse demand, and the benefits of independent deployment are not enough to offset construction and maintenance costs.
Conditions for re-evaluation
When multiple independent systems repeatedly build notification capability, unified channel governance is needed, or the existing notification module cannot meet reliability requirements, re-evaluate the necessity of an independent message center.
Note that these conditions are signals for triggering evaluation, not hard rules that require splitting once a certain number is reached.
This way, the review is no longer an argument between different technical preferences, but an engineering decision that can be reviewed, tracked, and revised.
Good architecture governance records not only “what to do”, but also “why to do it” and “why not to do it for now”.
6. A mature architect must maintain four technical perspectives
Architecture design requires technical breadth, but technical breadth alone is far from enough.
In my view, a mature architect needs to maintain four perspectives at the same time.
6.1 Code perspective: understand the real implementation boundaries of the system
Be able to identify actual coupling between modules by reading core code, dependency relationships, and database structure.
Know which modules are easy to modify, which interfaces have compatibility risks, and which changes may trigger chain reactions.
An architect does not have to commit code every day, but cannot stay away from code for long.
6.2 Business perspective: understand the actual needs behind technical decisions
Architecture is not a work of art independent of business.
Different businesses have different requirements for consistency, availability, performance, and delivery speed.
For example, marketing notifications can tolerate some delay, while payment result notifications may require strict status reconciliation and compensation mechanisms.
Even with the same notification infrastructure, it is not appropriate to simply adopt exactly the same reliability strategy.
Technical solutions need to serve business goals, not force the business to accommodate a particular technical philosophy.
6.3 Runtime perspective: understand the real production behavior of the system
Be able to read monitoring metrics, analyze call traces, participate in incident retrospectives, and understand how the system behaves under high load and abnormal conditions.
An architecture diagram can only describe how the system is expected to work.
Production logs, traces, metrics, and failure records are what help the team verify how the system actually works.
6.4 Cost perspective: understand the long-term price of complexity
Every added component adds a share of maintenance responsibility.
Every service split may introduce new deployment, communication, authentication, and failure boundaries.
Every new technology introduced requires consideration of team learning, talent availability, technical support, and upgrade compatibility costs.
A complex architecture being able to run does not mean it has a reasonable input-output ratio.
A good architect not only knows how to add capability, but also knows when to restrain complexity.
7. In the age of AI programming, architects cannot detach from real engineering
With the development of AI coding agents, code generation, and automated development tools, software engineering is changing visibly.
In the past, a system from design to coding often required a lot of manual implementation.
Now, with AI tools, developers can generate code faster, build prototypes, complete interface encapsulation, and even automatically execute some tests and refactoring tasks.
But this also brings new architecture risks.
A drop in code generation cost does not mean system complexity and long-term maintenance costs will also drop proportionally.
For example, AI can quickly generate a seemingly complete microservices project:
- API Gateway
- Service registration and discovery
- Redis cache
- PostgreSQL
- Message queue
- Docker Compose
- Kubernetes deployment configuration
- OpenTelemetry distributed tracing
From the code structure, the solution may look quite complete.
But are these components really necessary?
Does the team have the ability to operate them?
Have the exception paths been verified?
Does the generated code conform to the existing system's security boundaries, data model, and deployment constraints?
These questions still require human engineering judgment.
AI can speed up implementation, but it cannot automatically eliminate the consequences of wrong architecture decisions.
In AI-assisted development scenarios, the architect's responsibility becomes more important:
Shift from merely guiding how to write code, to defining system constraints, validating engineering quality, controlling complexity, and managing architecture evolution.
Architects need to ensure generated code is compatible with the real system, rather than allowing unnecessary architecture complexity to accumulate quickly just because generation has become faster.
Similarly, AI can help analyze dependencies, search code, and propose transformation plans, but its conclusions still need to be verified in a real environment.
Code can be generated quickly, but engineering responsibility cannot be generated with one click.
8. How to avoid becoming an architect detached from engineering practice
Rather than requiring architects to submit a certain number of lines of code every month, it is better to establish working mechanisms that keep them in continuous contact with real systems.
Because commit count and lines of code do not equal architecture ability.
More effective practices are:
First, continuously participate in key code reviews
Focus on core modules, cross-service interfaces, database changes, and infrastructure-related code.
The purpose of code review is not only to find coding problems, but also to identify architecture risks as they form.
Second, regularly inspect system dependencies and technical debt
Combine static code analysis, dependency scanning, architecture fitness functions, and other means to continuously verify module boundaries.
For example, use automated checks to restrict illegal cross-module dependencies, preventing a previously clear architecture from gradually degrading during daily iteration.
Third, major technology selections must complete a proof of concept
For technologies with high introduction cost, build a PoC (Proof of Concept) first.
Verify not only whether the feature works, but also performance, compatibility, failure recovery, deployment process, and team usage cost.
Fourth, participate in major production incident retrospectives
Incidents often expose problems that are hard to find in design documents.
For example, timeout propagation, resource contention, dependency service avalanches, database connection exhaustion, and lack of effective degradation mechanisms.
This information can correct architecture assumptions in return.
Fifth, establish a traceable architecture decision mechanism
For major technical decisions, use ADRs to record background, candidate options, trade-offs, and later verification results.
When business conditions change, historical decisions are allowed to be re-evaluated.
Architecture is not a one-time design artifact, but a decision process that keeps adjusting as business and systems change.
9. Do not treat “not writing code” as a sin, and do not treat “knowing architecture” as a privilege
At this point, it is necessary to avoid going to the other extreme:
Thinking that as long as an architect no longer takes on daily development tasks, they must lack architecture ability.
This judgment is also inaccurate.
Architects in large organizations do need to devote significant energy to technical strategy, cross-team collaboration, platform governance, and setting system evolution roadmaps.
Requiring them to continuously carry a large number of business requirements like ordinary developers is not necessarily the most reasonable arrangement of responsibilities.
What should really be focused on is whether a person's technical judgment is still built on sufficient engineering evidence.
A person who does not directly write business code for a long time, but continues to dig into the codebase, participate in key designs, understand production systems, and can drive complex transformations to completion, may still be an excellent architect.
Conversely, a person who writes code every day but cannot understand business boundaries, system costs, and long-term evolution may not necessarily possess system-level architecture ability.
Therefore, the key is never the job title, nor the number of commits.
It is:
Whether a person can still accurately understand the real state of the system and take engineering responsibility for their technical decisions.
10. Conclusion: good architecture design is often about knowing what not to do
There is a common misunderstanding in software engineering:
Directly associating architecture complexity with technical level.
It seems that the more components introduced, the richer the design patterns, and the finer the system splitting, the more it proves technical strength.
But truly mature engineering practice is often the opposite.
If a clear module can solve the problem, there is no need to rush to split out an independent service.
If reasonable abstraction can isolate change, there is no need to build large infrastructure in advance.
If a simple, reliable solution can meet current needs, there is no need to add system burden for the sake of so-called technical advancement.
Of course, this does not mean always choosing the simplest implementation.
If business scale, reliability requirements, and organizational collaboration have already formed real pressure, the system should be actively evolved, rather than avoiding necessary changes under the excuse of “keeping it simple”.
Architecture design must avoid both over-design and long-term under-design.
There is no universal formula between the two; it can only rely on sufficient information, clear constraints, and verifiable engineering judgment.
So, in my view, whether an architect is excellent should not be judged first by how many technical terms they know, nor by how complex an architecture diagram they can draw.
It should be judged by whether they can answer three questions:
Why change?
Why change now?
Why is this change worth the corresponding cost?
If these questions cannot be answered, no matter how beautiful the architecture diagram is, it may just be a technical vision detached from reality.
If they can be answered clearly, then even if the final choice is an ordinary-looking, even not-so-“advanced” solution, it may still be the most reasonable architecture decision at the moment.
The value of architecture is not in making the system look complex, but in using controlled complexity to continuously solve real business problems.
Technical viewpoint: there is no absolute best practice in architecture, only reasonable decisions built on real constraints, clear goals, and engineering evidence.