Giter VIP home page Giter VIP logo

Comments (13)

tywalch avatar tywalch commented on June 4, 2024 2

I'm back at my desk now, it looks like you're hoping to learn more about how you might query across multiple entities. With ElectroDB this functionality to accomplished via collections. Collections allow you to establish a PK interface across multiple entities and query across all entities that implement the same composite key. This maintains ElectroDB's entity isolation guarantees while also allowing for relationships through loose coupling.

Regarding your desire for more examples there are a few places you can view those:

  • The README contains a few examples (including usage of Collections) here
  • The repo itself has an examples directory here which a simple example similar to the one you provided (for a ticketing system) and a more complex version control database.
  • A runkit with examples and descriptions here

Regarding your specific example, I have put together a Service that allows you to accomplish the queries you described above.

Does this information speak to your original issue? Are any of the examples above not quite what you were expecting or could be improved for "Simple example docs"? I appreciate the ticket, this feedback helps to put focus on what new users need 👍

from electrodb.

tywalch avatar tywalch commented on June 4, 2024 1

Hi @aisflat439!

I'm not at my desk at the moment but when I am back I'll write more of an in-depth reply. In the interest of helping you sooner, hereis a playground that I think demonstrates your need.

Unlike other libraries, ElectroDB abstracts out the implementation of keys with DynamoDB. You simply provide the composite attributes used to build the key and ElectroDB does the rest.

Again I'll revisit this issue when I get back to my desk so feel free to add more detail or questions in the meantime. I hope this helps in the short term 👍

from electrodb.

tywalch avatar tywalch commented on June 4, 2024 1

Hey @aisflat439! A few things:

  1. In your actor/role example I wanted to correct the line you had that looked like this:
let resultICanAccessRoleFrom = await Actors.query.name("Tom Hanks").movie("Toy Story")

When performing a query the methods that follow expect an object with name and optionally movie properties. In your example you call .name() and .movie() which isn't quite ElectroDB syntax. Here is an example of how that would look with working ElectroDB syntax

  1. Regarding your user/order example checkout this playground to see how you can model that example with ElectroDB.

  2. If you'd like to see an even more built out example, here is an example that is similar to the Version Control example in Alex's book. It can also be found in the examples/versioncontrol directory in the repo.

Let me know if this helps!

from electrodb.

tywalch avatar tywalch commented on June 4, 2024 1

I recreated your playground here

The way you are using postComments is correct

The params you sent in your last message look correct:

{
  KeyConditionExpression: '#pk = :pk and begins_with(#sk1, :sk1)',
  TableName: 'amplify-m1-seven-single-tables-table',
  ExpressionAttributeNames: { '#pk': 'gsi1pk', '#sk1': 'gsi1sk' }, // <- The ExpressionAttributeNames point to the correct GSI
  ExpressionAttributeValues: {
    ':pk': '$reddit#postid_01g8v1bgzpkmbkxt96mgvj2t91',
    ':sk1': '$postcomments'
  },
  IndexName: 'gsi1pk'
}

from electrodb.

tywalch avatar tywalch commented on June 4, 2024 1

That is an error from Amazon (the docClient), looks like the table you're using doesn't have the GSI you're mapping to? You configured ElectroDB to use an index called gsi1pk which I assume is wrong because that is the same name you gave your pk on that index

from electrodb.

tywalch avatar tywalch commented on June 4, 2024 1

A few things:

  1. In the playground you shared, you mapped your postComments index to gsi1pk not gsi1
postComments: {
        collection: "postComments",
        index: 'gsi1pk', // <--
        pk: {
          field: "gsi1pk",
          composite: ["postId"]
        },
        sk: {
          field: "gsi1sk",
          composite: ["commentId"]
        }
      }

This explains your error:

The table does not have the specified index: gsi1pk

I believe your index is called gsi1 based on what you shared above, is that correct?

  1. The example I provided above (after switching the index name from gsi1pk to gsi1) the query parameters come out to this:
{
    "KeyConditionExpression": "#pk = :pk and begins_with(#sk1, :sk1)",
    "TableName": "Dynamo.Configuration",
    "ExpressionAttributeNames": {
        "#pk": "gsi1pk",
        "#sk1": "gsi1sk"
    },
    "ExpressionAttributeValues": {
        ":pk": "$reddit#postid_postid",
        ":sk1": "$postcomments"
    },
    "IndexName": "gsi1"
}

This looks correct to me, it sounds like your concern is the presence of sk1 and pk? If so, you can see in the ExpressionAttributeNames that those values map to correct column names: gsi1pk and gsi1sk

from electrodb.

aisflat439 avatar aisflat439 commented on June 4, 2024 1

Of course you are correct. I didn't fully understand the role that the index and collection keys play. This is a little exacerbated by not fully understanding the setup of SST's Ideal Stack

import { StackContext, Table } from "@serverless-stack/resources";

export function Database({ stack }: StackContext) {
  const table = new Table(stack, "table", {
    fields: {
      pk: "string",
      sk: "string",
      gsi1pk: "string",
      gsi1sk: "string",
    },
    primaryIndex: {
      partitionKey: "pk",
      sortKey: "sk",
    },
    globalIndexes: {
      gsi1: { // <---------- this name is the same index name, that a new concept for me.
        partitionKey: "gsi1pk",
        sortKey: "gsi1sk",
      },
    },
  });

  return table;
}

In my PostEntity

      // ...
      postComments: {
        collection: "postComments", // <----- I need this to have the query service work
        index: "gsi1", // <------ not exactly sure but I needed this
        pk: {
          field: "gsi1pk",
          composite: ["postId"]
        },
        sk: {
          field: "gsi1sk",
          composite: []
        }
      }

In my CommentEntity

      // ...
      postComments: {
        collection: "postComments", // <----- I need this too. They are linked
        index: "gsi1", // <------ still kinda fuzzy here but now I know the pattern
        pk: {
          field: "gsi1pk",
          composite: ["postId"]
        },
        sk: {
          field: "gsi1sk",
          composite: []
        }
      }

I'm continuing to practice these concepts. Thanks again for being so helpful. Feel free to ignore me any time as I work through this. I'll never begrudge you that. I'm starting to get it and I really appreciate all your comments, even though sometimes I don't understand them at first.

from electrodb.

aisflat439 avatar aisflat439 commented on June 4, 2024

Thanks for all this. I think I was finding the right path, based on what you sent. I'll have to work through it a bit more but Collections seem to be exactly what I'm looking for, and where the docs were taking me.

Here's what I'm noticing as someone who's newer to single table design and still newer to electrodb.

When I think about a table currently I'm thinking about examples from Rick's talks or Alex's book that are something like this:

image

I can solve for the access pattern "getRolePlayedByActor" with this access pattern.

  const params = {
    TableName: process.env.TABLE_NAME,
    KeyConditionExpression: "#pk = :pk and sk = :sk",
    ExpressionAttributeNames: {
      "#pk": "pk",
      "#sk": "sk",
    },
    ExpressionAttributeValues: {
      ":pk": "Tom Hanks",
      ":sk": "Toy Story",
    },
  };

The model I build in excel, dynobase, nosqlmodeler, has a relationship to the query. I can see "I write the pk as Tom Hanks". As someone who is coming from those sources, electrodb docs show the relationship between the electrodb and the query. The missing piece for me, is what the relationship between ElectroDB and the modeler.

Based on a whole 3 hours trying to pick up electrodb, that I'd want something like

const model = {
      model: {
        version: "1",
        entity: "Actor",
        service: "actors",
      },
      attributes: {
        name: {
          type: "string",
          required: true,
        },
        role: {
          type: "string",
          required: true,
        },
        movie: {
          type: "string",
          required: true,
        },
      },
    indexes: {
      primary: {
        pk: {
          field: "pk",
          composite: ["name"],
        },
        sk: {
          field: "sk",
          composite: ["movie"],
        },
      },
    },
}

let Actors = new Entity(model, {table})
let resultICanAccessRoleFrom = await Actors.query.name("Tom Hanks").movie("Toy Story")

I think that's in the neighborhood of correct at least, I'm still working that out. I think, similar to equivalent parameters, some tables that show what the data looks like would be helpful.

When I look at a table like this:

image

I have basically no mental model for applying ElectroDB to that. My thought process is a series of reversals. Here's the table I want, that can be made by queries that look, roughly like this, now I need to get ElectroDB to generate that query. As a result, I think examples that show a modeler view and the equivalent Entity Model in ElectroDB, and finally an example query of that Modeler would be valuable.

It's entirely reasonable that this is out of scope. I'm excited to work through this and get the hang of it either way. These are simply my first pass thoughts around this.

from electrodb.

aisflat439 avatar aisflat439 commented on June 4, 2024

Hey Tyler. I'm trying to work at this a bit more and get comfortable. Can you please validate if my understanding is correct?

I have three entities [Redditor, Post, Comment]

I want a query getPostById. In order to do this I create a service in ElectroDB that includes the Post and Comment entities.

const PostService = new Service({ PostEntity, CommentEntity });

This should generate

// primaryKey: service + postId + uuid of the post 
{ gsi1pk: '$reddit#postid_01g8v1bgzpkmbkxt96mgvj2t91' }
// sortKey: (name of collection that matches on Post and Comment) + either entity or commentID  
{ gsi1sk: '$postcomments' }

Allowing me to search by postId that begins_with $postComments which should return the post itself, plus any comments. Is this correct?

Here is my electro db playground

from electrodb.

aisflat439 avatar aisflat439 commented on June 4, 2024

Well I've got something wrong 😄. I'm getting an sk1 and the pk isnt' converting over. More to read.

 {
  KeyConditionExpression: '#pk = :pk and begins_with(#sk1, :sk1)',
  TableName: 'amplify-m1-seven-single-tables-table',
  ExpressionAttributeNames: { '#pk': 'gsi1pk', '#sk1': 'gsi1sk' },
  ExpressionAttributeValues: {
    ':pk': '$reddit#postid_01g8v1bgzpkmbkxt96mgvj2t91',
    ':sk1': '$postcomments'
  },
  IndexName: 'gsi1pk'
}

from electrodb.

aisflat439 avatar aisflat439 commented on June 4, 2024

Hmm... It must be something with the builder then. In GraphQl I'm geting

{
  "errors": [
    {
      // I think this error is just being surfaced through the builder in a strange way perhaps.
      "message": "The table does not have the specified index: gsi1pk - For more detail on this error reference: https://github.com/tywalch/electrodb#aws-error",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "getPost"
      ]
    }
  ],
  "data": null
}

Thanks again for all this help. I'll have to look at that getpost example you created. It's neat that I can split it up into post and comment like that.

from electrodb.

aisflat439 avatar aisflat439 commented on June 4, 2024

I'm not exactly sure I understand that comment. I think what you are saying is that I need to change my indexName. However, it's not very obvious to me what role they play here.

My experience hand writing these queries I'd write something like this:

const getCustomerByGSI = async ({ shop, customerId }) => {
  const params = {
    TableName: process.env.TABLE_NAME,
    IndexName: "gsi1pk",
    KeyConditionExpression: "#gsi1pk = :gsi1pk",
    ExpressionAttributeNames: {
      "#gsi1pk": "gsi1pk",
    },
    ExpressionAttributeValues: {
      ":gsi1pk": `${HASH.shop}${shop}${HASH.customer}${customerId}`,
    },
  };

  return await dynamoDb.query(params);
};

Obviously that example isn't one to one. But in this table I have:

image

So I'm creating the data with the correct gsi1pk and gsi1sk. When I write this query however:

const PostService = new Service({ RedditorEntity, PostEntity, CommentEntity });

export async function getPost(postId: string) {
  const params = PostService.collections.postComments({ postId }).params()
  console.log('params: ', params);

  return PostService.collections.postComments({ postId }).go()  
}

the params I get are:

KeyConditionExpression: '#pk = :pk and begins_with(#sk1, :sk1)',
 TableName: 'amplify-m1-seven-single-tables-table',
 ExpressionAttributeNames: { '#pk': 'gsi1pk', '#sk1': 'gsi1sk' },

It's not obvious to me why this is the result.

Note: I have validated that the following query works in Dynobase. So I am pretty confident that the gsi1pk and gsi1sk are correctly applied to the table.

{
 "TableName": "amplify-m1-seven-single-tables-table",
  "IndexName": "gsi1",
  "KeyConditionExpression": "#DDB_gsi1pk = :pkey and begins_with(#DDB_gsi1sk, :skey)",
  "ExpressionAttributeValues": {
    ":pkey": "$reddit#postid_01g8vn5sq50n4pkac6nkb97fbq",
    ":skey": "$postcomments"
  },
  "ExpressionAttributeNames": {
   "#DDB_gsi1pk": "gsi1pk",
    "#DDB_gsi1sk": "gsi1sk"
  },
  "ScanIndexForward": true,
  "Limit": 100
}

With my current setup I get the following from ElectroDB

{
 KeyConditionExpression: '#pk = :pk and begins_with(#sk1, :sk1)',
 TableName: 'amplify-m1-seven-single-tables-table',
 ExpressionAttributeNames: { '#pk': 'gsi1pk', '#sk1': 'gsi1sk' },
 ExpressionAttributeValues: {
   ':pk': '$reddit#postid_01g8vn5sq50n4pkac6nkb97fbq',
   ':sk1': '$postcomments'
 },
 IndexName: 'gsi1'
}

from electrodb.

tywalch avatar tywalch commented on June 4, 2024

Hey @aisflat439!

I hope modeling is going well for you, I am going to close this ticket for the sake of clean up, but feel free to open another if the need arises!

from electrodb.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.