> In the OOP world I would have expected to see a Loan class with methods like create, get_offers, and make_payment.<p>Rookie mistake! I'm usually very critical of OO, but this ain't it. Good OO is about <i>delegation of behaviour</i>. The <i>Loan</i> is not the <i>payer</i>, so no make_payment() method please. Same with create and get_offers (unless we're just talking about constructors/getters here, but I don't think that was the intent). If a Loan did have a make_payment, and get_offers: why stop there? It's also going to need calculate_interest(), persist_in_db(), renegotiate(), compare(), calculate_amortisation(), get_currency(), get_minimum_repayment(), forgive(), calculate_late_fee(), alert_late_customer(), and a hundred others to be figured out along the way. So it's good OO for a Loan to just be struct-ish (There's even a dumb word for it - "POJO"). Or, even better, just a UUID!<p>You can apply some SOLID cargo-culting to the above. If you put so much behaviour into Loan, you'll be forever changing it, updating it, and risking unrelated breakages, violating the Single-responsibility principle ("one reason for a class to change"). I'm not a huge fan of that particular principle (it's vague and doesn't tell you how to fix it!) but I am a fan of Dependency Inversion (which does!).<p>A Loan is <i>abstract</i>, we both know what we're describing - even though we have completely different tech stacks. If a Loan has get_offers(), those offers will need to come from somewhere, whether you're talking about other classes (OfferService? DiscountService?) or over http or via a database or something. And those are <i>concrete</i> (Java! JSON! Rest Calls! DB Tables!) DI says to flip the design around so that the concrete things instead know about the abstract Loan, and the Loan becomes sufficiently dumb. Then when it comes time to change interest calculations, you change only the InterestService, likewise with CurrencyConversionService, OfferService, etc. You're back to single responsibilites!<p>> It turns out the command structure checks all of these boxes. It's dead simple to understand. There's no layer of indirection trying to piece together OOP hierarchies. It encapsulates the business logic developers actually want to run in a single place, you don't have to figure out how to wire together a bunch of objects on your own. And testing is dead simple, it's just a function with easy to understand inputs and outputs.<p>If you skip the indirection and just test all your logic in a single place, you're bound to be tripped up by your dependencies. You can probably test 80% of `calculate_interest.rb` with just inputs, functions and outputs, but `make_loan_payment.rb`? There's going to be dependencies that you shouldn't invoke in a test scenario. The indirection & wiring is how you organise the code so that you can put test-doubles in the right places, and control exactly what you want to test for.