<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="61663" data-post-id="61663">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="christhekeele" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/christhekeele/120/1039_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  christhekeele
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Hey all!</p>
<p>Lest you think I’ve been sleeping, I thought I’d post an update on all the incognito progress I’ve been making.</p>
<p>If you don’t want to read through my boasting about what I’ve been up to, the most salient announcement is this:</p>
<blockquote>
<p><em><strong>This is the last announcement. There will be no more as the upcoming <code>v0.11.0</code> will be the last before <code>v1.0.0</code>.</strong></em></p>
</blockquote>
<p>To commemorate that event and garnish more notice I’ll be starting a new thread, so many thanks to everyone that’s followed my progress here to date!</p>
<p>Now that that’s out of the way, here’s a wall of text:</p>
<h2><a name="p-61663-v0100-1" class="anchor" href="#p-61663-v0100-1" aria-label="Heading link" rel="nofollow"></a>v0.10.0</h2>
<p>The observant of you <a href="https://github.com/christhekeele/mnemonix/compare/60b6432908912c3fcd136ce072366d1b61cd8b47...e2f47b5f1c63eac9aeaaf5a7603aaa312998d7c4" rel="noopener nofollow ugc">might have noticed</a> that I pushed out <code>v0.10.0</code> a few months ago. It provided support for ElasticSearch (thanks for the contribution, <a class="mention" href="/u/bdq" rel="nofollow">@BDQ</a>!) and the Elixir 1.5 <code>Map.replace</code> functions, and dropped support for Elixir 1.3 so it could use some newer language features internally (hence the minor version bump).</p>
<h2><a name="p-61663-v0110-2" class="anchor" href="#p-61663-v0110-2" aria-label="Heading link" rel="nofollow"></a>v0.11.0</h2>
<p>I didn’t make an announcement for that release, however, because it drew me into tackling the two least pleasant parts of the codebase internally and I felt more breaking changes on the horizon. This prompted <a href="https://github.com/christhekeele/mnemonix/compare/e2f47b5f1c63eac9aeaaf5a7603aaa312998d7c4...development" rel="noopener nofollow ugc">a lot of activity</a> in the last few months, touching nearly every line of code (although I’m proud to report that outside of initialization functions and formatting none of the doctests changed). This will shortly culminate in penultimate release before the <code>v1.0.0</code> milestone.</p>
<p>An overview of the more notable evolutions:</p>
<ul>
<li>
<p><strong>The signatures for store initialization will change.</strong></p>
<p>The solution I hand-rolled for this in <code>v0.9.0</code> was in fact unwittingly a sort of ad-hoc version of Elixir 1.5.0’s <code>child_spec</code>, but not similar enough to avoid being fundamentally incompatibly with it. I’m still updating the documentation to reflect this change so I’m afraid you can’t get great insight into the ramifications it by perusing it, but this will be in place for <code>v0.11.0</code>.</p>
</li>
<li>
<p><strong>All new Elixir 1.5 features that can be used, are, and support for 1.4 will be dropped.</strong></p>
<p>Aside from full <code>child_spec</code> support, all internal behaviours use <code>@impl</code> and behaviour-level <code>defoverridable</code>, and the handshake between clients and the server uses parameterized types to clean up some messy typespecs. This should make adding new features and stores to Mnemonix even easier as all sorts of tooling will warn if the internal contracts between client and server, and server and store implementation, are not quite met correctly.</p>
</li>
<li>
<p><strong>The client builder is substantially more sophisticated.</strong></p>
<p>One of the more ambitious goals I have had in mind for Mnemonix is either getting it into Phoenix, or having it be recommended by Phoenix guides, as the defacto out-of-memory cache and session store utility. Whether or not that will ever happen is completely undetermined but that hope has set the standard for the level of flexibility and rigor that I’ve been pursuing with the project, especially with regards to being used as a configurable intermediary to key/value stores in other libraries.</p>
<p>If for some reason you do not want users of your library to have to know about the Mnemonix namespace, and want to forge a custom API to interact with stores, you can simply <code>use Mnemonix.Builder</code> into a module. This module then receives all functions to interact with a Mnemonix store as if it were the <code>Mnemonix</code> module itself, with all relevant documentation. These functions can optionally be inlined into raw GenServer calls instead of simply delegating to Mnemonix functions, and you can omit their docs if you’d rather. You can cherrypick which feature-sets you want to include or get them all wholesale.</p>
<p>You can also build ‘singleton’ functions instead of normal ones—then the generated functions know in advance about a GenServer name for a store your library will be managing itself in its own supervision tree. All the functions in the module using the builder will no longer need to specify a store reference as their first parameter because they will be generated with knowledge of this GenServer name to use instead.</p>
</li>
<li>
<p><strong>The default implementations for all callbacks are trivially testable in isolation.</strong></p>
<p>Mnemonix is, at its core, two behaviours: one that client modules must implement to send commands to a store server, and one that store implementations must meet to understand and act upon instructions received by a server.</p>
<p>Each module using one of these behaviours must define some 35 functions in order to fluently speak Mnemonix, and that’s a lot to ask. Fortunately, these two behaviours can derive default implementations for all of them, save for 4 on the store implementations: an init function, and basic map put/fetch/delete implementations. In order to DRY up the source code, make contributing to Mnemonix easier, and allow custom Mnemonix clients to override behaviour, when you use a Mnemonix Feature Behaviour or Mnemonix Store Behaviour into a module, these default implementations are provided.</p>
<p>People who have ran into me on this forum or on the mailing list in certain conversations might know I’ve long been agitated by the current idiomatic solution for creating behaviours that furnish using modules with default implementations of their own callbacks. This is because the current default approach—throwing a bunch of <code>def</code>s inside a <code>__using__</code> macro—does not scale well or offer great testability for these default implementations. This is fine for most use-cases but spectacularly insufficient for Mnemonix’s very particular architecture.</p>
<p>I’ve created a very particular piece of metaprogramming to solve this pain point that also powers all of the features in the builder tool: <code>Mnemonix.Behaviour</code>. Both feature behaviours and store behaviours use this module; and every function they define that matches the name of a callback also defined in the module is transferred to any module that uses the behaviour as a default implementation. Crucially, this means that these default implementations concretely <em><strong>exist</strong></em> on their host modules instead of living in an un-compiled <code>__using__</code> void, so they can visibly document their behaviour, emit compiler and dialyzer warnings, and be easily tested outside of where their behaviours are used.</p>
<p>The current implementation is Mnemonix specific but I may get around to extracting it into a more general form and publishing it somewhere.</p>
</li>
</ul>
<p>I will release as <code>v0.11.0</code> once the lingering planned tasks are finished, which I anticipate to be sometime soon after the turn of the year.</p>
<h2><a name="p-61663-v100-3" class="anchor" href="#p-61663-v100-3" aria-label="Heading link" rel="nofollow"></a>v1.0.0</h2>
<p>The two main obstacles remaining before takeoff are, as they always have been, performing more extensive testing outside of moduledocs and replacing derived default store function implementations with optimized store-specific versions where possible.</p>
<p>Aside from some extra documentation and tooling tweaks, those are also finally the only obstacles left, and in fact things have been feature-complete and nearly API-stable for quite some time. I’ve talked about those two big hurdles a bit here and on the issue tracker, so there’s really not much more to say. However the preceeding <code>v0.11.0</code> can be treated as an RC since those remaining issues in no way affect any Mnemonix API or behaviour, and just polish them up.</p>
<p>However, I’ve put together a lot of supporting tooling with an eye to the pending Big One in mind that have taken a lot of effort that I thought I might mention before closing:</p>
<ul>
<li>A scriptable credo style guide is pretty much complete</li>
<li>Dialyzer is happy with the entire project</li>
<li>Both of those as well as code coverage and documentation coverage are executed in the build process</li>
<li>The source code is fully formatted with the upcoming formatter</li>
<li>The dev environment is instrumented with cortex to run tests as files change (and hopefully all of the above, soon)</li>
<li>There is a wiki on the repo to host user-contributed content and guides should they emerge</li>
<li>There is a project on the repo for triaging inbound issues and pull requests</li>
</ul>
<p>I look forward to seeing you in another thread! Maybe I can contrive to drop the Big One on Valentine’s Day to help express the gratitude I have for you all. <img src="https://forum.elixirforum.com/images/emoji/apple/wave.png?v=15" title=":wave:" class="emoji" alt=":wave:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="61663" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/mnemonix-a-generic-key-value-store-adapter-library-v0-8-1/2902/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-61663" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="61663"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="61712" data-post-id="61712">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="OvermindDL1" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/OvermindDL1/120/2677_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  OvermindDL1
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I’m curious as I did not come across it quickly, how easy is it to use multiple back-ends at once?  Do they have their own interfaces so you have to know which to access?  Is it possible to multiplex multiple ones via the same interface and shard them, mirror them, etc.. and etc…?</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="61712" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/mnemonix-a-generic-key-value-store-adapter-library-v0-8-1/2902/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-61712" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="61712"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="61751" data-post-id="61751">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="christhekeele" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/christhekeele/120/1039_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  christhekeele
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I’m glad you ask, actually, as this will be the subject of <a href="https://github.com/christhekeele/mnemonix/milestone/3" rel="noopener nofollow ugc">the planned <code>v1.1.0</code> release</a>. I’m really excited to work on it but I’m using <code>v1.0.0</code> as motivation to tend to some of the more tedious touch-ups first.</p>
<p>I intend to offer a series of stores under the  <code>Mnemonix.Stores.Meta</code> namespace that do exactly this.</p>
<p>Before then there will be <a href="https://github.com/christhekeele/mnemonix/wiki/Making-a-new-store" rel="noopener nofollow ugc">a guide</a> to implementing custom stores that should help you figure this out yourself. In the meantime I just threw together a proof-of-concept pass-through proxy here: <a href="https://gist.github.com/christhekeele/1c0dc69d23931c69e1d50fc2fe9ca842" class="inline-onebox" rel="noopener nofollow ugc">An example meta store for Mnemonix · GitHub</a></p>
<p>There are 4 core functions you must implement in a store, one of which is a setup phase to convert arbitrary store-specific arguments into arbitrary store-specific state to hold in the server process. The remaining mandatory 3 put/fetch/delete functions can make use of this state however they need to; the other 30-some functions are automatically derived from them. This makes it pretty easy to set up custom stores with whatever initial state you need and bootstrap a full feature-set with minimal effort.</p>
<p>Some notes:</p>
<ul>
<li><code>use Store.Behaviour</code> is what allows everything else to be derived</li>
<li><code>use Store.Translator.Raw</code> prevents any serialization/deserialization of the keys and values, which allows the dependent stores to handle that logic however they need to</li>
<li><code>def setup(opts)</code> returns <code>{:ok, state}</code>; that state can be accessed later under the <code>Mnemonix.Store</code> struct’s <code>state</code> field</li>
<li><code>def put(store, key, value)</code> and <code>def delete(store, key)</code> just run the operation against the frontend and backend stores sequentially, propagating errors</li>
<li><code>def fetch(store, key)</code> returns a found value from the frontend, then tries the backend. If found in the backend it also stores it in the frontend; otherwise it returns <code>:error</code></li>
</ul>
<p>There are some nuances that this implementation completely sidesteps:</p>
<ul>
<li>A lot of the other derived map operations in terms of the 3 core ones will exhibit a lot of unnecessary churn and could be overridden to minimize reads and writes; that’s exactly the sort of optimization I need to make for the existing stores before v1.</li>
<li>The <code>Mnemonix</code> client API should not be used inside store implementations themselves since warnings and errors intended to be propagated back to the calling client function will instead occur inside the server process of the metastore.</li>
<li>The arguments to <code>start_link</code> should be <code>{store_module, setup_opts}</code> (instead of pids of active stores) so that hot code updates and supervision recovery works better; this mandates the metastores also acting as a supervisor on some level so they’ll need smarter start_links.</li>
<li>I suspect that the naive serialization/deserialization strategies for single stores may not be sufficient for map functions that accept transformation functions when applying them across composed stores; that API may either have to be improved or those functions overridden in metastores with a smarter treatment.</li>
</ul>
<p>Outside of these known complications, though, this example passthrough seems to be working as advertised even for complicated derived commands like, say, <code>get_and_update</code>. The point of <code>v1.1.0</code> will be to create metastores that address these subtleties fully.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="61751" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/mnemonix-a-generic-key-value-store-adapter-library-v0-8-1/2902/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-61751" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="61751"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>