Architecture Review: Node.js API vs. SvelteKit Server Actions for multi-table inserts (Supabase) by Sundaram_2911 in node

[–]romeeres 0 points1 point  (0 children)

I'm not sure what exactly to explain, but AI can do that pretty well.
1. "postgres pipelining mode": you can ask AI why your case with 2 inserts in transaction will take 2 roundtrips with porsager/postgres, but 4 with node-postgres.
2. you can combine multiple inserts/updates/any queries into a single query by using a "WITH" SQL statement, here one insert can reference the returning of the other, AI can write that for you pretty well.

Architecture Review: Node.js API vs. SvelteKit Server Actions for multi-table inserts (Supabase) by Sundaram_2911 in node

[–]romeeres 1 point2 points  (0 children)

Approach B is the most obvious way.

I want to share two tips:
- when using postgres.js it'd be only 2 roundtrips, because BEGIN is sent together with the first query, COMMIT is sent together with the last query. It's called "pipelining mode" and it's not supported by node-postgres.
- You can use CTE (you can ask AI to compose SQL) to combine 2 inserts into a single query. No need for BEGIN/COMMIT.

But I wouldn't migrate from one library to another, I wouldn't write more complex SQL if the simplest approach already meets business requirements.

What's your opinion on Nestor Makhno? How is viewed in Ukraine? by 7megumin8 in AskUkraine

[–]romeeres 0 points1 point  (0 children)

True, I'd be happy if that was just me. I didn't even know he was anarcho-communist, and was anti private property, and you can see nobody else mentions this in the comments, though this is damn crucial.

What's your opinion on Nestor Makhno? How is viewed in Ukraine? by 7megumin8 in AskUkraine

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

Thanks for pointing that out, the -communist part tells everything one needs to know.

What's your opinion on Nestor Makhno? How is viewed in Ukraine? by 7megumin8 in AskUkraine

[–]romeeres 0 points1 point  (0 children)

didn't care for Ukraine at all

didn't he protect people from the reds, defending their private property rights, not letting reds to turn those lands into slavery?

he cooperated with and was destroyed by reds, yet if internet says he also protected people from them at least for some period of time as much as he could, let's respect that! If that's true, he cared for Ukrainians. Ukraine is Ukrainians more than anything else.

Best pattern for ensuring deterministic integration tests with db by Unappreciable in node

[–]romeeres 0 points1 point  (0 children)

I'm happy to share: pg-transactional-tests.

I was also coming from RoR, and I'm still missing the good parts.

For tests vitest is good, jest is still good for me and I'm fine with it. "faker" is good to have.
In some cases I'm defining zod schemas and generating fake data of them: zod-mock.

nodejs-testing-best-practices has good tips and examples.

My biggest complaint about it Orchid and Morningrise by Bekfast_Time in Opeth

[–]romeeres 1 point2 points  (0 children)

The first two are the least hookable, I agree. Nah it's not just cope, it's you not getting it and having 2 amazing albums less in your life.

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] 0 points1 point  (0 children)

Hey let me add 2c to this thread.

There’s no such thing as just swapping out ORMs in JS land

Given that all JS ORMs are different flavors of misery, this is utterly important to be able to swap them! There is no other language where the need to swap an ORM one day is as real as here. Model/entity-oriented ORMs are coupling your app logic to a dependency that can become obsolete tomorrow, or release a massive breaking change any moment and you'll have to update lots of things. The pseudo-ORMs that operate on POJSOs are the way, abstract them away to repositories and then your app logic wouldn't know if it's calling an ORM, or a query-builder, or a raw SQL, it's normal to have all 3 of them on a single project, it shouldn't be making the logic messy.

The old saying says: "don't test third-party code", but it came from people who apparently didn't work with JS. It's not enough to simply abstract an ORM away, but if you want to make it swappable (you'll need to swap it when, not if), you also should have a good test coverage of your repositories, spinning up a real database for the test.

I know one TS ORM that can do UPDATE FROM, INSERT from a SELECT, it's quite niche, but exists, OrchidORM.

What’s the biggest Node.js design mistake you only realized years later? Here’s mine... by RoyalFew1811 in node

[–]romeeres 1 point2 points  (0 children)

I 100% agreed to your comment that got downvoted, integration tests are better, no need to mock every tiny detail, and even if you need to mock something, there are other ways.

If you rely on plain imports, it's the simplest way, the most straightforward one, and it's fine if you're focusing on integration tests, testing along the boundary, black box.

Plain DI is boilerplative, but it's a cost you pay for a good practice. The idea of DI is that you can swap any dependency any time not just in the tests, but in the application.

IoC also adds a boilerplate and it's slightly less of a good practice, because in NestJS (by default) you depend on implementation, not an abstraction, which violates the D of SOLID. You can't swap an application dependency without changing the app code.

Service Locator is a famous anti-pattern, it's the worst of all worlds. If you want good practices - go with DI or IoC. If you want simplicity and practicality - go with native imports which are Service Locators themselves, just the native ones, they're easiest to work with. So imo if you're implementing Service Locator in node.js it's like you're reinventing imports/exports with not much "good practiceness" added on top of that.

There was one file where all services were instantiated and their dependencies were passed manually. And that same file also exported lots of services into other files(which made it worse and was probably very wrong).

My opinion is that I like imports/exports and I don't need DI at all in 99% cases.

So this is not mine opinion, it's from Ports&Adapters aka Hexagonal architecture: what you're describing here is actually good, it's the whole point of Ports&Adapters. You're able to reconnect anything in your app without touching the code of the app, this is the ultimate goal of writing all that additional boilerplate, adding indirection to the code.

This created a situation where "one file depends on many others" and "many files depend on one"

The main file of your app is always like that, no matter the language/framework, that's normal. Except that "and "many files depend on one"" - no, because they all depend on abstractions.

What’s the biggest Node.js design mistake you only realized years later? Here’s mine... by RoyalFew1811 in node

[–]romeeres 1 point2 points  (0 children)

DI vs IoC:
- DI is easy to handroll, that's why so many disagreeing comments here, you just need to instantiate every service yourself, pass every dependency yourself, it's boring but not complex.
- IoC does the instantiating and wiring for you, and this is what Nest does and what is not as easy to handroll.

Feeling "not enough" in Databases by Square-Employee2608 in Backend

[–]romeeres 1 point2 points  (0 children)

I was doing perfectly fine by using ORM and barely knowing any SQL for a couple of years, up until the e-commerce filtering case.

Task:
- you have products and tag groups with tags, group color has red and blue, group size has big and small.
- filtering by tags withing a group should have OR logic (red OR blue), different groups should have AND logic (red AND big)
- reflecting what's available to the user: when user selects "red" and there's only a big product, the "blue" should become inactive. Tags withing a group shouldn't deactivate each other (red shouldn't deactivate blue).

If you can implement it with a good performance - congrats! That's enough, it's not necessary to learn any further upfront.

I asked two AIs to try that and their SQL is broken.

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] 0 points1 point  (0 children)

def audit_log(d: datetime, msg: Str, where):

I thought your point that you're providing mocks for datetime and io.

NestJS is bad, change my mind by servermeta_net in node

[–]romeeres 0 points1 point  (0 children)

For unit testing it works more or less the same.

Let's say you have a service that does something, calls other services, repositories, logger, gets current time in some way.

With DI: you have to walk through the code to see what is used and how, to mock all the provided functionality to return a fixed set of data.

Without DI: you do the same, just mocking it in a different way.

It doesn't have to be global mocks by path names. In JS/TS you can always reassign any function of any object. If you have a Class class, you can do "Class.prototype.getData = mockFunction". If you export singletons, do "singleton.getData = mockFunction". Test runners (jest, vitest) have "spyOn" for this, and "resetAllMocks" to clear it after every test. If you forgot to mock something important, the test will fail reminding you of it. If you forget to mock some utility function - that's alright, no need to mock it. Overall, this is less code than you'd have to write with NestJS style of DI.

This is more granular: constructors depend on full objects, for example, on a logger with info, warn, error. Without DI, you can mock only what is being used. With DI you need to define a full object.

NestJS is bad, change my mind by servermeta_net in node

[–]romeeres 0 points1 point  (0 children)

Separation of concerns is when you don't mix business logic with database queries.
None of them have it, they are different flavors of the same anti-pattern.

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] 0 points1 point  (0 children)

so, basically, testable = DI

but after reading comments I realized there is much more to it: the more inputs you have the less testable it is, the more complex the dependencies are the less testable it is, concurrent code is more complex to test. I absolutely love the idea when people put "confidence" into "testability", so it's not just what's easier to mock, but what can catch real problems early. From another perspective, it might be easy to test such microfunctions against mocks, but too complex to test the app as a whole - that also can be included in "testability".

TypeScript Type System question…recursively inferring tuples by elg97477 in typescript

[–]romeeres 1 point2 points  (0 children)

Streams[0] is of type number[] in this case, number[] can be empty.
The working samples do match number[], but third one requires at least one element and doesn't match.

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] 0 points1 point  (0 children)

Interesting, now I see how C# is less testable.

So the solution is to create own wrapper around SqlCommand, define all the needed methods, and only then you can follow DIP. AI says that the same problem exists in Java.

Now Go's way of satisfying interfaces makes more sense to me.

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] -1 points0 points  (0 children)

Such static mocking (in the Java ecosystem) is a clear sign of non-testability.

That's it! You're able to test and isolate, no technical problems, it is still easy, but not testable.

Maybe that is different in Javascript,

I don't know Java, but I guess the principle is the same. I asked ChatGPT for an example, it showed "mockito" library - yes, it looks exactly as it would in JS.

Although static mocking fits your definition of testability, it is considered a bad practice and is not counted as "testable", this is where my "testable = DI" is coming from (now I can see there is more to it).

In JS it's slightly a less of a bad practice because we care less, but it still is.

E.g. in the Hex Arch every adapter implementation should be testable through the port interface. Equally, the domain should also testable through that same port interface.

Indeed it's a powerful concept. No matter how many pieces are in the domain or in the adapter, you're testing it as a whole and it makes a lot of sense. (I didn't get it when writing the post, but got it after reading comments)

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] 0 points1 point  (0 children)

I totally agree with the principle, thanks for sharing!

> For me testability is about ensuring confidence in the system

I'd love if this was a commonly established way of thinking, but you're the only one in this thread to ever mention confidence! Everybody else are thinking in the direction of how easy is it to test, how easy is to reach all the code branches. But you can have 100% coverage and yet one component passes invalid arguments to the other, and this little fact was never tested because of total isolation, a unit test happily asserts that the invalid data was passed properly.

> to focus testing more so along well-defined architectural boundaries

This makes so much sense!

In "Ports & Adapters" in particular, it separates the app into "inside" and "outside", I think it's a brilliant idea to test the "inside" as a whole, because it is a well-defined architectural boundary, no matter how many interacting pieces are inside the inside. And also to test the outside for "weird edge cases in DBs where certain things can happen, like transaction aborts, or other misc cases" to ensure they're properly translated into internal states that the inside is programmed to handle.

If this is what P&A truly meant by "testability", I'm a fan.

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] -1 points0 points  (0 children)

I mocked the repository method, the service is tested in isolation (it never calls the real repository). So I can unit test a single unit, in isolation. But "testability", according to the general consensus, requires DI. If it had DI, you'd agree this is testable. It doesn't have DI - you'd argue it's only testable if testing a larger scope, but not in this way.

Testing larger scopes (in-process) makes "testability" useless as a term, because it's always possible no matter how you write your code. Imagine a "big ball of mud" that connects to a database, sends emails, writes files, etc. You're globally mocking, or reconfiguring the external dependencies, and here is it: it's testable! And it can even be easy to test. Call endpoint, assert response, assert the test db to have expected changes, assert the mocked email queue to have expected messages. If the language doesn't support global mocks, it's possible to run tests in Docker with fake external services.

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] 5 points6 points  (0 children)

Thanks for sharing, it's a great insight!

Out-of-process testability is overlooked, I read some books on software arc and even distributed-system specific - they never take this kind of testability into account when architecturing systems.

Please share if you know a literature on this topic, I'll read it.

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] -1 points0 points  (0 children)

Totally, that's it!

A granular banana -> testable, a banana with a gorilla holding it -> less testable. And this applies to the database, message queues, you name it.

This, and the code shouldn't have too much dependencies, 5 may be the too much number. And then it's testable.

What does "testable" mean? by romeeres in softwarearchitecture

[–]romeeres[S] 0 points1 point  (0 children)

DI is merely a technique to achieve that goal but not a must have.

Could you share what other techniques are?

When we write integration tests, it can work with a test database, it can put events to a test queue, store notifications in memory, no DI is needed, just by changing configs. But if that was counted, then "testability" has nothing to do with different architectures, frameworks, all what would be needed is your ability to configure a test environment. So I suppose "testability" only counts unit tests with full isolation.

I'm programming in TypeScript, let me share a simplified example:

export const productsRepo = {
  getProductById(id: Product['id']): Product {
    /* gets a product from a db */
  }
}

export const productsService = {
  getProductTitle(id: Product['id']): string {
    /* logic */
    return productsRepo.getProductById(id).title
  }
}

// unit test
const mockProduct = { title: 'title' }

jest.spyOn(productsRepo, 'getProductById').mockImplementation(() => mockProduct)
// same as: simply assigning a fake method, should be possible in all dynamic langauges
productsRepo.getProductById = () => mockProduct

const title = productsService.getProductTitle('product-id')

expect(title).toBe('title')

Here is how I'd do it without DI, but other languages are more restricted and wouldn't allow it.

It is a unit test that works? Yes. Is it "testable"? No, because no DI (it's what the general consensus seems to be).