Skip to content
academia.sh

Lesson 12 / 14

Forced Update Strategy

A forced update strategy decides when, with which prompt, and for which reason old clients that the server no longer supports are directed to the new version. This lesson measures how well the payment feature works on a client below the minimum version.

Published Updated

In this lesson

  • Explains what the minimum supported version is and why the server asks for it
  • Tells a soft prompt from a forced update and says when each is appropriate
  • Derives capabilities from the client's version and measures the operability of a feature on an old version
  • Shows by measurement that the severity of the update prompt does not determine operability
Contents

In the previous lesson the rollout advanced in stages and the old version lived without trouble for a while longer. Not every old version can live forever. At some point the server stops offering an old contract, a security fix makes it impossible for an old version to run, or a payment provider refuses to talk to an old client. This lesson describes how to prepare for that point.

The question of the course stays the same: how well does this feature work under this condition? In this lesson the condition is the version of the client.

The Minimum Supported Version

Every server has a range of clients it can actually talk to. The lower bound of the range is the minimum supported version: a client below it can connect to the server, but the server does not accept some of its requests. The lower bound is set by choice. The team does not want to carry the maintenance load of offering an old contract, or cannot accept the old client for a security reason.

Setting the lower bound is a data decision, not a code decision. The team looks at the version distribution in the field: which versions are still in use, and how many users each has. Raising the lower bound without knowing the distribution means meeting some of the users with a block they were not warned about.

The lower bound is kept on the server and the client reads it remotely. If the lower bound is embedded in the application, changing it requires a new version, and the purpose of the lower bound, directing old clients, does not happen, because an old client never sees the new bound.

Soft Prompt and Forced Update

An update prompt takes two forms.

A soft prompt tells the user that a new version is out, and the user can postpone it. The prompt appears as a banner, a dialog, or a line on the settings screen. The purpose of a soft prompt is to steer clients that have not yet fallen below the lower bound toward the new version.

A forced update makes the application unusable and shows only the update screen. The user cannot use the application without updating. A forced update is shown only when the client is below the lower bound, that is, when the server really cannot talk to it. If it is used more broadly, it blocks the user over a version that does them no harm.

The two fragments below show how the policy sits on the server and how the prompt decision is made. The fragments are not measured.

# Platform code: update policy
update-policy:
  hard-minimum: 5
  latest: 6
// Platform code: update prompt
if installed < policy.hardMinimum {
    showBlockingUpdateScreen()
} else if installed < policy.latest {
    showUpdateBanner()
}

The decision comes down to two comparisons. If the installed version is below the minimum version, the prompt is forced; if it is below the latest version, the prompt is soft. Versions between the two bounds keep using the application.

The End of Support for an Old Version

Support is not cut in a single day. The team first announces, then steers with a soft prompt, and finally turns on the forced update. Each step depends on seeing that the previous one was not effective enough. If the soft prompt has moved enough users to the new version, the forced step may not be needed.

Communicating the end of support is also work. The text of the forced screen must tell the user why they need to update; “a new version is required” is not enough, because the user reads that as marketing language. When the reason is concrete, for example “the payment provider no longer supports this version”, the user sees the update as a requirement, not as an obstacle.

The Delay of Store Review

A forced update depends on the new version being ready in the store. At the moment the lower bound is raised, the new version must be available to users; otherwise the forced screen leaves a user who finds nothing to update in a dead end.

How long store review will take is not known in advance, and the team cannot set it. The order therefore has to be this: the new version is published in the store and offered to users first; only after that is the lower bound raised. Raising the lower bound is a remote value change and takes effect immediately. That speed is dangerous: a change made in the wrong order leaves a user with no update available on their device without a usable application.

The possibility that the review rejects the build is also part of the plan. If an urgent lower-bound increase is needed for a security reason and the new version has not yet been approved, the team is left with a choice between raising the lower bound and waiting. That choice is a risk decision, not a code decision.

Measuring the Condition of the Update

For the measurement, take three features. Payments is the payment; it needs the network and the min:5 capability, that is, a client at the fifth major version or above. The payment provider does not accept an old client, so it has no fallback. Catalog is the product list; it also needs min:5, but it can work with the old fields on an old client, and its fallback is degraded. Settings is the settings and needs only the network. The core is familiar from the first lesson and is given again in the same form in every lesson; the lesson does not touch it.

// operability.mjs — classifies how fully a feature works under a condition
export const LEVELS = ["full", "degraded", "deferred", "disabled"];
const FALLBACKS = ["degraded", "deferred"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "full";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "disabled";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} missing=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`affected: ${records.filter((r) => r.level !== "full").length}`);
}

After the core comes the lesson’s helper file. minimumFor(client) produces the min: capabilities from the client’s major version: a client at the fourth version carries min:1 through min:4 and does not carry min:5. conditionsFor(clients) adds the network to these capabilities and builds a condition from each client. promptFor(client, policy) says which prompt the client will get; it is a local function of this lesson and is not an input to the rule.

// updates.mjs — builds a condition from the client version, tells the update prompt
import { feature, condition } from "./operability.mjs";

export function minimumFor(client) {
  const levels = [];
  for (let level = 1; level <= client.major; level++) levels.push(`min:${level}`);
  return levels;
}

export function conditionsFor(clients) {
  return clients.map((client) => condition(client.name, ["network", ...minimumFor(client)]));
}

export function promptFor(client, policy) {
  if (client.major < policy.hardMinimum) return "forced";
  if (client.major < policy.latest) return "soft";
  return "none";
}

export const updateFeatures = [
  feature("Payments", ["network", "min:5"]),
  feature("Catalog", ["network", "min:5"], { "min:5": "degraded" }),
  feature("Settings", ["network"]),
];

RO7. The model reduces the minimum version to a single major version number: the client is either at the fifth major version and above, or it is not. A real lower bound can include the minor and patch versions as well, and the payment provider’s request may depend on another variable, for example a compatibility table kept on the server side. The model builds this operability’s rule; the model is not the framework itself.

The first measurement puts three clients side by side: clients at the fourth, fifth, and sixth major versions. The prompt each client will get is printed first, then the nine records follow. The policy defines the lower bound as five and the latest version as six.

// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { conditionsFor, promptFor, updateFeatures } from "./updates.mjs";

const clients = [
  { name: "client-v4", major: 4 },
  { name: "client-v5", major: 5 },
  { name: "client-v6", major: 6 },
];
const policy = { hardMinimum: 5, latest: 6 };
for (const client of clients) {
  console.log(`prompt: ${client.name} ${promptFor(client, policy)}`);
}
printLevels(assess(updateFeatures, conditionsFor(clients)));
prompt: client-v4 forced
prompt: client-v5 soft
prompt: client-v6 none
disabled: Payments @ client-v4 missing=min:5
degraded: Catalog @ client-v4 missing=min:5
full: Settings @ client-v4 missing=-
full: Payments @ client-v5 missing=-
full: Catalog @ client-v5 missing=-
full: Settings @ client-v5 missing=-
full: Payments @ client-v6 missing=-
full: Catalog @ client-v6 missing=-
full: Settings @ client-v6 missing=-

The client at the fourth version gets a forced update and its payment is disabled; the missing capability is min:5, and the payment has no fallback. On the same client the catalog stays degraded, because the catalog has a fallback defined, while the settings are full. The client at the fifth version gets a soft prompt and all three of its features are full; the client at the sixth version gets no prompt, and it is full as well.

Look at when the forced prompt is shown: only on the client where the payment is disabled. This is not a coincidence. A forced prompt makes sense at the point where a feature no longer works on the client; showing a forced prompt when nothing is disabled blocks the user for no reason.

The Effect of the Update

When the user updates, the client’s major version changes and the condition is rebuilt. Updating is the only way to open the disabled feature; the team cannot open the payment on the old client by changing a remote value, because what is missing is not on the server side but in the client itself.

The second measurement runs the same device before and after the update. The condition names differ, because two conditions cannot share a name. Only the payment and catalog records are printed.

// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { conditionsFor, updateFeatures } from "./updates.mjs";

const before = { name: "before-update", major: 4 };
const after = { name: "after-update", major: 5 };
console.log(`update: ${before.major} -> ${after.major}`);
const records = assess(updateFeatures, conditionsFor([before, after]));
printLevels(records.filter((r) => r.feature !== "Settings"));
update: 4 -> 5
disabled: Payments @ before-update missing=min:5
degraded: Catalog @ before-update missing=min:5
full: Payments @ after-update missing=-
full: Catalog @ after-update missing=-

Before the update the payment is disabled and the catalog is degraded. After the update both are full. The settings record was left out by the filter because it did not change.

RO8. The model assumes the update is a single step and succeeds. In reality an update can stop halfway, the device may not have enough space, or the user may close the update screen and abandon the application. The rule does not see these intermediate states; it measures only the two conditions before and after the update.

The Bounding Measurement

The focus record of this lesson is Payments @ client-v4. The bounding measurement shows two things together. First, the focus record is disabled, and on the same client the catalog stays degraded and the settings stay full; two of six records are affected. Second, the severity of the update prompt does not enter the record: whether the same client gets a forced or a soft prompt, its record is the same.

// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { conditionsFor, promptFor, updateFeatures } from "./updates.mjs";

const clients = [
  { name: "client-v4", major: 4 },
  { name: "client-v5", major: 5 },
];
const records = assess(updateFeatures, conditionsFor(clients));
printLevels(records.filter((r) => r.condition === "client-v4"));
countLevels(records);

const strict = promptFor(clients[0], { hardMinimum: 5, latest: 6 });
const lenient = promptFor(clients[0], { hardMinimum: 3, latest: 6 });
console.log(`prompt: ${strict} ${lenient}`);
const key = (list) => list.map((r) => `${r.level}:${r.feature}:${r.missing.join(",")}`).join("|");
const again = assess(updateFeatures, conditionsFor(clients));
console.log(`prompt does not enter the record: ${key(records) === key(again) ? "yes" : "no"}`);
disabled: Payments @ client-v4 missing=min:5
degraded: Catalog @ client-v4 missing=min:5
full: Settings @ client-v4 missing=-
full: 4
degraded: 1
deferred: 0
disabled: 1
affected: 2
prompt: forced soft
prompt does not enter the record: yes

The first three lines carry the lesson’s operability: on the same client the payment is disabled, the catalog is degraded, and the settings are full. The count lines say that of six records, four are full, one is degraded, and one is disabled; affected is two.

The last two lines show the limit of the model. For the same client, two policies produce two different prompts: one forced, one soft. But the function that builds the condition never receives the policy, and the records are the same in both cases. The prompt is the way the team talks to the user; operability classifies which capability the client has. A forced prompt does not open the payment, and a soft prompt does not disable it. For the payment to open, the client has to be updated, and the prompt only directs the user to that update.

This distinction determines the order of decisions. First it is measured which feature is disabled on which version, and then the severity of the prompt is chosen accordingly. Setting up a forced prompt for a version on which no feature is disabled is a severity the record does not support.

Summary

  • This lesson’s operability: disabled. Payments @ client-v4 came out disabled because the client lacks the min:5 capability and the payment has no fallback; on the same client Catalog stayed degraded and Settings stayed full.
  • The minimum supported version is kept on the server and read remotely; if it is embedded in the application, an old client never sees the new bound.
  • A soft prompt can be postponed and steers old versions above the lower bound; a forced update stops only the client below the lower bound.
  • Of six records, four were full, one was degraded, and one was disabled; affected was two. The update moved the payment from disabled to full and the catalog from degraded to full on the same device.
  • The severity of the prompt did not enter the record; before raising the lower bound, the new version has to be ready in the store.

Next Step

In this lesson the client’s version was a condition, and the update changed it. An update also has a cost: the application that is downloaded and installed takes up space on the device. The next lesson, App Size Management, covers the effect of app size on the decision to download, on-demand asset packs, and packages split by platform. It then measures how well a feature that depends on an asset pack not delivered in the first download works.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close