1

I am getting worried I have some terrible design pattern here in JS/Node.js/MongoDB. It seems to that to create an object, I need an object that looks like that object. This includes data that comes from the user or data that comes from the database. It is just a lot of duplication, but I have not figured another way around it.

Quick example

// Application user object
class User {
    // Right away, my constructor almost literally takes itself as params
    // The alternative being many setters
    constructor({
        username = '',
        password = '',
        email = ''
    }){
        this.username = username;
        this.password = password;
        this.email = email;
    }
}

// Form data from REST API
const formdata = {
    username: 'bob',
    password: '123',
    email: '[email protected]'
}

// Create the user in the app
const user = new User(formdata);

// Add to the db
insertIntoDb(user);

// Get from the db
const dbdata = getFromDb(userid);

// Turn into object
const user = new User(dbdata);

Maybe I am overthinking things. But my User class barely does anything to begin with. Is it acceptable if this class is just a definition of an object (kind of like a c struct) so developers know what its properties should be? This is mostly the point I think. Since JS is so loose, you could set object properties to whatever you want or read properties that don't even exist (which I usually solve by adding getters).

But I get this headache when I realize that most, if not all, of these user properties are just for persistence. Should this be part of a persistence layer only?

4
  • Well, why doesn’t your user class do anything?
    – Alexander
    Apr 10, 2021 at 21:59
  • I duno. I can pass it around to other functions to check its levels, but otherwise it doesn't do much. It could update itself with a setter, but its not an object that lives for more than a few lines of codes. It's practically pointless other than defining the schema. Apr 11, 2021 at 0:02
  • 1
    What does "check its levels" /mean? In general, having duplicate data structures isn't the worst thing. Some people apply DRY too early, and miss the point. Sometimes things are duplicated today, might not be duplicated in perpetuity. Consider a case where you use a third party API. Perhaps the DTO for that API looks identical to your model object, in terms of its fields. But in the future, if they start renaming fields, switching things up, you can can the DTO, and the adapter to your model, without needing your model to change across the app. Each duplication is like a kind of seam
    – Alexander
    Apr 11, 2021 at 0:52
  • It means it has a function like isAdmin() which returns true if this.level = 1. Other than that there are a just a few getters and setters that I only wrote as needed. I hate how JS gives us this inanely powerful dot notation anything goes style, but we aren't really allowed to use it. Apr 11, 2021 at 2:51

2 Answers 2

3

I would write the class the same way. Using an object is the normal way to simulate named parameters. Nothing wrong with this. What you gain with your solution is the following:

  • Your user objects don't just have a particular shape, but are also instanceof User.
    • If only a particular shape is needed, consider using TypeScript and declare an interface.
  • You have a clear place to add consistency checks, for example to ensure that the fields contain strings. Early checks make systems easier to debug.
  • You can verify that your user object won't contain unexpected fields.

Of course, these advantages may or may not matter. I think these are valuable when your object is part of your domain model and when there is business logic that depends on the object behaving a certain way (e.g. having fields that contain values of a particular type). You mention minimal business logic with an isAdmin() method, though it's not clear whether this should be modelled as part of this object.

If there is no substantial business logic (CRUD app) or when this object is really only used to pass user information to/from the database, then I'd skip it. Instead of attaching checks to the object, we could attach them to operations on the data – a procedural instead of an object-oriented view. So you might have a function db.updateUser({...}) that takes an object and then validates that it matches the database schema. In the context of schemaless databases like MongoDB, it could also be sensible to perform some validation when loading an object from the database.

2

It appears to me there are two different questions here:

  1. How to avoid the boilerplate code in the constructor?
  2. Is it a sound approach to start by designing an object in a struct-like fashion (and extend this later by adding methods)?

The answer to your first question ist the same I gave to your former question here: use Object.assign and the constructor becomes almost trivial.

To your second question: there are surely some people here in the community who would question this because it goes against their idealistic point of view "what OO should be". But I am a pragmatician, and I can tell you from first-hand experience that this is a perfectly viable approach to construct well maintainable, suffiently object-oriented software. If you need a User object with mostly data members and a few member functions, you can start with a class similar to the "DTO user" and refactor step-by-step, adding some business logic and maybe the required degree of encapsulation, if that's your design goal.

2
  • Thanks. I've avoided functions like Object.assign because it seems dangerous (it can assign properties not part of that object). Doing a hasOwnPoperty loop seems like it would be very expensive. So. I guess I will just use assign. I am in JS, I should use JS.. Apr 11, 2021 at 13:59
  • "Doing a hasOwnPoperty loop seems like it would be very expensive." - this sounds like an unsolicited assumption about performance without having measured it. As I wrote already in that other answer - retrieving the date from a database has a good chance to be so much slower than the code in the constructor that the performance hit can probably be neglected (but to be sure, you need to measure it).
    – Doc Brown
    Apr 11, 2021 at 15:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.