Part 01 · The Backend Mindset

Understanding Business Logic

BusinessLogicRules

The rules that make your product your product, and why they belong on the server.

⏱ 1 hours · Station 8 of 153

Two apps, two different prices

A food delivery company has a mobile app and a website. A customer orders the same biryani on both, a minute apart. The app charges 380 taka. The website charges 440.

Both are "correct". Each was built by a different team, and each team wrote its own version of the rules: the minimum order, the delivery fee, the first-order discount. One team rounded the discount differently. One forgot that delivery is free above 500 on weekends.

The code that makes a product this product, its prices, limits, permissions and promises, is called business logic. This lesson is about recognising it, keeping it in one place, and keeping it separate from everything else.

The problem: the rules end up everywhere

Look at any growing backend and you'll find the same rule written several times:

Where the rule hidesWhy it got there
The mobile app"To show the total before checkout"
The websiteSame reason, different team
The HTTP handler"It was the quickest place to add an if"
A SQL queryWHERE subtotal >= 200, buried in a report
A nightly scriptSomeone needed the discount for a refund

Each copy is a chance to drift. When marketing changes "20% off, max 100" to "25% off, max 120", someone has to find every copy, and they won't.

There's a second problem: when the rules are tangled with plumbing (reading HTTP, talking to the database, formatting JSON), you can't test the rules without starting a server and a database, so nobody tests them properly.

Think of a cricket rulebook

A cricket match has many moving parts: the pitch, the scoreboard, the TV broadcast, the ticket gates. None of that is cricket. Cricket is the rulebook: what counts as out, how many balls in an over, when a no-ball is called.

The rulebook is written once, by one body, and every match follows it. The TV channel doesn't get its own version of the LBW rule. And you can understand and check the rules without a stadium: you can read them, discuss them, and test situations on paper ("ball pitched outside leg, not out").

Business logic is your product's rulebook. HTTP, the database and the user interface are the stadium.

many doors in, one set of rulesmobile appwebsitecall centreHTTPplumbingbusiness rulesopen? min 200? discount?databaseplumbing350 taka150 takarefused: minimum order is 200600 taka
Orders arrive from the app, the website and a call-centre tool. They all pass through the same rules in the middle; HTTP on one side and the database on the other are just plumbing. The 150-taka order is refused by the rules, whichever door it came through.

Where the analogy stops working. Cricket's rules change rarely and slowly. A business changes its rules constantly: new discounts, new cities, new payment methods, sometimes weekly. That's exactly why the rules must live in one clearly marked place: the more often something changes, the more it hurts to have copies.

The building blocks

  1. Business logic is the code that encodes decisions the business makes: who may do what, what things cost, what's allowed, what happens next. If a non-programmer in the company would have an opinion about it ("free delivery from 500"), it's business logic.
  2. Plumbing (also called infrastructure) is everything else: parsing HTTP, reading and writing the database, sending email, logging, retries. It's essential, but it isn't your product. Another company's backend has nearly the same plumbing.
  3. Keep the rules pure where you can. A function that takes plain values (subtotal, hour, is this a new user) and returns a decision, without touching the network, the clock or the database, is easy to read, test and reuse. The plumbing gathers the inputs, calls the rules, and acts on the answer.
  4. One home, many callers. The mobile app, the website, the admin tool and the nightly script all ask the backend; the backend asks the rules. A client may show an estimate, but the number that's charged comes from the one home (lesson 2).
  5. Rules have reasons. A good rule returns not just "no" but why ("minimum order is 200 taka"), so the client can show a helpful message and support staff can explain it.
Predict first

A new user orders 400 taka of food at 13:00 and pays cash. The rules: minimum 200; delivery 60, free from 500; new users get 20% off, at most 100 taka; cash only up to 3000. What do they pay?

Behind the scenes: one order, many rules

Step through it

An order passes the rulebook

AppHTTP handlerRulesDatabase

Step 1/5 The app sends the order

The app sends what the user chose: the items, the payment method. It may show an estimated total, but it doesn't send one.

items
biryani x2
payment
cash

The picture to keep in your head

Draw three rings:

  • The core: business rules. Plain functions and types: quote(order), canCancel(order, now), Plan.noteLimit. No HTTP, no SQL.
  • Around it: the application, which loads what the rules need, calls them, and saves the result.
  • The outside: HTTP, databases, queues, email providers, the clock.

Dependencies point inward: the outside knows about the rules, the rules know nothing about the outside. That's what lets you change the database, add a new client, or test every rule in milliseconds, without touching the rulebook. Module 5 builds this structure properly.

Try it: business logic or plumbing?

Try each case

Is this a business rule?

if subtotal < 200 { refuse }
works: Business logic. The company chose 200; it could choose 150 tomorrow. Someone in sales would have an opinion about it.

Code from first principles

A food delivery shop's rules in one pure function, quote. It takes plain values (the subtotal, the hour, whether it's a first order, the payment method) and returns either the amount to pay or the reason the order is refused. No HTTP, no database, no clock: the caller passes the hour in.

The program runs seven orders through it, like a table of test cases.

package main

import "fmt"

type order struct {
	subtotal int    // taka, already worked out from the menu prices
	hour     int    // when it was placed, 0-23
	newUser  bool   // first order ever?
	payment  string // "card" or "cash"
}

// quote holds the shop's business rules, and nothing else: no HTTP, no database.
// It returns the amount to pay, or the reason the order can't be placed.
func quote(o order) (int, string) {
	if o.hour < 10 || o.hour >= 22 {
		return 0, "we deliver 10:00-22:00 only"
	}
	if o.subtotal < 200 {
		return 0, "minimum order is 200 taka"
	}
	if o.payment == "cash" && o.subtotal > 3000 {
		return 0, "cash on delivery only up to 3000"
	}
	delivery := 60
	if o.subtotal >= 500 {
		delivery = 0 // free delivery from 500
	}
	discount := 0
	if o.newUser {
		discount = min(o.subtotal*20/100, 100) // 20% off the first order, at most 100
	}
	return o.subtotal - discount + delivery, ""
}

func main() {
	cases := []struct {
		name string
		o    order
	}{
		{"small lunch", order{150, 13, false, "card"}},
		{"regular lunch", order{350, 13, false, "card"}},
		{"family dinner", order{600, 20, false, "cash"}},
		{"first order, big", order{600, 20, true, "card"}},
		{"first order, small", order{400, 13, true, "cash"}},
		{"office party", order{3500, 12, false, "cash"}},
		{"midnight snack", order{300, 23, false, "card"}},
	}
	fmt.Printf("%-19s %8s %5s %-4s %-5s %s\n", "order", "subtotal", "time", "new", "pay", "result")
	for _, c := range cases {
		total, reason := quote(c.o)
		result := fmt.Sprintf("pay %d", total)
		if reason != "" {
			result = "refused: " + reason
		}
		isNew := map[bool]string{true: "yes", false: "no"}[c.o.newUser]
		fmt.Printf("%-19s %8d %02d:00 %-4s %-5s %s\n", c.name, c.o.subtotal, c.o.hour, isNew, c.o.payment, result)
	}
}

Output (the same in all five languages):

order               subtotal  time new  pay   result
small lunch              150 13:00 no   card  refused: minimum order is 200 taka
regular lunch            350 13:00 no   card  pay 410
family dinner            600 20:00 no   cash  pay 600
first order, big         600 20:00 yes  card  pay 500
first order, small       400 13:00 yes  cash  pay 380
office party            3500 12:00 no   cash  refused: cash on delivery only up to 3000
midnight snack           300 23:00 no   card  refused: we deliver 10:00-22:00 only

Reading the output:

  • small lunch / midnight snack / office party: each is refused by a different rule, with a reason a person can read. The rules are checked in a deliberate order: shop open first, then the minimum, then payment limits.
  • regular lunch: 350 + 60 delivery = 410.
  • family dinner: 600, delivery free because the subtotal is at least 500.
  • first order, big: 20% of 600 is 120, capped at 100, so 500; delivery is free.
  • first order, small: 400 − 80 + 60 = 380, the answer to the predict question.
  • Notice what you didn't need: a server, a database or a real clock. Seven situations, checked in microseconds. That's the payoff of keeping the rules pure.

Real engineering

  • Give the rules a home. Many teams use a domain or core package for business rules and keep handlers thin: parse, call the rules, respond. (Module 5, lessons 6 and 11.)
  • Test the rules like a table. Exactly like the output above: inputs in, expected answer out, dozens of rows. Rules are where bugs cost money, and pure functions are the cheapest code to test.
  • Make time an input. Rules that call now() inside are hard to test ("what happens at 21:59?"). Pass the time in, as quote does.
  • Store the result, not just the inputs. Save the total, discount and fee an order actually got. When the rules change, old orders must not change with them (lesson 4).
  • Some rules become configuration. A threshold that changes every week (free delivery from 500) can move into a settings table that the rules read, so changing it doesn't need a deploy. The rule stays in code; the number becomes data.

When it breaks

Try each case

Pick a failure

app: 380 taka
website: 440 taka
same cart
breaks: Each client calculated the price itself. Move the calculation to the backend; clients ask for a quote and show what comes back.

Think before you read on

The mobile team asks: "Can we keep a copy of the discount rules in the app, so we can show the total instantly without waiting for the server?" Is that allowed?

Reveal

Yes, as an estimate. Showing a quick total is good for users. But the app's copy must never be the one that decides what's charged: the backend recalculates with its own rules, and the app replaces its estimate with the backend's answer. And to keep the copies from drifting, many teams have the backend send the current rule values (or a quote endpoint) instead of hard-coding them in the app.

Check yourself

1. What's a quick test for whether some code is business logic?

Answer

Would someone in the business (sales, finance, support, a manager) have an opinion about it, or want to change it? Prices, limits, permissions and promises are business logic; parsing, retries and database access are plumbing.

2. Why is a pure quote(order) function easier to work with than one that reads the database and the clock itself?

Answer

It can be tested with plain values in microseconds, covering edge cases like 21:59 or exactly 500 taka, and it can be called from any client path without dragging I/O along.

3. Why should an order store the discount it received, even though the rules could recompute it?

Answer

Because the rules will change. The order is a historical fact; recomputing with next month's rules would rewrite what the customer actually paid.

4. The app shows an estimated total using its own copy of the rules. What must the backend still do?

Answer

Recalculate the total with its own rules and charge that amount, ignoring the client's number (lesson 2).

Your challenge: a weekend rule

The business adds a rule: on Fridays, delivery is free from 300 taka instead of 500.

  • Add the day of the week as an input to quote, the same way the hour is.
  • Add two rows to the table: a 350-taka order on a Friday and the same order on a Tuesday.

Before you run it, predict both totals.

You've succeeded when the Friday order pays 350 and the Tuesday order pays 410, every existing row prints exactly what it did before, and quote still doesn't read the clock itself.

The mental model

Business logic is your product's rulebook: prices, limits, permissions and promises. Plumbing is everything that moves data around it. Give the rules one home on the backend, make them plain functions of their inputs, and let every client and script go through them. Clients may estimate; the rules decide.

Station stamp

Finish the chapter

Done reading? Press Complete to save your progress. XP is Pro-only — on Pro this chapter is worth +10 XP.

Go Pro to earn XP →