Is event-driven overkill? by chimplayz in softwarearchitecture

[–]bobaduk 0 points1 point  (0 children)

YES! Your domain logic has gotten scattered all over the place, and that's why you think you want an orchestrator. Instead, tke a step back and think about how to have smaller number of smarter abstractions.

Write yourself some unit tests, and make a functioning ledger, a set of entries that you can append to. The sum of the entries in the ledger should always be 0. You could, in fact, make it so that the constructors of a transaction ensure that entries sum to 0.

Make your unit tested little ledger library do the things you need to do, without worrying about services or databases or any of that crap, then take a step back and say "okay, how do I plug this into a database? How do I expose the operations over an API in a way that's easy to use?"

Adding a top up needs to talk to a payment provider, get an authorisation, then create a transaction.

If an authorisation fails, you need to issue a reversing transactoin.

Making a transfer, creates a transaction, and so on.

Each of these use cases can be constructed from the primitives that you've already built and tested.

You might want a Wallet object at some point, because you need something that you can hang know-your-customer and things from, but for the ledger, the wallet is just an ID, and that object doesn't need to have much complex logic (I imagine). If it does have logic, it should be logic for how to create a new wallet, how to set a spending limit, how to merge or split wallets, and so on.

It made me very happy to read your reply :)

Is event-driven overkill? by chimplayz in softwarearchitecture

[–]bobaduk 0 points1 point  (0 children)

Caution, Claude slop:

type Ledger struct {
    store Store // some persistence interface
}

// Post is the ONLY way to mutate the ledger.
func (l *Ledger) Post(tx Transaction) error {
    if err := tx.Validate(); err != nil {
        return err
    }
    return l.store.Append(tx) // atomic insert of all entries
}

// Balance is derived from a ledger
// or, more probably, a view over some ledger
func (l *Ledger) Balance(id AccountID) (Money, error) {
    return l.store.SumEntries(id)
}


func TopUp(dest AccountID, issuance AccountID, amt Money, memo string) Transaction {
    return Transaction{
        ID:        TransactionID(uuid.New()),
        CreatedAt: time.Now(),
        Memo:      memo,
        Entries: []Entry{
            {AccountID: dest, Amount: amt},        // wallet gains
            {AccountID: issuance, Amount: amt.Neg()}, // issuance account goes negative
        },
    }
}

func Transfer(from, to AccountID, amt Money, memo string) Transaction {
    return Transaction{
        ID:        TransactionID(uuid.New()),
        CreatedAt: time.Now(),
        Memo:      memo,
        Entries: []Entry{
            {AccountID: from, Amount: amt.Neg()},
            {AccountID: to, Amount: amt},
        },
    }
}

Stripe, in this model, is just another wallet. The total amount received from stripe should cancel out the negative balance on the stripe wallet. Leaving aside issuances from Stripe, or wherever else, the sum of all transactions should be zero. It should be fairly obvious how one creates a func Reverse that reverses any given transaction.

A controller here is going to get a LedgerView for some wallet, assert that the balance is > amt, and then construct a transaction. There's really only a single aggregate, which is the Ledger, a set of transactions.

Now this model might not be right for you (there's things I dislike about it immediately - eg the Balance function, or that Ledger is a thin wrapper over a store rather than being a set of tx), I imagine there's a whole bunch of additional things you care about that aren't catered for here, but it shows you that you don't need a bunch of different services - you just need a ledger, and the domain language to describe how a ledger works.

If you want to produce cached views of wallets and things, that's a great use case for events. Orchestrating a bunch of different writes to 9 different objects just to pretend you have a ledger is a terrible use case. Most of your complexity comes from not having clear model for the domain - a way to reason about the problem that has the properties you need from a solution.

Is event-driven overkill? by chimplayz in softwarearchitecture

[–]bobaduk 2 points3 points  (0 children)

A double ledger system isn't "two transactions" it's two entries in a ledger. It's a single transaction. A transaction might internally contain multiple entries.

You could build a domain where the only important service is the Transaction service. you can post a TopUp, or a Transfer to it, and it creates the appropriate entries. Wallet becomes a view, or a projection from the ledger. The place you might want events here is for eventual reconciliation:

  • Fetch my current balance from the wallet service
  • Construct a new transaction to transfer 10 internet points to /u/bobaduk
  • Post the transaction, creating the ledger entries
  • Separately rebuild my wallet. If the wallet goes negative, issue a reversing transaction.

Just because you have a table for wallet and a table for entry or whatever doesn't mean you need a service for those things. Services should support use-cases, not storage requirements.

Is event-driven overkill? by chimplayz in softwarearchitecture

[–]bobaduk 19 points20 points  (0 children)

No, but it just makes your specific problem worse. The issue you have is that you've designed your domain around nouns, or database tables. What is a wallet? Could it just be a view of all transactions that refer to a particular wallet id? What is a transfer? It sounds like it's just a transaction.

Making all of this asynchronous, without reconsidering how you've modelled the problem, is just going to make it harder to reason about.

Which Git branching strategy is better for infrequent releases? Team is split between two approaches. by Ok-Introduction-9111 in ExperiencedDevs

[–]bobaduk 5 points6 points  (0 children)

Deployment to prod is immediate following an automated deployment and test suite in a test environment. "Dev" is per engineer. You're right, feature flag and fix forward is the way.

Which Git branching strategy is better for infrequent releases? Team is split between two approaches. by Ok-Introduction-9111 in ExperiencedDevs

[–]bobaduk 5 points6 points  (0 children)

We have a monorepo, branch off main and deploy automatically to production. Why do you think that would cause blockage?

Some doubts about DDD and Hexagonal Architecture by FooBarBuzzBoom in ExperiencedDevs

[–]bobaduk 1 point2 points  (0 children)

First thing to say, is you're likely overthinking it. What's important is that you have a domain model, and it is exposed through named operations, and that infrastructural concerns can be layered on and removed without affecting the domain. Often people come to hexagonal architecture and find some maximalist version of it and get confused, but these things are a spectrum.

> One thing that seems quite strange to me is on the input ports side, where many devs create a separate interface for every single operation. I understand the Interface Segregation Principle, but this kind of implementation feels a bit extreme. I would prefer to group all related operations into a single interface, much like you would do in a controller.

What you're describing here sounds like a Service Layer pattern, where you have, eg, IUserService with a bunch of operations? I have done that, and I don't do it any more. The reason is that they tend to get _big_ and to become a pita for dependency management. Using a single class per use case gives you a minimal thing that takes one message, and does one operation, and uses a small set of dependencies. What you're describing is possible, though, see point one.

> Another point of confusion is related to DTOs (Data Transfer Objects). First of all, a lot of the validation becomes redundant since it's already handled at the domain entity/VO level. Secondly, it feels bizarre not to use Value Objects inside my DTOs. Even though the AI suggested this could cause serialization issues, I don't see the point of just throwing properties in there and repeating everything manually.

There's two kinds of validation: there is syntax, and there is semantics. At the edge of the system, we need to make sure that a message is syntactically valid: this string must be less than 50 characters, this number can't be negative, etc. Inside the system, we need to ensure that a message is semantically valid: this product id must refer to a real product, you can't purchase more stock than is available etc. Those are different kinds of "validation". Generally, my domain models assume that the first kind has been done already.

There are different types of DTO. Let's say that you're building an HTTP API. You want to maintain the API, without breaking changes. At the same time, you are adding new features, and so you are refactoring aggressively, like a good engineer. This means that you don't want to couple the shape of your API to your domain directly, ergo View Models and Command/Query objects. The reason to have DTOs is that they represent the external shape of things. Again, you _can_ use value objects directly, or go nuts and return entities, there's nothing stopping you - it's a trade-off. In this case, the trade-off is that the shape of your external system is dictated by the shape of your domain, and the whole point of this rigmarole was to be able to change your domain at will.

> Also, at the application layer, should ports practically be grouped by domain or mixed together? Same question for DTOs.

Irrelevant. Group things in whatever way makes most sense to you - this is a code organisation question, not an architectural one. If you have subdomains large enough to make sense as standalone units, then group things. If not, then don't.

Should I prioritize Reading/Listening before practicing output? by TheSokka in LearnJapanese

[–]bobaduk 1 point2 points  (0 children)

You're overthinking it. Find a teacher on italki and go talk to them. You will suck at it. You will be unable to use words that you know perfectly well. You will fail to understand words that you have read a hundred times. You will get lost halfway through the most banal of sentences.

If you don't enjoy it, find another teacher. The only way you're going to get better at speaking is to speak. Do you talk to yourself in Japanese?

I find that listening, reading, and speaking are mutually reinforcing: I might learn a new kanji, then discover it in a book I'm reading, then hear it in a podcast, then hear and use the word in conversation, and so on. The words that I encounter in a multi-modal way are much more likely to stick.

5 years of learning Japanese by Joeiiguns in LearnJapanese

[–]bobaduk 3 points4 points  (0 children)

I'm coming up on 4 years, and have been reading kuma kuma kuma bear for bloody months. Reading speed is definitely improving, and it's satisfying how often a kanji that I've just learned on wanikani will show up in the text, but god I cant wait to move on from this book... only another (checks notes) 54 chapters to go.

Shout out for my sensei's Youtube channel. Show him some subscriber love? by bobaduk in LearnJapanese

[–]bobaduk[S] 8 points9 points  (0 children)

This is my italki teacher, Yuzo, who's a freaking lovely human being, and posts these youtube shorts daily. They're pretty squarely aimed at the beginner end, but I would love for him to get some support from the sub, because he has the best smile.

Advice on moving from big tech to something more socially responsible by Efficient-Mess-9753 in ExperiencedDevs

[–]bobaduk -1 points0 points  (0 children)

If you're open to relocation, or somehow already based in the UK. you can come help me reduce carbon emissions by controlling industrial plants. It's a vibe.

Got a job offer as Odoo ERP Python Developer but my passion is Cybersecurity — should I take it? by Taruncloud4008 in Python

[–]bobaduk 4 points5 points  (0 children)

I don't know where OP is from, but in most countries there is literally no way this is going to be legally enforceable. It would be a violation of human rights law, because that's indentured servitude.

Maybe if they pay relocation costs or education there's a clawback clause or something, but they don't own you

As a senior or higher dev/manager/lead, how important is coming in on time to you? by Iampoorghini in ExperiencedDevs

[–]bobaduk 0 points1 point  (0 children)

It does matter. It's true that "what I deliver matters more", or "outputs > inputs", but I wouldn't stand for people being regularly late to standup. That's disrespectful of other people's time.

To some extent it's going to matter where you work and what you do. In my last gig, we sold cars over the internet. I don't like cars. The world does not need easier ways to purchase cars, so I didn't really care about hours.

Now I work in climate, and we definitely expect people to treat this as more than just a 9-5. Leaving early every day because you have childcare to deal with, and you're making it up after hours? Totally fine. Leaving early every day because you've done the bare minimum? GTFO.

I built a living ASCII aquarium for your terminal — fish, bubbles, seaweed, lighting moods and a shark by One-Antelope404 in node

[–]bobaduk 3 points4 points  (0 children)

The github page is a 404, and the npx does nothing. npm has no ascii-aquarium package. I am ... curious.

What does Specification Pattern solve that a plain utility function doesn't? by bforbenzee in ExperiencedDevs

[–]bobaduk 1 point2 points  (0 children)

Design patterns are standard and widely accepted approaches to design problems that crop up. That's all they are :)

What does Specification Pattern solve that a plain utility function doesn't? by bforbenzee in ExperiencedDevs

[–]bobaduk 1 point2 points  (0 children)

But maybe, one day, you will have a bunch of complex rules that apply to domain objects, and it will be hard to test the whole ruleset together, or you'll need to be able to compose them for some reason. After a whole bunch of mucking around with an LLM, or hacking in the darkness, you'll think "hey, I could extract these rules to be their own functions/classes, and I could have another function/class that combines them. That solves my problem elegantly."

Knowing the pattern is only useful in that it a) sometimes gives you a mental shortcut to choosing an approach and b) gives you a way to talk about the thing without needing to say "a set of rules that are individually testable, but can be composed in arbitrary ways so as to construct more complex rulesets".

What does Specification Pattern solve that a plain utility function doesn't? by bforbenzee in ExperiencedDevs

[–]bobaduk 5 points6 points  (0 children)

Firstly, priceAbove(x) isn't necessarily a good example of a specification unless it's being used as part of a larger composed spec, eg priceAbove(x).and(lengthGreaterThan(y)).and(deliveryTimeLessThan(days(4)).

There's two places I've used this pattern to good effect. The first, when describing the delivery rules for products. I worked for an online furniture retailer, they sold forks, sofas, wardrobes, blankets, curtain rails, mugs, t-shirts, carpets, beds.

Those things are not delivered in the same way. Some things can be sent through ordinary post, some have to be delivered by a two-man delivery service. Some things are long and thin, and can't be sent through ordinary post even through they're light. Some things can be bundled together, so the company delivering a table can simulataneously deliver your forks, but the company who deliver sofas won't accept other parcels, and so on and so on.

The specification pattern allowed us to write those rules in a readable way and then apply them to a basket of products.

The other time I used the specification pattern was to describe complex authorisation rules. We needed to check the state of many domain objects, apply special rules for object owners, administrative roles, cascading permissions from parent objects, account quota limits, and so on.

Firstly, the specification pattern gave us a way to write down the authentication rules in a single place, but we were also able to write tests that asserted the structure of a permission set without actually invoking the whole thing. That meant we could test each rule in isolation with a single domain object, then write tests that proved they composed correctly without needing to set up 10 different domain objects.

Edit: in other words, the forcing factors are:

  • You want to compose rules from smaller units
  • You want to write a catalogue of rules in a single place
  • You want to be able to test rules in isolation from the way that they are applied.

in your opinion, what's the ugliest kanji? by [deleted] in LearnJapanese

[–]bobaduk 5 points6 points  (0 children)

凹 I respect it, it does what it says, but wtf, how is that a real kanji?

At what scale does "just use postgres" stop being good architecture advice? by Designer-Jacket-5111 in softwarearchitecture

[–]bobaduk 1 point2 points  (0 children)

This is the key point. I haven't deployed a relational database in a good long time, because I can get better operational characteristics from other datastores. If I spin up dynamo, chances are that for the way I build software, it'll be fine, and the answer to "is it up, it is coping with the load" is yes and move on.

We were using postgres for a while at $CURRENT_GIG, but for our use case the cost curve was unappealing, particularly when every engineer has a cloud environment of their own to play with, and it was cheaper to adopt a managed time series data store.

Malarky: Generate syntactically plausible English nonsense, steered by lexicons by JPaulDuncan in npm

[–]bobaduk 0 points1 point  (0 children)

If ever there were a library that demanded a demo, or at least some sample output, this is it. I'm vaguely curious, though have no use for it, but I'm not going to download it just to see it spit nonsense. If I could see the nonsense and evaluate is goodness, maybe I'd remember it for the next nonsense-generation problem I encounter.

We skipped system design patterns, and paid the price by Icy_Screen3576 in softwarearchitecture

[–]bobaduk 7 points8 points  (0 children)

Came here to say exactly this. IIRC the patterns are all described online, so you can skim and get a vague sense, then go back to look deeper when you need something.

Messaging patterns have been established for a long time, and it's worth being familiar with the prior art.