The second app can expose a decision the first app allowed a team to postpone. Should it copy the first app’s account, billing, logging, and release code, or depend on shared mechanisms? By the fifth app, the answer can determine how many systems the team must understand whenever a dependency changes.
A software factory needs a repeatable operating foundation. It also needs to avoid building a grand platform before any app has earned a customer. The title expresses a direction: share demonstrated capabilities and conventions so a portfolio does not become fifty unrelated maintenance obligations.
It does not require every app to share one database, one deployment, or one user experience. Those choices should follow data boundaries, failure consequences, and operating needs.
This cornerstone of The AI Software Factory, within the SalarsNet AI section, proposes a platform design for a small team. It distinguishes useful shared foundations from premature infrastructure. No Salars platform effectiveness or cost-saving experiment is claimed.
Define the platform by the work it repeats
A platform should reduce repeated work that real apps need. Its value does not come from the number of abstract services in a diagram.
Start with capabilities such as account access, job records, input validation, notifications, usage accounting, or release conventions only when actual app requirements support them. Each needs a contract and an owner.
A hypothetical file-conversion app and listing-preparation app may both need an upload intake, a job status, and a reviewed output. They need not share the rules for interpreting supplier identifiers or describing item condition.
The shared foundation could handle the mechanical lifecycle while each app retains its domain decisions. This boundary prevents a general job service from accumulating every product’s business rules.
The capabilities chapter examines how to identify those reusable jobs. Platform design arranges the proven capabilities and operating conventions into a coherent foundation.
Begin with two concrete consumers
An abstraction based on one use case can conceal its assumptions. A second appropriate consumer helps reveal what is common and what is specific.
For the hypothetical apps, an upload contract might work for small tabular files but not for large image collections. A generic output record might work for a downloadable file but need a different review state for a listing draft. Those differences should be examined before generalizing.
Two consumers are a useful proposed design discipline, not a scientific minimum or universal rule. Some security or operating conventions deserve shared treatment from the first app. The point is to obtain concrete requirements before claiming broad reuse.
When the second app needs different behavior, avoid forcing both through a vague interface. Preserve a small common contract and let the product-specific work remain local. Generalization should simplify the actual consumers.
Record the evidence that justified each shared mechanism. Later developers should know whether it arose from observed repetition or from an untested expectation about future apps.
Separate product identity from platform identity
Customers buy a result for a task. They should not need to understand the internal platform before using it.
Each app needs a clear promise, supported scope, onboarding path, and support responsibility. Shared infrastructure can remain behind that experience. A platform brand may be useful commercially, but it should not obscure what the app actually does.
The platform record serves operators: which capabilities exist, which apps consume them, where data live, and how changes are released. The customer-facing app record serves the buyer: what result they receive, what it costs, and what they must review.
Do not turn the platform into a generic dashboard containing every capability just because those capabilities share code. A customer using a narrow converter may need one clear task flow, not a menu of fifty future ventures.
The app-template chapter examines repeated setup, while the app registry tracks deployed products and lifecycle. Platform identity connects those records without replacing them.
Share conventions before centralizing services
A team can gain consistency through common conventions without immediately deploying a new shared service. Naming, configuration, logging fields, error categories, test requirements, and release records can be standardized in the existing repository.
For example, every job could have an app identifier, customer boundary, input reference, state, result reference, and attempt history. The exact schema should follow actual requirements. The shared convention helps operators understand jobs across apps even if implementations remain separate.
A template can apply the convention to new apps. A shared package can implement stable local behavior. A central service can be justified when several consumers genuinely benefit from one operated capability.
Choose the smallest form that handles the repeated work. A central service introduces network, availability, versioning, and support obligations. A local package introduces version coordination. A convention introduces enforcement and documentation work.
These are different costs, not stages every team must pass through automatically. The platform should make the obligations manageable under the team’s capacity.
Keep domain boundaries explicit
A platform can become tangled when one app reaches directly into another’s data or assumes its internal workflow.
Define which module or service owns each business concept. A supplier mapping belongs to the capability or app responsible for interpreting that mapping. A listing approval belongs to the listing workflow. A shared job record should not decide either domain rule.
Expose a narrow interface when another app needs a result. The interface should express the business operation and its conditions rather than allow unrestricted access to internal tables.
A hypothetical listing app might request a validated item record and receive a result with provenance. It should not edit the supplier converter’s internal mapping tables simply because they share a database.
The modular-monolith chapter examines deployment choices. Clear domain ownership matters within one deployment as well as across several services. Physical separation alone does not establish a sound boundary.
Protect customer and tenant boundaries
Shared infrastructure increases the importance of separating customer contexts. A capability must know which records and operations belong to the requesting customer and app.
Do not assume that a shared account system implies permission to share all customer data across products. The product’s promise, access rules, and applicable requirements determine what can be used.
A job identifier should be interpreted within an authorized context. The platform must enforce access where records are read or changed, not rely only on a user interface hiding other customers’ links.
Use harmless test cases that attempt inappropriate cross-customer access. Expected outcomes should be specified independently of the implementation. A successful ordinary request does not demonstrate isolation.
The platform also needs a policy for support access. Operators may need to diagnose a failure without viewing every customer’s content. Logging and redacted reproduction cases can reduce unnecessary exposure while preserving useful evidence.
The agent-security chapter covers broader authority. Platform design should make narrow authority practical for every app, including AI-driven workflows.
Share authentication carefully; keep authorization local to the task
A common sign-in mechanism can establish an identity. It does not decide what that identity may do in each app.
Authorization should reflect the app, customer boundary, role, operation, and resource. A user allowed to prepare a listing draft may not be allowed to publish it. A supplier analyst may read records without permission to alter purchasing details.
The shared platform can supply consistent primitives and enforcement patterns. Each product still needs the rules appropriate to its task. A generic “admin” flag can become too broad when many apps share it.
Make action boundaries visible in the capability contract. Preparation, review, and commitment can be separate operations with different permissions. This allows useful automation without granting every worker the authority of the whole platform.
Avoid storing powerful credentials where generated code can freely read them. Use the existing supported credential and operation boundaries appropriate to the environment. The architecture should align access with the task rather than depend on an agent’s promise to behave.
Use a common job lifecycle without hiding exceptions
Many apps perform work that outlasts one request. A shared job lifecycle can help with status, attempts, results, and recovery.
Define states that correspond to actual conditions. A job might be received, validating, awaiting review, completed, rejected, or failed. A retrying job and a job awaiting a customer’s decision are different states.
For the hypothetical converter, missing required fields might produce a rejected input. Ambiguous mappings might require review. An external service interruption might produce a recoverable failure. Combining all three as error makes support and automation less precise.
Keep the lifecycle mechanism separate from domain interpretation. The platform can record that review is required; the app explains what the reviewer must decide.
A job should preserve enough history to diagnose what happened without storing unnecessary private content. Attempt identity and result references can help distinguish a new job from a retry. The idempotency chapter develops duplicate-effect control in detail.
Make cost attribution possible
Shared infrastructure can obscure which app consumes resources. A portfolio needs enough attribution to distinguish a valuable product from one that creates disproportionate cost or support.
Record usage at the level that informs decisions: app, customer context, task type, provider operation, and result where appropriate. Do not collect detailed content merely to allocate expense.
Separate observed provider charges from estimated internal time and unobservable costs. A job’s API charge can be available while its support burden is known only from operator records. Both can matter without being combined into false precision.
A shared fixed cost can be allocated by a stated convention, but the convention should not be mistaken for a causal cost. Adding one app may not increase an already-paid service cost; removing it may not reduce that cost immediately.
The platform should supply the facts needed for a software portfolio decision, while the business analysis explains the allocation. A dashboard cannot create profitability by hiding unattributed work.
Design for failure containment
One shared capability can affect several apps. Its failure consequences should be understood before the team centralizes it.
If a notification mechanism stops, can apps retain completed work and send messages later? If a model provider is unavailable, can the task pause or use a reviewed fallback? If billing status cannot be confirmed, what operations remain allowed under the product’s policy?
Avoid automatic assumptions about graceful degradation. A fallback can be wrong or unauthorized. Define the behavior that preserves the customer’s task and boundaries under the specific failure.
A shared platform needs observability sufficient to identify which consumers are affected. A common incident record can connect the capability failure to app-level symptoms without exposing unrelated data.
Centralization can reduce duplicated work and increase shared failure impact. The design should acknowledge both. The appropriate boundary depends on the consequence, recovery options, and team capability.
Choose deployment boundaries from operating needs
A shared platform does not require microservices. A modular monolith can keep domain boundaries within one deployment. Independent services can be justified when scaling, ownership, isolation, or release needs differ materially.
Microsoft’s architecture guidance describes independently deployable services and service-owned data, while identifying distributed-system challenges such as consistency, transactions, interservice communication, testing, and versioning. That documents tradeoffs; it does not prove one architecture always wins. Microservices architecture style.
For a small team, consider who will operate each boundary. A diagram with many services creates jobs: deployment, configuration, monitoring, access control, compatibility, and recovery. Managed infrastructure can help, but it does not remove the need to define the behavior.
Keep frequently changing coupled behavior together where practical. If two services always need coordinated updates, their physical separation may not provide the independence imagined.
Choose a boundary for a concrete reason and record the condition that would reopen it. Architecture can evolve as real usage reveals a need.
A shared package and a template solve different problems
A shared package lets consumers adopt an identified version of a mechanism. A template copies a starting structure. Their update behavior differs.
GitHub documents template-created repositories as copies of structure and files with a new initial commit, distinct from forks retaining parent history. Future changes to a template do not become an automatic shared runtime update in those copies. Creating a repository from a template.
Use templates for setup conventions and app-specific starting material. Use a shared package or operated capability when continued common behavior is important and its ownership is established.
Avoid copying security-sensitive logic into every app without an update plan. A later fix then requires finding and changing all copies. A template can point to a maintained mechanism rather than embedding its entire implementation.
The platform record should identify what is copied, what is versioned, and what is centrally operated. That distinction helps the team predict the effect of a change.
Test shared contracts and app outcomes
A capability’s tests should check its defined behavior. Each consuming app should also test the end-to-end outcome it promises.
For the hypothetical job lifecycle, capability tests might verify state transitions and attempt records. The converter’s app test checks that a reviewed input produces the correct inspectable output. The listing app’s test checks that a draft remains separate from publication authority.
Shared tests cannot cover every domain condition. App tests cannot replace the shared mechanism’s own evidence. The layers answer different questions.
Keep difficult cases from real permitted delivery and preserve protected later cases where transfer matters. If a case is used to adjust implementation, mark it development evidence. Do not claim broad reliability from the cases used to build the mechanism.
The AI evaluation chapter examines variable model behavior. Deterministic platform rules and model outputs should receive checks suited to each rather than one vague quality score.
Control changes across consumers
A shared capability update should identify affected apps, expected behavior, checks performed, and recovery options. The change should have one authoritative artifact rather than a collection of informal local edits.
A narrow implementation fix may preserve the contract. A contract change can alter what consumers rely on and needs explicit coordination. Version the relevant boundary in a way the team can inspect.
Roll out through the existing authorized process. Test locally and in preview where appropriate, then verify actual production behavior before declaring completion. A successful upload does not establish that the intended version is serving users.
Preserve predecessor artifacts and understand data changes separately. Rolling back code may not reverse a write, migration, or external action. The platform should make these commitments discoverable.
The rollback chapter covers recovery design. Shared infrastructure increases the importance of knowing which apps and state a recovery affects.
Require a concrete consumer for platform work
A platform can become a refuge from uncertain customer work. Building a generic framework feels productive while the first app’s demand remains untested.
Ask each proposed shared mechanism which actual consumer needs it and which repeated obligation it reduces. If the answer concerns only imagined future apps, defer it unless a present risk independently justifies the work.
Avoid generalizing unstable domain behavior. A supplier mapping that changes with every customer may need more discovery before becoming a shared API. A broad abstraction can freeze assumptions the team has not understood.
Also avoid making the platform a mandatory dependency for every harmless utility. A small standalone tool can be appropriate when integration would add more work than it removes. Consistent records and release conventions can preserve coherence without forcing identical architecture.
The platform should enable useful apps, and its scope should be evaluated through those apps. Its own complexity is an operating cost.
Walk through one shared-capability decision
Suppose the hypothetical converter and listing app both need to accept a file, create a job, validate the input, and return a result for review. The team identifies job recording as a candidate shared mechanism.
The initial common contract can remain small: create a job in an authorized customer and app context, record an input reference, expose supported states, attach an outcome reference, and preserve attempt history. It does not need to know how a supplier code maps to an item or whether a condition description is accurate.
The converter uses the mechanism to record validation and transformation. The listing app uses it to record draft preparation and approval readiness. Both can share the lifecycle while retaining different acceptance criteria.
Now introduce a third hypothetical app that performs a long-running external write. Its job may need additional commitment and recovery states. The team should examine whether those states belong in a broader shared contract or a separate capability. Extending the first mechanism automatically can burden the two simpler consumers.
A good decision might preserve the original read-oriented lifecycle and add a separate commitment record linked to it. Another design might generalize the lifecycle if the semantics are clear and consumers remain understandable. The choice needs the actual task and failure conditions.
The key review question is whether a state has one meaning across consumers. If completed means a draft was prepared in one app and money was transferred in another, an operator could misunderstand the consequence. Product-specific labels and commitment boundaries should make that difference explicit.
This walkthrough illustrates how common mechanics and different consequences can coexist. The platform should preserve that clarity instead of compressing every app into one generic status.
Consider the economics of sharing
Shared work can reduce duplication while creating coordination. The business needs to observe both before claiming a saving.
For an illustrative planning case, assume three apps each require four hours to implement a similar logging convention independently. The total is twelve hours. A shared convention and helper might require six hours initially, plus one hour integrating each app, or nine hours before maintenance. Those hypothetical inputs imply three hours less initial work.
The illustration is incomplete until coordination and updates are included. If the shared helper requires several additional hours of compatibility work each month, the initial difference may not matter. If a single fix prevents three separate mistakes, the benefit can exceed the setup estimate. Neither outcome should be assumed without actual records.
At an assumed planning value of $30 per hour, the initial three-hour difference is $90. That is an illustrative resource valuation, not cash profit or a measured Salars saving. It should not become a return-on-investment claim.
Use an actual pilot to record setup, integration, update, support, and incident work. Distinguish a cost that disappears from one that moves to the platform owner. Centralizing an obligation can improve consistency while leaving its total size unchanged.
The economics also depend on reuse frequency. A shared mechanism maintained for one consumer may cost more than a local implementation. A stable mechanism serving several compatible consumers may spread review and update work. The contract and consumer pattern determine which situation applies.
Migrate gradually from unrelated apps
An existing portfolio may already contain duplicated mechanisms. Replacing them all at once can create unnecessary release risk.
Start with inventory and a common contract. Identify the copies, their versions, important differences, owners, and consumers. Some apparent duplication may represent legitimate differences in task or authority.
Choose one bounded migration. A harmless logging helper or read-only validation mechanism may be easier to move than authentication or billing. Preserve the existing behavior as a baseline and compare the relevant outcomes before changing more consumers.
Do not erase a product-specific rule merely because the shared version looks cleaner. Find out why it exists. The rule may encode a customer’s data condition or a safety boundary that the new abstraction missed.
After the first migration, examine the actual work and failures. Did the shared mechanism reduce an obligation? Did it introduce a new dependency? Could another maintainer explain it? Use that evidence to decide whether the next app should move.
A gradual transition can leave several versions temporarily. Record that state in the registry and catalog. Temporary variation becomes dangerous when operators believe the portfolio is uniform and troubleshoot the wrong behavior.
Retirement belongs in the platform design
Shared capabilities can outlive the apps that justified them. A service with no active consumer may still consume credentials, expense, and attention.
Record retirement conditions. A capability can be archived when no supported consumer depends on it and required records have been preserved under the applicable policy. A replacement can leave a compatibility path until consumers complete migration.
Do not delete customer data or historical evidence merely because code is retired. Data obligations and operational history need separate decisions and appropriate authorization.
Retirement should remove unnecessary access and operating cost while preserving enough provenance to explain earlier behavior. The record should identify the successor, affected consumers, and final state.
The possibility of retirement makes sharing more disciplined. A mechanism is maintained because current products need its contract, not because the platform once added it to a diagram.
Keep a platform decision record
For each shared mechanism, record the consumers, common contract, implementation form, owner, data boundary, authority, evidence, update process, failure consequences, and retirement conditions.
The record can live with the component catalog. Platform-wide conventions can have their own concise documentation, linked from app templates and registry entries.
Preserve disagreements and alternatives when they affect the decision. A service boundary chosen for isolation should explain the failure being isolated. A local package chosen for simplicity should explain how updates remain coordinated.
Review after meaningful changes: a new consumer, a scaling constraint, an incident, a rights change, or evidence that the current boundary impedes delivery. Avoid architecture churn driven only by fashion.
A record should also identify the conditions outside its evidence. A capability tested with one small customer group may need additional review before serving a different data boundary or consequential action. Preserving that limit prevents a successful pilot from becoming an unrestricted platform promise. The next consumer can then reuse what is established while investigating what changes.
The record makes platform design a series of reviewable commitments rather than a one-time grand blueprint.
Evaluate the platform through a bounded pilot
A proposed pilot could compare two small apps using the current process with the same apps using a limited shared foundation. Required correctness, customer separation, and authority boundaries would remain independent success criteria.
The baseline should be explicit: present setup, release, and maintenance work. Outcomes could include repeated work avoided, consequential omissions detected, time observed, and whether a new developer can operate the apps from the records.
Do not call the platform successful because it contains more shared modules. A shared module can create coupling without reducing burden. Include a counterexample where an app should remain separate or reject a proposed shared contract.
Reserve later app tasks before tuning the foundation. If they guide revisions, they become development cases. A fresh task is needed before claiming transfer to unfamiliar apps.
Stop if the pilot exceeds its authorized budget, cannot obtain representative tasks, or reveals that the shared foundation adds more obligations than it removes. Report inconclusive results honestly.
No such pilot has been executed here. Real app tasks, independently specified outcomes, and measured records would be necessary before making a local effectiveness claim.
What Would We Do at Salars?
A proposed Salars foundation would begin with a few app candidates that have actual problem and payment evidence. The team would identify repeated job, validation, access, and release needs rather than build an all-purpose venture platform first.
Each shared capability would have a contract, owner, scoped evidence, and consumer record. Apps would preserve their domain rules, customer boundaries, review points, and product promises. Templates would copy setup conventions while maintained capabilities carried common behavior.
The team would choose deployment boundaries based on actual operating needs and capacity. Shared cost attribution, failure records, and recovery plans would make portfolio decisions possible without assuming every app must share one database or service.
A bounded pilot could check whether the foundation reduces repeated work while preserving correctness and boundaries. Until executed, the design would remain proposed. No Salars platform saving, reliability result, or deployed factory is asserted here.
The platform becomes useful when each new app inherits a smaller burden without inheriting an unclear promise. Shared mechanisms should make the team’s commitments easier to understand and keep.
Sources
- Microsoft: Microservices architecture style, official benefits and distributed-system tradeoffs.
- GitHub: Creating a repository from a template, official copy and history behavior.
Sources inspected October 7, 2026. The platform design, hypothetical app pair, and pilot are proposals. No platform benchmark, production implementation, or measured Salars saving was executed for this article.
Loading comments…