Which of these 2 MongoDB design choices is recommended? - mongodb

I have 2 models: Supplier and Supplier type. The client should be able to retrieve all Suppliers belonging to a particular Supplier Type:
Supplier:
name: {
type: String,
required: true,
minlength: 2,
maxlength: 255,
},
...
supplier_type: { type: ObjectId, ref: 'SupplierType' },
}
SupplierType:
name: {
type: String,
required: true,
minlength: 2,
maxlength: 50,
},
suppliers: [{ type: ObjectId, ref: 'Supplier' }],
};
Design 1:
A field on Supplier contains a reference to the assigned Supplier Type object.
An array on the Supplier Type object contains object references to all the Suppliers that have the Supplier Type.
The client queries the relevant Supplier Type document, let's say Vegetables and contained in the response, among other fields, will be a list of Suppliers.
With this approach, each time a new Supplier is saved, at least one other DB operation would be needed to update the Suppliers array on the Supplier Type object.
Design 2:
Remove the Suppliers reference array from Supplier Type object
A field on Supplier contains a reference to the assigned Supplier Type object, as in design 1.
The Client, this time, queries the Supplier document with a parameter specifying the Supplier Type i.e. GET /suppliers?supplier-type=Vegetables
Which design makes the most sense/would be the recommended approach in MongoDB?

I see no reason to split these two objects into separate collections. Embed the SupplierType inside the Supplier object. If a Supplier can be of more than one SupplyType you can extend that object into an array.
Now when you get any Supplier you automatically get their SupplierType without the need for a join and/or a second query.
Want to find all the Supplier docs by SupplyType? Query on the SupplyType field and a single cursor will return all the relevant suppliers.
Apply indexes to either field if the number of items is large to improve performance.

Related

One way one to many relation

I have a Recipe and a Tag model. Currently, the recipe contains an array of id's belonging to Tag:
#Entity()
export class Recipe extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
public id!: string;
#Column({ type: 'varchar' })
public title!: string;
#Column({ type: 'varchar' })
public description!: string;
#Column({ type: 'simple-array', nullable: true })
public tags: string[];
}
#Entity()
export class Tag extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
public id!: string;
#Column({ type: 'varchar' })
public name!: string;
}
However, I am currently not making use of the relational capabilities of TypeORM. I was wondering though, how would i go about doing this? Since the relation only works one way, i.e. the one Recipe having many Tags.
I could be wrong, but I believe by default, you must declare both ways--even if you only intend to use a single direction of the relationship.
For example, you need to declare that a Recipe has many Tags you also have to set up the Tag to Recipe relationship even if you aren't going to use it.
Given your example, you'll need to set up a one:many and a many:one relationship.
Since Recipe will "own" the tags, it will have the one:many:
// recipe.entity.ts
#OneToMany(() => Tag, (tag) => tag.recipe)
tags: Tag[];
Then the inverse will look like this:
// tag.entity.ts
#ManyToOne(() => Recipe, (recipe) => recipe.tags)
#JoinColumn({
name: 'recipeId',
})
recipe: Recipe;
If you're considering having many recipes own the same tag, you may need to consider using a many:many relationship
EDIT
I suppose you could technically store an array of id's in a column to represent tags for any given recipe. The question here is, what happens if you decide you need further info on any given tag?
IMO, (and it's just that so take all of this with a grain of salt). You are bending your recipe table to also store relationship info.
I have found it to be more helpful to keep my "buckets" (tables) as specific as possible. That'd leave us with:
recipes | tags | recipes_tags
-----------------------------
That way my recipes table just has recipes & that's it. "Just give me all recipes...". Tags is the same, "just show me all tags"
The two things are completely different entities. By setting up a ManyToMany relationship, we're telling TypeORM that these two columns are related--without "muddying" either of their underlying data.
You can add/remove columns on the pivot table should you decide you want more info about the relationship. At that point, you'd still be working with the relationship, not a tag or recipe so your data would still be nice & lean!
Another example from one of my own use cases...
I have an Activity and a Resource any given resource can have one or more Activities. (activities = tags/ resources = recipes)
// activity.entity.ts
...
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
name: string;
...
#ManyToMany((type) => Resource, (resource) => resource.activities)
resources: Resource[];
// resource.entity.ts
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
name: string;
...
#JoinTable()
#ManyToMany((type) => Activity, (activity) => activity.resources)
activities: Activity[];
The above generates a resources_activities_activities table.
Within that table is:
resourceId | activityId
------------------------
I could add additional columns here as well. createdBy or status or something else that is specific to the relationship. Each entry in this table has a relationship back to the activity and the resource--which is great!
I realize we've gone outside the scope of your original question, but I think this is a pretty small step outside, for a potential big win later on.
When I make a request to get a resource: example.com/resources/123 I get something like this back:
"id": "123"
...
"activities": [
{
"id": "f79ce066-75ba-43bb-bf17-9e60efa65e25",
"name": "Foo",
"description": "This is what Foo is.",
"createdAt": "xxxx-xx-xxxxx:xx:xx.xxx",
"updatedAt": "xxxx-xx-xxxxx:xx:xx.xxx"
}
]
...
Likewise, any time I get an activity, I also get back any resources that are related to it. In my front-end I can then easily do something like resource.activities.

How make this relation in stack MERN?

I have Online Shop, where exists products and category. The product has in model field
category: {
type: Types.ObjectId,
ref: "Category",
required: [true, "Product category is required"]
},
and when i add new product select category ( in list exists ) and add id.
But if remove category, the products have categoryId which does not exists. Is it possible to implement the functionality that used in wordpress. For example if in wordpress i remove category, all posts who had this category, transfer in category uncategorized (This category create automatically and cannot be delete)
If I understood you correctly, you can use mongoose middlewares to "cascade deleting". So when a category is removed, you can to code that every product has now "Uncategorized"
You can use a pre hook when you call delete function and do something like this (not tested with your schema):
category.pre(/(?:delete|remove)/, function(next) {
var id = this.getQuery()._id; //get category _id
product.updateMany({
category: id
},{
$set:{
category:yourUncategorizedId
}
}).then(next()).catch(e => next(e))
})
So, in this case, a regex match is used to go into the hook.
This hook is called when a remove or delete function is called from category model. So, when a category is deleted, this hook will update all references by your uncategorizedId to ensure any product has a non-existing category reference.

Populate a query in Mongoose with Schema First approach and NestJS

First off I want to say this question is similar to this one which references this one. I have the exact same question as the second link except a notable difference. I'm trying to extend a class generated by NestJS which defines a property.
I'm using NestJs with the Schema first approach found here. I'm also generating a classes file based on my GraphQL Schema.
Here is the Schema:
type Location {
name: String!
owner: User!
}
Which generates the class:
export class Location {
name: string;
owner: User;
}
Now, I want to extend this class so I don't have to repeat the data (there are a lot more fields not shown). I also I want to add fields that live on a document but are not in the schema (_id in this example). Here is my LocationDocument and my schema.
export interface LocationDocument extends Location, Document {
_id: Types.ObjectId
}
export const LocationSchema: Schema = new Schema(
{
name: {
type: String,
required: true,
},
owner: {
type: Types.ObjectId,
ref: 'User',
}
);
Now here is my issue. The generated Location class from the GraphQL schema defines the owner property as a User type. But in reality it's a just a mongodb id until it is populated by Mongoose. So it could be a Types.ObjectId or a User on a UserDocument. So I attempted to define it as:
export interface LocationDocument extends Location, Document {
_id: Types.ObjectId
owner: User | Types.ObjectId;
}
But this throws an error in the compiler that LocationDocument incorrectly extends Location. This makes sense. Is there any way to extend the User Class but say that owner property can be a User Type (once populated by Mongoose) or a mongo object ID (as is stored in the database).
I decided that having a property that can be both types, while easy with Mongoose and JS, isn't the typed way. In my schema I have an owner which is a User type. In my database and the document which extends it, I have an OwnerId. So to people accessing the API, they don't care about the ownerId for the relationship. But in my resolver, I use the Id. One is a Mongo ID type, the other is a User type.

How to query an n:n adjacency list map in DynamoDB without using scan

I'm trying to model a cataloging system in DynamodDB. It has "Catalogs" which contains "Collections". Each "Collection" can be tagged by many "Tags".
In an RDBMS I would create a table "Catalogs" with a 1:n relationship with "Collections". "Collections" would have an n:n with "Tags" as a Collection can have multiple Tags and a Tag can belong to multiple Collections.
The queries I want to run are:
1) Get all catalogs
2) Get catalog by ID
3) Get collections by catalog ID
I read on AWS I can use the adjacency list map design (because I have the n:n with "Tags"). So here is my table structure:
PK SK name
cat-1 cat-1 Sales Catalog
cat-1 col-1 Sales First Collection
cat-1 col-2 Sales Second Collection
cat-2 cat-2 Finance Catalog
tag-1 tag-1 Recently Added Tag
col-1 tag-1 (collection, tag relationship)
The problem here is I have to use a scan which I understand to be inefficient in order to get all "Catalogs" because a query's PK has to be an '=' and not a 'Begins With'.
The only thing I can think of is creating another attribute like "GSI_PK" and add "Catalog_1" when the PK is cat-1 and the SK is cat-1, "Catalog_2" when the PK is cat-2 and SK is cat-2. I've never really see this done so I'm not sure if it's the way to go and it takes some maintenance if I ever want to change IDs.
Any ideas how I would accomplish this?
In that case, you can have the PK be the type of the object and the SK be a uuid. A record would look like this { PK: "Catalog", SK: "uuid", ...other catalog fields }. You can then do a get all catalogs by doing a query on the PK = Catalog.
To store the associations you can have a GSI on two fields sourcePK and relatedPK where you could store records that associate things. To associate an object you would create a record like e.g. { PK: "Association", SK: "uuid", sourcePK: "category-1", relatedPK: "collection-1", ... other data on the association }. To find objects associated with the "Catalog" with id 1, you would do a query on the GSI where sourcePK = catalog-1.
With this setup you need to be careful about hot keys and should make sure you never have more than 10GBs of data under the same partition key in a table or index.
Let's walk through it. I'll use GraphQL SDL to layout the design of the data model & queries but you can just apply the same concepts to DynamoDB directly.
Thinking data model first we will have something like:
type Catalog {
id: ID!
name: String
# Use a DynamoDB query on the **Collection** table
# where the **catalogId = $ctx.source.id**. Use a GSI or make catalogId the PK.
collections: [Collection]
}
type Collection {
id: ID!
name: String
# Use a DynamoDB query on the **CollectionTag** table where
# the **collectionId = $ctx.source.id**. Use a GSI or make the collectionId the PK.
tags: [CollectionTag]
}
# The "association map" idea as a GraphQL type. The underlying table has a collectionId and tagId.
# Create objects of this type to associate a collection and tag in the many to many relationship.
type CollectionTag {
# Do a GetItem on the **Collection** table where **id = $ctx.source.collectionId**
collection: Collection
# Do a GetItem on the **Tag** table where **id = $ctx.source.tagId**
tag: Tag
}
type Tag {
id: ID!
name: String
# Use a DynamoDB query on teh **CollectionTag** table where
# the **tagId = $ctx.source.id**. If collectionId is the PK then make a GSI where this tagId is the PK.
collections: [CollectionTag]
}
# Root level queries
type Query {
# GetItem to **Catalog** table where **id = $ctx.args.id**
getCatalog(id: ID!): Catalog
# Scan to **Catalog** table. As long as you don't care about ordering on a filed in particular then
# this will likely be okay at the top level. If you only want all catalogs where "arePublished = 1",
# for example then we would likely change this.
allCatalogs: [Catalog]
# Note: You don't really need a getCollectionsByCatalogId(catalogId: ID!) at the top level because you can
# use `query { getCatalog(id: "***") { collections { ... } } }` which is effectively the same thing.
# You could add another field here if having it at the top level was a requirement
getCollectionsByCatalogId(catalogId: ID!): [Collection]
}
Note: Everywhere I use [Collection] or [Catalog] etc above you should use a CollectionConnection, CatalogConnection, etc wrapper type to enable pagination.

Sequelize Polymorphic HasMany/HasOne through Join Table

I have a Customer table, an Address table and a join table named CustomerAddress.
The customer needs a Shipping Address, a Billing Address, a Default Address, and all of the customer's addresses need to be accessible in a field addresses, this is because Address table is polymorphic.
I would like to do this without having a target key or foreign/target key in the Customer table or Address table, and rely on the CustomerAddress table to specify which address is which, this is what I have for the join table:
//CustomerAddress
schema = {
address_id: {
type: Sequelize.INTEGER,
unique: 'customeraddress_address'
},
// Type of address eg. shipping, default, billing
address: {
type: Sequelize.STRING,
unique: 'customeraddress_address'
},
customer_id: {
type: Sequelize.INTEGER,
unique: 'customeraddress_address',
references: null
}
}
I'm not able to figure out how the associations need to be set up to accomplish this. Any help is greatly appreciated.
The way to do this is actually through a belongsToMany association where you imply that it's a belongsTo. This way it uses the join table be default. The difference being that it returns an array instead of an object, which defeats the point. A compromise could be that when overriding the instanceMethod toJSON, you morph the array into an object, or make a custom setter/getter that does this when the association is fetched.