Complete guide · Technology

Eight software practices that keep business apps reliable

Small engineering habits that prevent double payments, lost files, silent failures, and risky releases in the software a business depends on.

By OdooMatrix Published 9 min read

How to use this guide

None of these practices needs a large team or a new framework. Each one is a single decision you can make in an existing application, with a test that shows it works.

1. How to write the contract for an HTTP API

A contract is the list of what the caller may send and what they will get back, including the failures.

Describe one operation

Write the method, the path, who may call it, and the fields. Mark which fields are required.

List the failures

Say what a missing field, a forbidden caller, and a missing record look like. A client cannot handle an error you have not named.

Give one example each

Include one successful body and one failed body. Keep real customer data out of the examples.

Version a breaking change

Adding an optional field is usually safe. Removing or renaming a field needs a new path or a version, and a date when the old one stops.

Common questions

Is a conversation enough?

No. Write it down where the caller can read it without asking you.

Do I document every future idea?

Document the operation you are ready to keep. Mark the rest as not available.

What if the implementation disagrees with the contract?

Fix one of them before a second team binds to the wrong behavior.

2. How to call a JSON API from a business system

An integration is ready when a rejected call is as understandable as a successful one.

Name one operation

Write the record you will create or read, the address, and the fields you send. Do not start by syncing every object the other system offers.

Keep the secret out of logs

Store the key in the place your host provides for secrets. Logs and error messages should show a request id, not the key.

Prove a success

Send one call that should work. The other system should show the record, and your system should store the other side’s id.

Prove a rejection

Send one call with a missing required field. Your user should see a clear failure, and you should be able to try again without creating a duplicate.

Common questions

Should I import the whole history first?

No. Prove one record in each direction, then decide what history is worth moving.

What if the other system times out?

Record the timeout and whether it is safe to retry. A retry that creates a second order is a failed integration.

Do I put the key in the database?

Only in a secret store or an encrypted setting. Do not put it in source code or a ticket.

3. How to retry a payment request without charging twice

A timeout is not proof that the charge failed. The retry has to be the same payment, not a new one.

Choose a stable key

Give each payment attempt a key that stays the same when you retry. A new random key on every click is a second charge waiting to happen.

Store the result

When the provider answers, save the key with the provider’s payment id and the status. A later retry should find that row first.

Retry on purpose

Simulate a lost response and send the same key again. The provider or your own check should return the original payment, not create another.

Treat a changed amount as new

If the amount or the customer changes, use a new key. Reusing the key for a different charge hides the difference.

Common questions

Is a unique order number enough?

Only if the same order cannot be charged twice on purpose, such as a deposit and a balance. Use a key per charge.

What if the provider has no idempotency key?

Store your own key and refuse to send a second create when the first result is unknown until you look it up.

Should the user click Pay again?

Let them retry the same attempt. Do not start a new charge while the first is unknown.

4. How to cache a slow read that can be slightly old

A cache is a copy. Use it when a slightly old answer is acceptable, and say how old.

Pick a read that can be old

Choose something expensive and safe to delay, such as a public article or a translated label. Do not start with a stock quantity or a payable balance.

Set an expiry

Store the answer with a time limit you can explain, such as one minute or one hour. An entry with no expiry is a bug.

Prove it expires

Read once, change the source, and confirm the old answer disappears after the expiry. If it never changes, the expiry is not applied.

Leave money and stock alone

Read price, tax, and on-hand quantity from the current record when the business will act on them. A cached price that undercharges is not a performance win.

Common questions

Will a cache fix a slow query by itself?

It hides the query until the expiry. Fix the query if the miss is still too slow.

Can two servers have different copies?

Yes, until each one expires. Do not use that for a value that must be the same everywhere right now.

Should I cache logged-in pages?

Only when the page does not contain that person’s private data. Private pages are easy to leak through a shared cache.

5. How to store uploads outside the application process

A file that lives only in the request, or that anyone can download by changing an id, is not stored.

Pick a store

Put uploads on a disk or object store that survives a restart of the web process. Keep the path or object key on the record, with the owner.

Limit what you accept

Allow the types the feature needs and a maximum size. Reject the rest before you save.

Check the owner

Download as the owner and succeed. Download as another user and fail. Guessing a nearby id must not return the file.

Delete with the record

When the business record is removed, remove or retain the file on purpose. Do not leave customer files with no owner.

Common questions

Can I store files in the database?

You can for small files, but large uploads will bloat backups. Decide before the first real customer file.

Should the file name be the download name?

Show a safe display name. Do not trust the original path from the browser.

What about a public brochure?

Mark it public on purpose. Everything else stays private to the owner.

6. How to log a failed job so you can find it later

A log is useful when you can find the one job that failed. A page of unmarked text is not a log.

Add a correlation id

When a job starts, give it an id and write that id on the record the user can see, such as an order or an import.

Log a few fields

On failure, log the id, the job name, and the error. Do not log passwords, card numbers, or access keys.

Find it once

Trigger one failed test job and search the logs for its id. You should land on that failure, not on an unrelated error.

Decide how long logs live

Keep them long enough to investigate a customer report. Do not keep secrets you were not supposed to log in the first place.

Common questions

Is print output enough?

Only if it includes the id and you can search it. A console on a laptop is not a production log.

What if the job failed before it had an id?

Log the failure at the edge that received the request, with whatever id the caller sent.

Should I log the whole request body?

No, if it can contain secrets or personal data. Log the id and the field that failed.

7. How to release a feature behind a flag

A flag lets you deploy code without forcing every user onto the new behavior.

Default it off

The new behavior should be invisible until the flag is on. Deploy that and confirm the old path still works.

Turn it on for one user or one test

Enable the flag only for the account you will check. Walk the new path once and write down what you saw.

Turn it off

Disable the flag without deploying. The old path should return immediately. If you need a deploy to undo it, it is not a flag.

Delete the flag later

When the feature is the default for everyone, remove the flag and the old branch. A permanent flag is a second product.

Common questions

Is a flag a substitute for a backup?

No. It only switches behavior. Take a backup when the change writes data you cannot undo.

Can every feature be flagged?

Flag the ones you may need to turn off. A database migration still needs its own plan.

Who may turn the flag on?

Name those people. An unused flag that anyone can flip is an incident.

8. How to translate one product screen

Translate a catalog of strings. Do not build sentences by gluing translated words together.

Extract one screen

Move the visible labels and messages on one screen into the message catalog. Leave names the user typed, such as a product title, as data.

Translate that screen

Add one language and translate every string on the screen. Switch the user to that language and read the screen.

Check the length

A translated label is often longer. Buttons and headings should still be readable at a phone width. Rewrite the source string if the translation cannot fit.

Find what you missed

Any string still in the original language is a hardcoded label or data. Fix the label. Translate the data in the record, not in the catalog.

Common questions

Should I translate the database?

Translate records, such as product names, in the product. The catalog is for interface text.

Can I concatenate “Hello ” and the name?

Translate the whole sentence with a placeholder for the name. Word order changes between languages.

Does this replace an Odoo language?

No. Odoo has its own language activation. Use that for the ERP, and this approach for a product you are building.

9. Related guides and next steps

Scope

Sample names and amounts are illustrative. They are not a client result, a price, or legal or tax advice. Menus can differ between versions and installed apps.

Continue learning

Want this set up for you?

Describe your business, the system you use now, and what has to change. See our Custom software development service or contact OdooMatrix.

← Back to all articles