github.com/ipld/go-ipld-prime@v0.21.0/storage/api.go (about) 1 package storage 2 3 import ( 4 "context" 5 "io" 6 ) 7 8 // --- basics ---> 9 10 // Storage is one of the base interfaces in the storage APIs. 11 // This type is rarely seen by itself alone (and never useful to implement alone), 12 // but is included in both ReadableStorage and WritableStorage. 13 // Because it's included in both the of the other two useful base interfaces, 14 // you can define functions that work on either one of them 15 // by using this type to describe your function's parameters. 16 // 17 // Library functions that work with storage systems should take either 18 // ReadableStorage, or WritableStorage, or Storage, as a parameter, 19 // depending on whether the function deals with the reading of data, 20 // or the writing of data, or may be found on either, respectively. 21 // 22 // An implementation of Storage may also support many other methods. 23 // At the very least, it should also support one of either ReadableStorage or WritableStorage. 24 // It may support even more interfaces beyond that for additional feature detection. 25 // See the package-wide docs for more discussion of this design. 26 // 27 // The Storage interface does not include much of use in itself alone, 28 // because ReadableStorage and WritableStorage are meant to be the most used types in declarations. 29 // However, it does include the Has function, because that function is reasonable to require ubiquitously from all implementations, 30 // and it serves as a reasonable marker to make sure the Storage interface is not trivially satisfied. 31 type Storage interface { 32 Has(ctx context.Context, key string) (bool, error) 33 } 34 35 // ReadableStorage is one of the base interfaces in the storage APIs; 36 // a storage system should implement at minimum either this, or WritableStorage, 37 // depending on whether it supports reading or writing. 38 // (One type may also implement both.) 39 // 40 // ReadableStorage implementations must at minimum provide 41 // a way to ask the store whether it contains a key, 42 // and a way to ask it to return the value. 43 // 44 // Library functions that work with storage systems should take either 45 // ReadableStorage, or WritableStorage, or Storage, as a parameter, 46 // depending on whether the function deals with the reading of data, 47 // or the writing of data, or may be found on either, respectively. 48 // 49 // An implementation of ReadableStorage may also support many other methods -- 50 // for example, it may additionally match StreamingReadableStorage, or yet more interfaces. 51 // Usually, you should not need to check for this yourself; instead, 52 // you should use the storage package's functions to ask for the desired mode of interaction. 53 // Those functions will will accept any ReadableStorage as an argument, 54 // detect the additional interfaces automatically and use them if present, 55 // or, fall back to synthesizing equivalent behaviors from the basics. 56 // See the package-wide docs for more discussion of this design. 57 type ReadableStorage interface { 58 Storage 59 Get(ctx context.Context, key string) ([]byte, error) 60 } 61 62 // WritableStorage is one of the base interfaces in the storage APIs; 63 // a storage system should implement at minimum either this, or ReadableStorage, 64 // depending on whether it supports reading or writing. 65 // (One type may also implement both.) 66 // 67 // WritableStorage implementations must at minimum provide 68 // a way to ask the store whether it contains a key, 69 // and a way to put a value into storage indexed by some key. 70 // 71 // Library functions that work with storage systems should take either 72 // ReadableStorage, or WritableStorage, or Storage, as a parameter, 73 // depending on whether the function deals with the reading of data, 74 // or the writing of data, or may be found on either, respectively. 75 // 76 // An implementation of WritableStorage may also support many other methods -- 77 // for example, it may additionally match StreamingWritableStorage, or yet more interfaces. 78 // Usually, you should not need to check for this yourself; instead, 79 // you should use the storage package's functions to ask for the desired mode of interaction. 80 // Those functions will will accept any WritableStorage as an argument, 81 // detect the additional interfaces automatically and use them if present, 82 // or, fall back to synthesizing equivalent behaviors from the basics. 83 // See the package-wide docs for more discussion of this design. 84 type WritableStorage interface { 85 Storage 86 Put(ctx context.Context, key string, content []byte) error 87 } 88 89 // --- streaming ---> 90 91 type StreamingReadableStorage interface { 92 GetStream(ctx context.Context, key string) (io.ReadCloser, error) 93 } 94 95 // StreamingWritableStorage is a feature-detection interface that advertises support for streaming writes. 96 // It is normal for APIs to use WritableStorage in their exported API surface, 97 // and then internally check if that value implements StreamingWritableStorage if they wish to use streaming operations. 98 // 99 // Streaming writes can be preferable to the all-in-one style of writing of WritableStorage.Put, 100 // because with streaming writes, the high water mark for memory usage can be kept lower. 101 // On the other hand, streaming writes can incur slightly higher allocation counts, 102 // which may cause some performance overhead when handling many small writes in sequence. 103 // 104 // The PutStream function returns three parameters: an io.Writer (as you'd expect), another function, and an error. 105 // The function returned is called a "WriteCommitter". 106 // The final error value is as usual: it will contain an error value if the write could not be begun. 107 // ("WriteCommitter" will be refered to as such throughout the docs, but we don't give it a named type -- 108 // unfortunately, this is important, because we don't want to force implementers of storage systems to import this package just for a type name.) 109 // 110 // The WriteCommitter function should be called when you're done writing, 111 // at which time you give it the key you want to commit the data as. 112 // It will close and flush any streams, and commit the data to its final location under this key. 113 // (If the io.Writer is also an io.WriteCloser, it is not necessary to call Close on it, 114 // because using the WriteCommiter will do this for you.) 115 // 116 // Because these storage APIs are meant to work well for content-addressed systems, 117 // the key argument is not provided at the start of the write -- it's provided at the end. 118 // (This gives the opportunity to be computing a hash of the contents as they're written to the stream.) 119 // 120 // As a special case, giving a key of the zero string to the WriteCommiter will 121 // instead close and remove any temp files, and store nothing. 122 // An error may still be returned from the WriteCommitter if there is an error cleaning up 123 // any temporary storage buffers that were created. 124 // 125 // Continuing to write to the io.Writer after calling the WriteCommitter function will result in errors. 126 // Calling the WriteCommitter function more than once will result in errors. 127 type StreamingWritableStorage interface { 128 PutStream(ctx context.Context) (io.Writer, func(key string) error, error) 129 } 130 131 // --- other specializations ---> 132 133 // VectorWritableStorage is an API for writing several slices of bytes at once into storage. 134 // It's meant a feature-detection interface; not all storage implementations need to provide this feature. 135 // This kind of API can be useful for maximizing performance in scenarios where 136 // data is already loaded completely into memory, but scattered across several non-contiguous regions. 137 type VectorWritableStorage interface { 138 PutVec(ctx context.Context, key string, blobVec [][]byte) error 139 } 140 141 // PeekableStorage is a feature-detection interface which a storage implementation can use to advertise 142 // the ability to look at a piece of data, and return it in shared memory. 143 // The PeekableStorage.Peek method is essentially the same as ReadableStorage.Get -- 144 // but by contrast, ReadableStorage is expected to return a safe copy. 145 // PeekableStorage can be used when the caller knows they will not mutate the returned slice. 146 // 147 // An io.Closer is returned along with the byte slice. 148 // The Close method on the Closer must be called when the caller is done with the byte slice; 149 // otherwise, memory leaks may result. 150 // (Implementers of this interface may be expecting to reuse the byte slice after Close is called.) 151 // 152 // Note that Peek does not imply that the caller can use the byte slice freely; 153 // doing so may result in storage corruption or other undefined behavior. 154 type PeekableStorage interface { 155 Peek(ctx context.Context, key string) ([]byte, io.Closer, error) 156 } 157 158 // the following are all hypothetical additional future interfaces (in varying degress of speculativeness): 159 160 // FUTURE: an EnumerableStorage API, that lets you list all keys present? 161 162 // FUTURE: a cleanup API (for getting rid of tmp files that might've been left behind on rough shutdown)? 163 164 // FUTURE: a sync-forcing API? 165 166 // FUTURE: a delete API? sure. (just document carefully what its consistency model is -- i.e. basically none.) 167 // (hunch: if you do want some sort of consistency model -- consider offering a whole family of methods that have some sort of generation or sequencing number on them.) 168 169 // FUTURE: a force-overwrite API? (not useful for a content-address system. but maybe a gesture towards wider reusability is acceptable to have on offer.) 170 171 // FUTURE: a size estimation API? (unclear if we need to standardize this, but we could. an offer, anyway.) 172 173 // FUTURE: a GC API? (dubious -- doing it well probably crosses logical domains, and should not be tied down here.)