How to restore persistence properties that the domain model does not have - domain-driven-design

I have been thinking and reading about this for some time with not success.
I'm in the process of separating the db layer from the domain (I was using orm entities as domain entities).
To do it I have created simple mapper classes with methods to transform a persistence entity to a domain entity and viceversa.
The problem comes when a domain entity does not have all the persistence properties as they are useless for the bussiness logic (for example the user logo image url).
I map the user db result to its domain entity without saving the logo so when I try to convert it back to persistence I don't have a way to retrieve again the logo.
My question is, there is a pattern to solve this or a domain entity does have to always contain all the necessary info to be persisted even when that info is completely useless for the bussiness logic?
Code example:
// User persistence entity
class User {
name
is_active
account_id
logo
}
// User domain entity
class User {
name
isActive
account
}
// Mapper
class UserEntityMapper {
static toDomain(raw) {
return new User({
name: raw.name,
isActive: raw.is_active,
account: raw.account_id,
})
}
static toPersistence(User domain) {
return {
name: domain.name,
is_active: domain.isActive,
account_id: domain.account,
logo: // How tf can i get the logo here????
}
}
}

Related

Prisma client generated type for create input has weird type requirement for related table field, how to get a working type?

Use case is simple:
users table
sessions table
Each user can be logged in multiple places therefore they can have multiple sessions -> one to many relationship. in SQL this isn't a big deal/problem, but the generated types available in #prisma/client create a weird type when it comes to the input models.
I'm using NestJS and need TS models based on classes that exist at runtime, so I can make use of decorators on keys for input validation and graphql usage.
I'm creating a model based off of the Prisma.SessionCreateInput coming from #prisma/client.
prisma schema:
model User {
id String #id #default(uuid())
...
sessions Session[]
}
model Session {
id String #id #default(uuid())
user User #relation(fields: [userId], references: [id])
userId String
}
The TS model:
import { Prisma } from '#prisma/client';
export class SessionModel implements Prisma.SessionCreateInput {
id?: string;
userId?: string;
user: Prisma.UserCreateNestedOneWithoutSessionsInput;
}
Now I'm defining the type for user explicitly based on what Prisma is telling me to, in my opinion I don't need any user data there when creating a session, merely the userId.
UserCreateNestedOneWithoutSessionsInput:
type Prisma.UserCreateNestedOneWithoutSessionsInput = {
create?: (Prisma.Without<Prisma.UserCreateWithoutSessionsInput, Prisma.UserUncheckedCreateWithoutSessionsInput> & Prisma.UserUncheckedCreateWithoutSessionsInput) | (Prisma.Without<...> & Prisma.UserCreateWithoutSessionsInput);
connectOrCreate?: Prisma.UserCreateOrConnectWithoutSessionsInput;
connect?: Prisma.UserWhereUniqueInput;
}
which seems some sort of meta type that I very much do not need?
How can I maintain type safety and have these derived TS classes to work with?

Prisma client type system creates strong coupling with service methods

I am using prisma ORM with nestjs and it is awesome. Can you please help me understand how can I separate my database layer from my service methods since results produced by prisma client queries are of types generated by prisma client itself ( so i wont be having those types when i shift to lets say typeorm ). how can i prevent such coupling of my service methods returning results of types generated by prisma client and not my custom entities. Hope it makes sense.
The generated #prisma/client library is responsible for generating both the types as well as the custom entity classes. As a result, if you replace Prisma you end up losing both.
Here are two possible workarounds that can decouple the types of your service methods from the Prisma ORM.
Workaround 1: Generate types indepedently of Prisma
With this approach you can get rid of Prisma altogether in the future by manually defining the types for your functions. You can use the types generated by Prisma as reference (or just copy paste them directly). Let me show you an example.
Imagine this is your Prisma Schema.
model Post {
id Int #default(autoincrement()) #id
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
title String #db.VarChar(255)
author User #relation(fields: [authorId], references: [id])
authorId Int
}
model User {
id Int #default(autoincrement()) #id
name String?
posts Post[]
}
You could define a getUserWithPosts function as follows:
// Copied over from '#prisma/client'. Modify as necessary.
type User = {
id: number
name: string | null
}
// Copied over from '#prisma/client'. Modify as necessary.
type Post = {
id: number
createdAt: Date
updatedAt: Date
title: string
authorId: number
}
type UserWithPosts = User & {posts: Post[]}
const prisma = new PrismaClient()
async function getUserWithPosts(userId: number) : Promise<UserWithPosts> {
let user = await prisma.user.findUnique({
where: {
id: userId,
},
include: {
posts: true
}
})
return user;
}
This way, you should be able to get rid of Prisma altogether and replace it with an ORM of your choice. One notable drawback is this approach increases the maintenance burden upon changes to the Prisma schema as you need to manually maintain the types.
Workaround 2: Generate types using Prisma
You could keep Prisma in your codebase simply to generate the #prisma/client and use it for your types. This is possible with the Prisma.validator type that is exposed by the #prisma/client. Code snippet to demonstrate this for the exact same function:
// 1: Define the validator
const userWithPosts = Prisma.validator<Prisma.UserArgs>()({
include: { posts: true },
})
// 2: This type will include a user and all their posts
type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>
// function is same as before
async function getUserWithPosts(userId: number): Promise<UserWithPosts> {
let user = await prisma.user.findUnique({
where: {
id: userId,
},
include: {
posts: true
}
})
return user;
}
Additionally, you can always keep the Prisma types updated to your current database state using the Introspect feature. This will work even for changes you have made with other ORMS/Query Builders/SQL.
If you want more details, a lot of what I've mentioned here is touched opon in the Operating against partial structures of your model types concept guide in the Prisma Docs.
Finally, if this dosen't solve your problem, I would request that you open a new issue with the problem and your use case. This really helps us to track and prioritize problems that people are facing.

type-graphql entities how to omit fields from JSON.stringify?

We are using https://github.com/MichalLytek/type-graphql to define our graphql schema When we serialize the raw typescript entity object this doesn't respect the various field annotations in our GQL entities and ends up leaking unwanted data. Example below a Profile entity class
import { Field, Int, ObjectType } from 'type-graphql'
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'
import { Account } from '../account/account.entity'
export class Profile {
#Field()
#Column({ unique: true })
public username: string
#Field()
#Column()
public name: string
// Relations
#Column()
public accountId: string
#ManyToOne(type => Account, account => account.profiles, { eager: true })
public account: Account
}
account has sensitive data. When we JSON.stringify a Profile reference we don't want account output. Account is not annotated with #Field and we expect it would not be output.
The decorators used by type-graphql only exist to instruct type-graphql how to translate your class to a GraphQL type -- they are not going to somehow impact how an instance of the class is serialized by a native function like JSON.stringify.
In the context of your schema, the account won't ever be returned in the response unless you explicitly create a field for it, even if the Profile instance used by your resolvers has that property. This is a symptom of how field resolution works in GraphQL.js. However, a Profile instance will always have an account property on it because that's what you've defined as part of your class.
It's unclear from your question why you're calling stringify in the first place, but assuming it's to use in some other context, like logging, then you'll want to expose your own method for serializing the instance that limits which properties are returned. This can be done easily using something like lodash's pick or omit.
serialize () {
return _.omit(this, ['account'])
}

Force database tablename for collection relationship in Sails

In a sails project, considering a model User and a model Role, with a relationship between User and Role :
// `User.js
module.exports = {
attributes: {
...
roles: {
collection: 'role',
dominant: true
},
...
}
}
For the the database representation, sails/waterline will create following tables :
table user,
table role,
table like user_roles__role_roles_role to represent the collection
I know we can force the name for the models USER and ROLE
(with the property 'tablename' : http://sailsjs.com/documentation/concepts/models-and-orm/attributes).
But how can we force the name the relationship table ? (Especially this name is quite long and tends to exceed limit).
Assuming this is a two-way relationship, and the Role model has a users collection, Sails will expect a table named role_users__user_roles, which has the role id first, user id second.
Your example table name would require User to be dominant and would require the Role model to have an attribute named roles_role that is a User collection.
To create your own join table, you can use the through association method and add a new model that represents the relationship, perhaps UsersRoles, and specify the tableName in that model definition.
Examples of the through association:
sails
docs
similar question
gist from comments in that question

Apply a typescript class to a member of a mongoose document

I have a mongoose schema in typescript and I am already using an interface for properties of the document itself. I want to create a class so when I find a document I can immediately call methods. I also want the document to contain members that are instances of other classes and call the methods those members. Is this possible? Here is what I have so far:
models/user.ts:
import mongoose from '../lib/mongoose';
const Schema = mongoose.Schema;
interface IUser extends mongoose.Document {
username: string;
email: string;
hash: string;
};
var userSchema = new Schema({
username: String,
email: String,
hash: String
});
export default mongoose.model<IUser>('User', userSchema);
I don't actually need to do this for my user model but it is the shortest so I thought it would be a good example. Let's say I wanted a method to check if the user's email contains their username.
Here is a simple user class:
class User {
constructor(public username: string, public email: string, public hash: string) {
}
emailUsernameCheck(): boolean {
return this.email.includes(this.username);
}
}
I want to be able to make a call like this:
import User from '../models/user';
User.findOne({username: 'foo'}).emailUsernameCheck();
Let's also say I wanted to give each user a square with a height and width, and the square class has an area method.
Here is a sample square class:
class square {
constructor(public height: number, public width: number) {
}
area(): number {
return this.height * this.width;
}
}
I also want to be able to make a call like this:
import User from '../models/user';
User.findOne({username: 'foo'}).square.area();
I know I can make the classes separately, give them constructors that take objects, and construct the classes that way. I will do this if it is the only way but it means importing the class separately and adds an extra step to every database lookup. It also opens up room for error since the classes are maintained separately from the schema. I would love to handle this all in my model. Is there something I can add to models/user.ts so I can do things like the examples above?
I think is not possible to use a separate Class in mongoose.
You can extend the Document Model schema using instance and statics methods
Or define a Custom Schema Type for your fields using separate classes (you can find a full example in Mongoose advanced custom schema object type).
Btw you can copy methods from an external object and prototype to Schema.* and Schema.methods.*, but the final object won't share the same prototype as the source class (instanceof).

Resources