guregu / dynamo

expressive DynamoDB library for Go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Querying for multiple item types in a single query

chris opened this issue · comments

I'm wondering what advice folks have for using this package to handle queries that produce multiple item/model types in a single query. This is the single table design pattern where you might do something like query for a user AND their orders (or some other item that is not represented by the same model as User)? This would be a query where say you only specify the PK, or maybe the PK and then an SK with some less than or greater than or other case, and that would then return something like:

PK SK
USER#123 USER#12345
USER#123 ORDER#789
USER#123 ORDER#312

Obviously this would need more complex marshalling and maybe just doesn't fit the general use of this package? The benefit here is avoiding doing two DynamoDB queries to get this data, so just wondering if others have done this using this Go package, or what suggestions folks have.

It's not well documented, but this package's equivalent of json.RawMessage is map[string]*dynamodb.AttributeValue. You can unmarshal your stuff to that type first and then unmarshal it again when you know the proper type.

Something like this (untested, but should illustrate the technique):

func getOrders(ctx context.Context, userID int) (user User, orders []Order, err error) {
	pk := fmt.Sprintf("USER#%d", userID)
	iter := db.Table("...").Get("PK", pk).Iter()

	var item map[string]*dynamodb.AttributeValue
	for iter.NextWithContext(ctx, &item) {
		sk := item["SK"].S
		switch {
		case sk == nil:
			err = fmt.Errorf("invalid sort key")
			return
		case strings.HasPrefix(*sk, "USER#"):
			err = dynamo.UnmarshalItem(item, &user)
			if err != nil {
				return
			}
		case strings.HasPrefix(*sk, "ORDER#"):
			var order Order
			err = dynamo.UnmarshalItem(item, &order)
			if err != nil {
				return
			}
			orders = append(orders, order)
		default:
			err = fmt.Errorf("get orders: unknown type: %s", *sk)
			return
		}
	}
	err = iter.Err()
	return
}

It'd be nice to have something built-in to deal with this. Maybe generics can help? 🤔

Thank you @guregu ! That makes sense. Something built in may work, but this also seems fairly simple and there are uses cases of this technique where you might have different add-on types (e.g. depending on how you sort and filter, you might have one that is user+orders, and another that is user+friends or some such thing). But, as you said, maybe with generics you might be able to do that. Anyway, I appreciate the super fast response and info!

Just a quick followup - I've implemented this and it works great.