Use the ledger's context or a specific map?

Consider an author trying to maintain a library on the ledger with

template Book
  with
    author : Party
    isin : Text
    chapters : [Int]
  where
    signatory author

    key (author, isin) : (Party, Text)
    maintainer key._1

template Chapter
  with
    author : Party
    bookIsin : Text
    chapter : Int
    text : Text
    bookId : ContractId Book
  where
    signatory author 

    key (author, bookIsin, chapter) : (Party, Text, Int)
    maintainer key._1

Now the author uses the CreateAndExercise pattern to upload the library to the ledger. But within the processing of the upload, there are two options to keep track of the association between Books and Chapters: via the ledger or via a specific Map.

template UploadLibrary
  with
    author : Party
    books : [(Text, [Int])]             -- isin, chapters
    chapters : [(Text, Int, Text)]      -- isin, chapter, text
  where
    signatory author

    choice CreateViaLedger : ([ContractId Book], [ContractId Chapter]) 
      controller author
      do
        bookIds <- forA books (\(isin, chapters) -> create Book with ..)

        chapterIds <- forA chapters (\(bookIsin, chapter, text) -> do
          -- books have been created
          Some bookId <- lookupByKey @Book (author, bookIsin)
          create Chapter with ..)

        return (bookIds, chapterIds)

    choice CreateViaMap : ([ContractId Book], [ContractId Chapter]) 
      controller author
      do
        (bookMap, bookIds) <- foldlA (\(bookMap, bookIds) (isin, chapters) -> do
          bookId <- create Book with ..
          return (insert isin bookId bookMap, bookId::bookIds) )
            (mempty, []) books

        chapterIds <- forA chapters (\(bookIsin, chapter, text) -> do
          let Some bookId = lookup bookIsin bookMap
          create Chapter with ..)

        return (bookIds, chapterIds)

What do you think is the better approach ?

  • The two methods are not equivalent as the former allows one to send updates on Chapters outside of the UploadLibrary payload; those that might exist previously.
  • Is there a performance tradeoff to using the lookupByKey instead of the regular Map.lookup ?

Unless you can exploit the semantics of a Chapter in a ledger-specific way, e.g. by having some Chapters observable to different parties from their associated Books, I don’t see much value in having separate Chapter contracts here.

lookup in a Map is faster than looking up by contract key for small maps. The only time lookupByKey will have a performance advantage is when the Map is larger or modified sufficiently frequently; the whole Map is included with each transmitted copy of the contract that contains it, after all, and therefore you get knock-on effects like, if you build up a map one element at a time, one exercise at a time, you get n² ledger space usage. That doesn’t seem relevant at all for this example, though.