OlexG / Patterns

Simples demos of various design patterns in javascript and typescript.
0 stars 0 forks source link

Singletons are generally considered an antipattern #4

Closed mattfbacon closed 3 years ago

mattfbacon commented 3 years ago

Your example of a DB connection is a classic counterexample against singletons: a DB connection is not really unique and global:

It's not unique because you can have connections to multiple different databases.

It's not global because the DB itself handles multiple connections to the same database, and trying to handle this within your singleton would be reinventing the wheel.

My suggestion is to remove this.

mattfbacon commented 3 years ago

Naturally this applies to Creational/singleton.ts.

OlexG commented 3 years ago

I did see that singletons are generally frowned upon but I think it is still worth implementing them as in some cases they can be useful.

Well, what if you want only one connection to one database.

I am not sure what you mean by "not global", can you elaborate? Let's say you want to reuse the connection as much as possible, to save resources maybe. Wouldn't you want your one connection to be global?

mattfbacon commented 3 years ago

Reusing a connection (e.g., via a connection pool) is different from using it in multiple async blocks at the same time. Consider the following function:

function actOnDb() {
  db.foo();
  db.bar();
}

If this function is called by two async blocks at the same time, you would want them to execute like this:

But that is not guaranteed and it could also occur as:

This can create a really obscure and hard-to-find bug.

On the other hand, by having two database connections when you are doing two things to the database at the same time, you can delegate any necessary synchronization to the database, e.g., by starting a transaction.

The transactions themselves are also good examples. Consider the following function:

function sensitiveAtomicDatabaseAction() {
  db.startTransaction();
  db.sensitive();
  db.sensitive2();
  if (db.checkSomething()) {
    db.commit();
  } else {
    db.rollback();
  }
}

I think you can see how interleaving calls to startTransaction and rollback/commit can be problematic. A single database connection is not reentrant.

OlexG commented 3 years ago

Some databases like SQLite only allow one concurrent write so in this case, we might as well reuse the connection and let the database handles queuing. The transaction stuff is good but not too related to the pattern and just shows how you can reuse this connection. But as you mentioned, I will add the map for different database types.

mattfbacon commented 3 years ago

Some databases like SQLite only allow one concurrent write

This is pretty much irrelevant to the issue shown above. You would probably need a mutex (either in the programming language, or using BEGIN EXCLUSIVE) for the database in that case, but it's not important.

Also, SQLite does allow multiple connections so that would be an easy way around this, as I was saying. In that case you would be able to make a type of "connection pool", where you could request a connection that would be guaranteed to be only for your use for some period of time.

OlexG commented 3 years ago

but then it wouldn't be a singleton pattern, so I will leave it as is.