No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

29KB

FOCUS Architecture — Chapter-19: Anti-Patterns: How to Wreck FOCUS

  • Source: /library/FOCUS Architecture/source-file.pdf
  • PDF pages: 583–615
  • Pages without text: none

Anti-Patterns: How to Wreck FOCUS In this chapter, you’ll: identify, in a diff, which of the six anti-patterns is present, naming the symptom; name each one’s damage in cost of change, what gets expensive six months later, not in an adjective; apply each card’s fix, with a citation to the chapter that taught the rule it breaks. The coffee shop’s slice got built piece by piece across chapters 10 through 17: a dumb View, an orchestrator that connects, a pure use case, a repository at the boundary, and a test suite covering all of it. This chapter is the same design seen in negative: the six most common ways to tear that slice down without a single test screaming on the day it happens. No new concept shows up here; every wreck breaks a rule you already know, and every fix points back to the chapter that taught it. Why a catalog of wrecks? Because code rots through shortcuts that look harmless in the diff. Nobody writes “coupling the slices” in a pull request description; they write “extracted a helper.” This chapter’s goal is immunization: after it, you look at a diff and name the wreck by its symptom, before the merge, while undoing it is still cheap. And so the catalog doesn’t turn into a witch hunt, every card ends with the legitimate exception:

the case where that same code is NOT an anti-pattern. Keep that part. A reviewer who memorized the ban and forgot the exception is a wreck of a different kind. The shape of the card The six cards follow the same skeleton, in the order the book has always worked in (anti-solution before solution, since chapter 2): Symptom: what shows up in the diff or on screen, the phrase you use in code review. Damage: the concrete cost of change six months later, measured in files touched, tests rewritten, or silent defects. Fix: the refactor, with a citation to the chapter that taught the rule. Legitimate exception: when that same code isn’t a wreck and the reviewer should let it pass. The six wrecks attack different points of the flow chapter 10 drew. The diagram marks where each one punctures the arrow:

Markers 1 and 5 puncture the orchestrator (an inline business rule and a domain try/catch). Marker 2 punctures the use case (which starts fetching its own data). Markers 3 and 4 puncture the repository (too generic or too ceremonial). Marker 6 is the only one that crosses two slices at once: the premature shared/ couples use cases from different features through a common helper. Now, the cards. Card 1: business rule in the orchestrator Symptom: the orchestrator decides instead of connecting. In the diff, a business calculation (discount, eligibility, total) shows up inside the event handler, instead of a call to chapter 14’s use case. The review comment is short: “that if is business, it doesn’t live here.” Here’s the wreck in code. Someone copied the discount rule into the orchestrator “because it was just an if,” and the copy aged: the use case learned the ItemOutOfStock variant, and the copied

switch never found out. Dart class TabOrchestrator { TabOrchestrator(this.tabs, this.loyaltyPointsByTable); final Map<int, Tab> tabs; final Map<int, int> loyaltyPointsByTable; String on(PayTab event) { final tab = tabs[event.table]!; final points = loyaltyPointsByTable[event.table] ?? 0; final DiscountResult result; if (points < 100) { result = NotEligibleForDiscount(points, 100); } else {

final total = tab.totalInCents; final discounted = total - total * 10 ~/ 100; result = DiscountApplied( Tab(tab.table, tab.items, discounted), ); } switch (result) { case DiscountApplied(:final tab): final dollars = (tab.totalInCents / 100).toStringAsFixed(2); return “Table ${tab.table}: $$dollars”; case NotEligibleForDiscount(:final points, :final pointsNeeded): return “Not eligible: $points of $pointsNeeded points”;

} } } Here the compiler helps: since the Result is a sealed family (chapter 8), dart analyze catches the stale copy. The output below is real, not edited: error - 19-card1-broken.dart:75:5 - The type ‘DiscountResult’ isn't exhaustively matched by the switch cases since it doesn't match the pattern ‘ItemOutOfStock()'. Try adding a default case or cases that match ‘ItemOutOfStock()'. - non_exhaustive_switch_statement You got lucky this time. The Kotlin version of the same wreck compiles without complaint, because whoever copied the rule used an else to silence the compiler: Kotlin return when { points >= 100 && outOfStock == null -> { val total = tab.totalInCents val discounted = total - total * 10 / 100

“Table ${tab.table}: $${discounted / 100}.” + “%02d”.format(discounted % 100) } else -> “Not eligible: $points of 100 points” } Run this code with an item out of stock and 120 points: the screen says “Not eligible: 120 of 100 points.” The customer is eligible; the item is what’s missing. The else turned a compile error into a lying message. Damage: six months later, the discount rule exists in two places. Chapter 14’s use case evolves (birthday customers, happy hour, a discount cap) and the orchestrator’s copy doesn’t; which of the two versions Rosie’s register runs depends on which screen fired the event. The use case’s test passes, the defect stays in production, and the fix demands an archaeological diff to find out when the versions drifted apart. Fix: the orchestrator goes back to only connecting. It fetches the data, hands it to the applyLoyaltyDiscount use case (chapter 14), and translates the Result into state, with the exhaustive switch chapter 13 demanded. The temptation has a source, and it isn’t the orchestrator’s lineage. The unidirectional architectures that inspired it never told anyone to concentrate rules there. The official Elm guide (guide.elm-lang.org) describes update as something that reacts to messages, the Redux documentation (redux.js.org, “Prior Art” section) inherits that design, and André

Staltz (staltz.com) describes the whole family as a data flow, not a decision warehouse. The reducer is the seat of the state TRANSITION; the business rule lives in the use case. Dart class TabOrchestrator { TabOrchestrator(this.tabs, this.loyaltyPointsByTable); final Map<int, Tab> tabs; final Map<int, int> loyaltyPointsByTable; String on(PayTab event) { final tab = tabs[event.table]!; final points = loyaltyPointsByTable[event.table] ?? 0; return switch (applyLoyaltyDiscount(tab, points)) { DiscountApplied(:final tab) => “Table ${tab.table}: $${_dollars(tab.totalInCents)}",

NotEligibleForDiscount(:final points, :final pointsNeeded) => “Not eligible: $points of $pointsNeeded points”, ItemOutOfStock(:final name) => “No go: $name is out of stock”, }; } } Kotlin spells the exhaustiveness check differently. A when used as an expression forces you to cover every variant of the sealed type, and the else is exactly what can’t show up: with it, the compiler stops demanding the new variant. Notice also the val r = inside the when , which names the value so the is branches can read it: Kotlin class TabOrchestrator( private val tabs: Map<Int, Tab>, private val loyaltyPointsByTable: Map<Int, Int>, ) { fun on(event: PayTab): String {

val tab = tabs.getValue(event.table) val points = loyaltyPointsByTable[event.table] ?: 0 return when (val r = applyLoyaltyDiscount(tab, points)) { is DiscountApplied -> { val c = r.tab.totalInCents “Table ${r.tab.table}: $${c / 100}.${"%02d”.format(c % 100)} " } is NotEligibleForDiscount -> “Not eligible: ${r.points} of ${r.pointsNeeded} points” is ItemOutOfStock -> “No go: ${r.name} is out of stock” } } }

Legitimate exception: a trivial presentation if can stay in the orchestrator. Deciding whether the state becomes Loading or Ready , picking the generic error message, formatting cents as dollars: that’s translating a Result into state, the orchestrator’s actual job. The test is to ask “if Rosie changes the business rule, does this if change?” If the answer is no, it can stay. Try it: open https://focus.kodel.com.br/en/dart/19-01 (or swap dart for kotlin , ts , java , csharp , go , php , python , swift , rust ) and run the fixed slice: an orchestrator that only connects and a use case that receives data. Delete one case from the translation switch and watch your language’s compiler demand the missing variant. Card 2: use case that hits the database Symptom: the use case’s signature picked up a dependency. In the diff, applyLoyaltyDiscount(tab, points) turned into applyLoyaltyDiscount(repository, tab) , almost always with the justification “just this once, it’s a small lookup.” Dart · Kotlin int applyLoyaltyDiscount( LoyaltyPointsRepository repository, Tab tab, ) {

final points = repository.pointsForCustomer(tab.table); if (points < 100) { return tab.totalInCents; } final total = tab.totalInCents; return total - total * 10 ~/ 100; } The signature lies. It claims to calculate a discount, but it also decides where the points come from. And the lie charges you at test time: what used to be applyLoyaltyDiscount(tab, 120) with literal values now demands building a repository double just to exercise a ten-line rule. Damage: six months later, every new test of the rule pays the double’s toll, and the use case stops composing. Chapter 16 chained use cases together because they all shared the same shape (data goes in, a Result comes out); a use case that fetches on its own breaks the chain, because nobody can call it without infrastructure wrapped around it. The coffee shop’s most important rule becomes the most expensive one to test.

Fix: use cases receive DATA, not dependencies. The one who knows the repository is the orchestrator (chapter 13); it fetches the points and hands them over ready-made. It’s the line chapters 7 and 9 drew: a pure function at the center, injection only at the edge, in the composition root Mark Seemann has described since 2011 (blog.ploeh.dk), on the foundation Martin Fowler laid in “Inversion of Control Containers and the Dependency Injection pattern” (2004). Dart · Kotlin int applyLoyaltyDiscount(Tab tab, int loyaltyPoints) { if (loyaltyPoints < 100) { return tab.totalInCents; } final total = tab.totalInCents; return total - total * 10 ~/ 100; } Legitimate exception: when the rule demands multiple chained lookups (fetch, decide, fetch again based on the decision), pushing everything into the orchestrator turns it into a

procedural script. In that case a use case that orchestrates other pure use cases, a thin, documented command that receives the repository and delegates every decision to pure functions, is a solution, not a wreck. The sign of health: the RULES still live in functions that test with literals; only the choreography touches the repository. Try it: the route https://focus.kodel.com.br/en/dart/19-01 (and the other languages, same pattern) carries exactly this slice: card 1’s fix and card 2’s fix are the same code, because both wrecks are deviations from the same design. Card 3: generic repository Symptom: a single type serves the entire coffee shop. In the diff, CoffeeShopRepository (or Repository<T, TId> ) handles tabs, inventory, and payment, and its methods speak the database’s language: a string filter, a string sort key, a string table name. The TypeScript ecosystem knows this plague well, because ORMs hand you the generic one ready-made, right out of the box. TypeScript class CoffeeShopRepository { constructor( private readonly tableName: string, private readonly rows: ReadonlyMap<string, T>,

) {} query(filter: string): T | undefined { return this.rows.get(${this.tableName}:${filter}); } list(sortBy: string): readonly T[] { return [...this.rows.values()].sort((a, b) => String((a as Record<string, unknown>)[sortBy]).localeCompare( String((b as Record<string, unknown>)[sortBy]), ), ); } } Look at the callers: tabs.query(“table=4”) , inventory.query(“name=Espresso”) . Every call carries a query fragment. The repository existed to be the database’s boundary (chapter 15); the generic version tore the

boundary open and let the database leak into every file that consumes it. Damage: six months later, changing the database schema means hunting strings across the whole application, because every feature writes its own filters by hand. And the verbs the business actually needs don’t exist: markAsPaid turns into a scattered update(“status=paid”) , with no single place for the write rule. Ben Morris called the generic repository a lazy anti-pattern in “Why the generic repository is just a lazy anti-pattern” (ben- morris.com), and the original definition of a repository, in Martin Fowler’s Patterns of Enterprise Application Architecture (2002), already called for an interface speaking the domain’s language. Fix: one repository per feature, with that feature’s verbs and nothing else, the way chapter 15 built it. The infrastructure exception gets translated exactly once, right here, and only Results circulate outward. TypeScript class TabRepository { constructor(private readonly http: HttpClient) {} findTab(table: number): LookupResult { try { return { kind: “tabFound”, tab: this.http.query(table) }; } catch {

return { kind: “infraFailure”, failure: “noConnection” }; } } } Legitimate exception: an INTERNAL generic is fine. If TabRepository and InventoryRepository share a private QueryRunner , hidden behind the business verbs, nobody outside sees the generic and the boundary stays closed. The symptom was never the itself; it’s in the PUBLIC signature, which forces the caller to speak the database’s language. Try it: open https://focus.kodel.com.br/en/ts/19-02 (or swap ts for your language) and run the per-feature repository. Take down the network ( new HttpClient(true) ) and watch the outage turn into a typed Failure.noConnection instead of a loose exception. Card 4: layer by ceremony Symptom: files that only pass things along. In the diff, an ITabRepository interface with exactly one implementation, a TabDto identical to the model, and a mapper that copies field by field. No new behavior; just toll booths between the call and the data. Kotlin · Dart

data class TabDto(val table: Int, val totalInCents: Int) data class Tab(val table: Int, val totalInCents: Int) interface ITabRepository { fun findTab(table: Int): Tab } fun dtoToDomain(dto: TabDto) = Tab(dto.table, dto.totalInCents) class TabRepositoryImpl( private val dtos: Map<Int, TabDto>, ) : ITabRepository { override fun findTab(table: Int) = dtoToDomain(dtos.getValue(table)) }

Damage: six months later, adding a field to the tab crosses four files (model, DTO, mapper, interface) to reach the same place, and any one of them can drift silently. The team starts “forgetting” the field in the DTO, the mapper zeroes the value out, and the defect shows up far from its cause. Alex Bolboacă ran these numbers in “Is Hexagonal Architecture Overengineering?” (mozaicworks.com, 2025): layers pay for themselves when they isolate change, and charge you when they just repeat types. Dan North proposed CUPID (dannorth.net, 2022) with the same target in mind: code that’s a joy to work with carries no ceremony, and ceremony that protects nothing is dead weight. Fix: the concrete class, no ceremony interface and no twin DTO, the way chapters 6 and 9 argued. Seemann is explicit in the dependency injection book (Dependency Injection in .NET, 2011; 2nd edition, 2019): you extract an abstraction when the second REAL implementation shows up, not before, not “just in case.” Kotlin · Dart data class Tab(val table: Int, val totalInCents: Int) class TabRepository(private val tabs: Map<Int, Tab>) { fun findTab(table: Int) = tabs.getValue(table) } Legitimate exception: an interface with two or more real implementations is architecture, not ceremony. And chapter 17’s test double COUNTS as a real implementation: if the tests’ in-

memory repository implements the same contract as the HTTP repository, the interface is earning its own keep. The same holds for a DTO when the boundary genuinely diverges from the domain (the card processor’s JSON isn’t your tab). Ceremony is the layer that exists with no second form in sight. Try it: the route https://focus.kodel.com.br/en/kotlin/19-02 (and the other languages) shows the fix’s concrete repository: card 3’s fix and card 4’s fix land in the same file, because the generic type and the single-implementation interface die together. Card 5: domain try/catch Symptom: a business case treated like an accident. In the diff, a try { } catch (e) { log(e); } wraps a call that returns a legitimate business refusal, and the flow moves on as if nothing happened. Dart · Kotlin String pay(int table, int cents) { try { processor.charge(cents); } catch (e) { print(“log: $e”);

} return “Table $table: payment approved”; } Run it: the card is over its limit, the processor declines it, the log records it, and the screen prints “Table 4: payment approved.” Rosie finds out at closing, when she counts money that never came in. Damage: a silent failure is the most expensive defect to diagnose, because the symptom shows up far from the cause, days later. Chapter 3 introduced GitClear’s reports on AI-generated code; the 2026 edition measured error masking, exactly this wreck, with a 47% rise (gitclear.com). Code generators prefer the silent catch that makes today’s test pass and hides tomorrow’s refusal. This is the only anti-pattern on the list that the tools commit STRAIGHT OUT OF THE BOX: if you accept AI suggestions without reading the catch, you already have this wreck in your repository. Fix: a business refusal is a value, not an exception (chapter 8). The payment Result gains a CardDeclined variant with a typed reason, and the only try/catch left standing lives at the repository’s boundary (chapter 15), which translates the processor library’s exception exactly once. It’s the design Scott Wlaschin called Railway-Oriented Programming (fsharpforfunandprofit.com, 2013): the error rail runs alongside the success rail all the way to the screen, with no invisible detours. Dart · Kotlin

PaymentResult charge(int cents) { try { processor.charge(cents); return PaymentApproved(cents); } on StateError catch (e) { return CardDeclined(e.message); } catch (_) { return InfraFailure(); } } And the caller has no way to lie: the exhaustive switch over the sealed Result forces the screen to show the refusal. String pay(int table, int cents) => switch (repository.charge(cents)) {

PaymentApproved() => “Table $table: payment approved”, CardDeclined(:final reason) => “Table $table: payment declined ($reason)", InfraFailure() => “Table $table: no connection, try again”, }; Legitimate exception: a try/catch AT the infrastructure boundary is exactly where it belongs, and it’s one per slice, not one per call. The repository above uses a try/catch and isn’t an anti-pattern: it translates, once, the outsider’s exception into the insider’s value. The card’s symptom is a catch in DOMAIN code, the kind that swallows a business decision. Try it: open https://focus.kodel.com.br/en/dart/19-03 (or your language) and run both charges: the declined one shows up declined. Then swap on StateError for a generic catch that returns approved, and watch the lie come back. Card 6: premature shared/ Symptom: this is the only one you recognize by the file’s PATH, before reading a single line: shared/helpers/ receiving code on the second occurrence of a similar-looking calculation. Two features had similar functions; someone unified them “to avoid duplication.” Dart · Kotlin

// shared/helpers/discount_calculator.dart int calculateDiscount( int totalInCents, int points, { required bool isBirthday, }) { if (isBirthday) { return totalInCents - totalInCents * 15 ~/ 100; } if (points < 100) { return totalInCents; } return totalInCents - totalInCents * 10 ~/ 100;

} Notice the isBirthday boolean. It’s the scar left by the unification: the two features did NOT share the same rule, they had similar- looking rules, and the helper needed a parameter to break the tie. Every new divergence adds another parameter and another if. Damage: here’s my position, stated in the first person: I consider the premature shared/ the most expensive anti-pattern on this list, because it’s the hardest to reverse. The other five undo themselves by editing one slice; this one demands touching EVERY consumer of the helper, working out which half of the if each one uses, and separating back out what never should have been glued together. The cost of reversing it grows with the number of consumers, and consumers only ever increase. Sandi Metz named the cause: “duplication is far cheaper than the wrong abstraction” (sandimetz.com, 2016). Kent C. Dodds turned the advice into an acronym, AHA (Avoid Hasty Abstractions, kentcdodds.com/blog/aha-programming), and the rule of three, which chapter 11 brought over from Fowler’s Refactoring (1999), gives the number: extract on the third occurrence, never the second. Fix: each feature keeps its own function, and the duplication gets accepted as the cost of independence between slices, the design Jimmy Bogard argues for in Vertical Slice Architecture (jimmybogard.com, 2018), the one chapter 11 adopted. Dart · Kotlin // features/tab/loyalty_discount.dart int loyaltyDiscount(int totalInCents, int points) {

if (points < 100) { return totalInCents; } return totalInCents - totalInCents * 10 ~/ 100; } // features/loyalty/birthday_bonus.dart int birthdayBonus(int totalInCents) => totalInCents - totalInCents * 15 ~/ 100; Two functions, two slices, zero tie-breaking parameters. If tomorrow the birthday bonus becomes 20%, the tab feature doesn’t even find out. Legitimate exception: extracting to shared/ is legitimate once the reuse has PROVEN itself: a third occurrence, the same rule (not similar-looking rules), and the same reason to change in all three. Formatting cents as dollars is the classic example: every spot formats the same way and changes together. The test isn’t “does the code look alike?”; it’s “when one changes, DOES the other have to change too?”

Try it: open https://focus.kodel.com.br/en/dart/19-04 (or your language) and run the two independent features. Then try reintroducing the single helper and count how many tie- breaking parameters you need to keep the same output. What Go won’t let you do Two of the six cards aren’t expressible in Go, and that earns a page of counterpoint instead of forced examples. There’s no inheritance or type hierarchy to hide a CoffeeShopRepository[T] behind subclasses (card 3 is born crippled), and there’s no exception for a generic catch to swallow (card 5 simply doesn’t compile in spirit: there’s no throw). The error in Go is a value returned, the way Rob Pike summed up in “Errors are values” (the official Go blog, 2015), and a returned value shows up in the signature: Go func Charge(cents int) (int, error) { if cents > 3600 { return 0, errDeclined } return cents, nil

} The caller pays the price in verbosity, and it’s a price, not a detail: the (T, error) pair charges an if err != nil on every call, line after line, where Dart’s sealed Result charges one switch per translation. value, err := Charge(total) if err != nil { return fmt.Sprintf(“Table %d: payment declined (%v)", table, err) } In exchange, ignoring the refusal becomes a decision visible in the diff (an _ where err should be), not a forgotten catch three layers up. The counterpoint’s lesson holds for the other nine languages: the less your language prevents by construction, the more this chapter’s cards are the discipline holding the roof up. Pitfalls A reader who finishes this chapter with a trained eye runs a new risk: rushing off to fix all six wrecks at once, in a single heroic- refactor pull request. That big bang is the seventh wreck. A diff that touches the orchestrator, the use case, the repository, and shared/ all at the same time is impossible to review, impossible to revert, and nearly guaranteed to break behavior no test was

covering. Fixing an anti-pattern follows the same rule as every change in FOCUS: one card per pull request, one slice at a time, with the slice’s test green before and after. Chapter 20 shows the grown-up version of this discipline, strangling legacy code; save the impulse for there. Q&A I fixed card 1 and the orchestrator ended up three lines long. Isn’t that layer by ceremony? No: ceremony is a layer that only passes things along WITHOUT protecting anything. The three-line orchestrator protects the View from knowing the use case and the use case from knowing the screen, and it’s where the state is born. It’s small because it’s right. And the duplication between features, nobody pays for that? Somebody does, and the book accepts the price with eyes open: duplicating a ten-line calculation costs less than coupling two slices through a helper with a tie-breaking boolean. The accepted cost has a clear limit (chapter 11): on the third occurrence of the SAME rule, changing for the same reason, extract it. Before that, duplication is cheap rent; coupling is a mortgage. My language has no sealed types or exhaustive switch. Do cards 1 and 5 still apply to me? They apply harder: with no compiler demanding the missing variant, you’re left with chapter 18’s discipline (a single translation, Result by convention, a test for the translation). The Go counterpoint above is the same reasoning. Quick tip

Card 2’s wreck gets hunted with a grep. In a flat slice the only file allowed to know about infrastructure is the repository, so grep -rn “import” features/ | grep -E “http|sql|dio|axios” | grep -v _repository has to come back empty. Hang that grep on the project’s lint step and card 2 never gets past a pull request again. Quick reference Symptom in the diff Anti-pattern Ch. Business calculation in the handler Rule in the orchestrator 13 and 14 Repository in the use case’s signature Use case that hits the database 7 and 9 Public Repository , string filter Generic repository 15 Single- implementation interface, twin DTO Layer by ceremony 6 and 9 Generic catch that logs and moves on Domain try/catch 3 and 8 Helper in shared/ on the 2nd occurrence Premature shared/ 11

Anti-pattern Fix Rule in the orchestrator the rule moves back to the use case Use case that hits the database use case receives data; the orchestrator fetches Generic repository one repository per feature, business verbs Layer by ceremony concrete class until the 2nd real implementation Domain try/catch refusal becomes a Result variant; translate at the edge Premature shared/ each feature keeps its own function; rule of three Exercises

  1. The diff below landed in a coffee shop pull request. It contains TWO anti-patterns from this chapter, one inside the lines and one outside them. Name both, citing each card’s symptom. The answer key follows right after; try it before you read it. --- /dev/null +++ b/shared/helpers/payment_helper.dart @@ -0,0 +1,13 @@

+String paySecurely(int table, int cents) {

  • try {
  • final result = repository.charge(cents);
  • if (result is CardDeclined) {
  •  log("declined: ${result.reason}");
    
  • }
  • } catch (e) {
  • log(e);
  • }
  • return “Table $table: payment approved”; +}
  1. Open question, no answer key: open your OWN current project’s repository and walk through the quick reference table row by row. How many of the six wrecks exist in it

today? Which one has the most consumers, and therefore costs more with every week that passes? Answer key for exercise 1: inside the lines, a domain try/catch (card 5): the payment’s refusal is logged and swallowed, and the function returns “approved” unconditionally, the same design as the catch that logs and moves on. Outside the lines, in the file’s path, a premature shared/ (card 6): shared/helpers/payment_helper.dart is a payment helper being born outside the payment slice. Two wrecks, and you identified the second one without reading a single line of code. Tip 19 The cost of reversing a wreck grows with the number of consumers. That’s why the premature shared/ is the most expensive one on the list, and why the time to name the anti-pattern is in the diff, while the consumer count is still one. Next chapter: and what about when the code was already born with all six wrecks at once, in a ten-year-old legacy system holding up the company’s register?

Powered by TurnKey Linux.