<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/interchange/</id>
  <link href="https://www.endpointdev.com/blog/tags/interchange/"/>
  <link href="https://www.endpointdev.com/blog/tags/interchange/" rel="self"/>
  <updated>2026-06-16T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Interchange Redis Sessions</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2026/06/interchange-redis-sessions/"/>
      <id>https://www.endpointdev.com/blog/2026/06/interchange-redis-sessions/</id>
      <published>2026-06-16T00:00:00+00:00</published>
      <author>
        <name>Mark Johnson</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2026/06/interchange-redis-sessions/cover.webp&#34; alt=&#34;An eastern bluebird with a rust-orange breast and blue-gray head perched on a bare branch, set against a soft blue sky with white clouds.&#34;&gt;&lt;br&gt;
Photo by Josh Ausborne, 2022.&lt;/p&gt;
&lt;p&gt;Interchange&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; now supports storing user sessions in Redis&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, an in-memory data store, as a core feature of Interchange 5.12. Sessions are where Interchange keeps client state that makes a catalog operate as a continuous experience&amp;ndash;the shopping cart, form values, login status, and other data that persist from one request to the next. How and where that state is stored have a direct impact on a busy catalog&amp;rsquo;s performance, and Redis gives developers a fast, purpose-built option that sits alongside the storage backends Interchange has always offered.&lt;/p&gt;
&lt;h3 id=&#34;session-storage-in-interchange&#34;&gt;Session storage in Interchange&lt;/h3&gt;
&lt;p&gt;By default, Interchange writes each session to a file on disk, and it has long been able to store sessions in a relational database instead by way of &lt;code&gt;Vend::SessionDB&lt;/code&gt;, configured with the &lt;code&gt;SessionType DBI&lt;/code&gt; and &lt;code&gt;SessionDB&lt;/code&gt; directives. The database approach is the natural choice once a catalog is served by more than one Interchange server, since every server needs to read and write the same pool of sessions.&lt;/p&gt;
&lt;p&gt;Relational databases certainly can function in this capacity, but a high-traffic session table is not where they shine. Every page view reads, writes, and locks a session row, and that constant churn competes with database performance for the application. Redis is built precisely for this kind of small, hot, frequently updated key/value data, and that makes it a compelling choice for session storage.&lt;/p&gt;
&lt;h3 id=&#34;using-redis-for-sessions&#34;&gt;Using Redis for sessions&lt;/h3&gt;
&lt;p&gt;Enabling Redis sessions takes two directives in your catalog&amp;rsquo;s &lt;code&gt;catalog.cfg&lt;/code&gt;. Set &lt;code&gt;SessionType&lt;/code&gt; to &lt;code&gt;Redis&lt;/code&gt;, and point &lt;code&gt;SessionDB&lt;/code&gt; at the address and port of your Redis server:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Variable REDIS_SERVER 127.0.0.1:6379
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SessionType Redis
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SessionDB   __REDIS_SERVER__&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That is the whole of the required configuration. Behind the scenes, the new &lt;code&gt;Vend::SessionRedis&lt;/code&gt; module ties Interchange&amp;rsquo;s session storage to Redis through the standard Redis CPAN module&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, so Redis and that module must be installed and reachable from your Interchange host.&lt;/p&gt;
&lt;p&gt;Redis sessions also work optionally with the session compression recently introduced.&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; If you set &lt;code&gt;SessionDBCompression&lt;/code&gt; to a viable compression algorithm, session data is compressed on the way into Redis and decompressed on the way out, exactly as it is for database sessions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SessionDBCompression Zstd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;more-lists-and-moredbtable&#34;&gt;More lists and MoreDBTable&lt;/h3&gt;
&lt;p&gt;Some catalogs use Interchange&amp;rsquo;s MoreDB feature to cache search-result sets (&amp;ldquo;more&amp;rdquo; lists) into the session database rather than writing to files on disk. This feature is often leveraged for the same reason that &lt;code&gt;SessionDB&lt;/code&gt; is used: to support a distributed, load-balanced server environment. &lt;code&gt;MoreDB&lt;/code&gt; relies on a real database table, and Redis&amp;ndash;having no support in Interchange&amp;rsquo;s database abstraction layer&amp;ndash;cannot provide one. To bridge that gap, there is a new &lt;code&gt;MoreDBTable&lt;/code&gt; directive that tells &lt;code&gt;MoreDB&lt;/code&gt; which relational table to use for more lists, independent of where sessions themselves live:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MoreDB yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ifdef REDIS_SERVER
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   include dbconf/__SESSIONDB_ENGINE__/sessions.__SESSIONDB_ENGINE__.__MOREDB_EXT__
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   MoreDBTable sessions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When you run Redis sessions together with &lt;code&gt;MoreDB&lt;/code&gt;, &lt;code&gt;MoreDBTable&lt;/code&gt; is required. Without Redis it remains optional: &lt;code&gt;MoreDB&lt;/code&gt; falls back to &lt;code&gt;SessionDB&lt;/code&gt; just as it always has. The strap demo ships ready-made schema definitions for MySQL, PostgreSQL, and SQLite, so the supporting table is created with the right column types.&lt;/p&gt;
&lt;h3 id=&#34;locking-and-a-friendlier-welcome-for-bots&#34;&gt;Locking and a friendlier welcome for bots&lt;/h3&gt;
&lt;p&gt;Concurrent requests for the same session have to be serialized so two clicks don&amp;rsquo;t trample each other&amp;rsquo;s data. For this release, the RDBMS session backend was reworked to lock session records with proper database transactions rather than the old approach of inserting and deleting lock rows. Redis gets its own locking scheme built on Redis&amp;rsquo;s atomic &lt;code&gt;SETNX&lt;/code&gt; (&amp;ldquo;set if not exists&amp;rdquo;) operation, exposed through a new &lt;code&gt;has_lock()&lt;/code&gt; method. The mechanism is general enough that any other storage system able to offer the same primitive&amp;ndash;Memcached, for example&amp;ndash;could adopt it.&lt;/p&gt;
&lt;p&gt;Redis sessions add one more practical refinement. Automated crawlers generate an enormous number of &amp;ldquo;hit and run&amp;rdquo; sessions that are created once and never used again, and on a database-backed catalog they pile up until something prunes them. &lt;code&gt;Vend::SessionRedis&lt;/code&gt; instead gives every brand-new session a fixed 30-minute expiration. A human user comfortably makes a second request within that window, at which point the session is promoted to the full lifetime set by your &lt;code&gt;SessionExpire&lt;/code&gt; directive. A bot that never returns simply expires on its own quickly, limiting data bloat, and Redis reclaims the memory without any housekeeping on your part.&lt;/p&gt;
&lt;h3 id=&#34;wrapping-up&#34;&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;Redis sessions give Interchange developers a fast, horizontally scalable place to keep session state, with optional compression, a robust locking model, and built-in resistance to bot-driven session bloat&amp;ndash;all configured with a couple of lines in &lt;code&gt;catalog.cfg&lt;/code&gt;. As with the rest of Interchange, the implementation lives in core and is open for the community to extend; the &lt;code&gt;has_lock()&lt;/code&gt; interface in particular invites support for additional storage backends.&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.interchangecommerce.org&#34;&gt;Interchange - https://www.interchangecommerce.org&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://redis.io/&#34;&gt;Redis - https://redis.io/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/Redis&#34;&gt;Perl&amp;rsquo;s Redis module on CPAN - https://metacpan.org/pod/Redis&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.endpointdev.com/blog/2025/04/interchange-compress-sessiondb/&#34;&gt;Interchange Compression for SessionDB&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

      </content>
    </entry>
  
    <entry>
      <title>Interchange Compression for SessionDB</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/04/interchange-compress-sessiondb/"/>
      <id>https://www.endpointdev.com/blog/2025/04/interchange-compress-sessiondb/</id>
      <published>2025-04-30T00:00:00+00:00</published>
      <author>
        <name>Mark Johnson</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2025/04/interchange-compress-sessiondb/misty-lake.webp&#34; alt=&#34;A misty lake with mountains hidden by fog and clouds is viewed through a rectangular train window, straight on.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2025. --&gt;
&lt;p&gt;New support for compression of sessions and more lists stored in a RDBMS has been added to core Interchange.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;sup&gt;,&lt;/sup&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;A new module, &lt;code&gt;Vend::Util::Compress&lt;/code&gt;, operates as a general interface for compressing and uncompressing scalar data in Interchange. The module currently offers hooks for the following compression algorithms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zstd (preferred)&lt;/li&gt;
&lt;li&gt;Gzip&lt;/li&gt;
&lt;li&gt;Brotli&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional algorithms can be easily added as needed.&lt;/p&gt;
&lt;p&gt;Each algorithm depends on the developer having installed the necessary CPAN modules for the given compression type:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IO::Compress::Zstd&lt;/code&gt;/&lt;code&gt;IO::Uncompress::UnZstd&lt;/code&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IO::Compress::Gzip&lt;/code&gt;/&lt;code&gt;IO::Uncompress::Gunzip&lt;/code&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IO::Compress::Brotli&lt;/code&gt;/&lt;code&gt;IO::Uncompress::Brotli&lt;/code&gt;&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;Vend::Util::Compress&lt;/code&gt; exports &lt;code&gt;compress()&lt;/code&gt; and &lt;code&gt;uncompress()&lt;/code&gt; on demand. In scalar context, they return the reference to the scalar holding the transformed data. In list context, they return additional measurements from the process.&lt;/p&gt;
&lt;p&gt;List &lt;code&gt;compress()&lt;/code&gt; returns an array of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$ref&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$before_size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$after_size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$elapsed_time&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$alert&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;List &lt;code&gt;uncompress()&lt;/code&gt; returns an array of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$ref&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$elapsed_time&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$alert&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any errors encountered when called in scalar context are written to the catalog error log. Errors encountered when called in list context are returned in &lt;code&gt;$alert&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;compression-performance-comparison&#34;&gt;Compression Performance Comparison&lt;/h3&gt;
&lt;p&gt;The following chart summarizes the results of running the same Interchange instance through several steps of session growth to measure performance and impact of the three supported compression algorithms. Tests ranged from a minimum session size of 68 kilobytes up to a maximum of 110 megabytes. Fields marked in green indicate the best performance of the particular parameter, across all algorithms, for each given session size. Any fields marked in yellow indicate performance measurements reaching a level of concern. Any fields marked in red indicate measurements reaching a level of unacceptable performance in any typical situation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/04/interchange-compress-sessiondb/compression_reduction_chart.png&#34; alt=&#34;Results chart for tests of compression algorithms. For each algorithm, there are 4 rows: &amp;ldquo;after&amp;rdquo;, &amp;ldquo;reduction&amp;rdquo;, &amp;ldquo;cmp time&amp;rdquo;, and &amp;ldquo;unc time&amp;rdquo;. There are 9 columns, 6.8k to 110M. Zstd shows white results for everything except compression time, where every data point is green. Its compressed size at 110M is 7.7M. Gzip shows white results for everything except compression time at 110M, where it is yellow (1.15s), with an after-compression size of 3.9M. Brotli shows all green, with a 110M after-compression size of 2.3M, but the compression time is yellow at 1.7M before, and red for all larger sizes, maxing out at 160s for 110M.&#34;&gt;&lt;/p&gt;
&lt;p&gt;For size reduction, all 3 algorithms produced a substantial decrease across the board, generally exceeding 90% and increasing the reduction factor as the session grew. All 3 algorithms also uncompressed the session on retrieval at comparable rates, well within tolerance. Where we see significant separation is in the time to compress. Zstd is the fastest across the board, but really stands out when working with larger sessions, when the impact of compression is most needed for reducing transfer latency between the Interchange and database servers.&lt;/p&gt;
&lt;h3 id=&#34;configuring-your-catalog-to-use-vendutilcompress&#34;&gt;Configuring Your Catalog to Use Vend::Util::Compress&lt;/h3&gt;
&lt;p&gt;To begin using session compression, add the new catalog configuration parameter, &lt;code&gt;SessionDBCompression&lt;/code&gt;, set to one of the aforementioned compression algorithms. Note the values for specifying compression type to use are case-sensitive. For example, to enable Zstd:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SessionDBCompression Zstd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;An empty value for the parameter bypasses compression. An invalid value logs an error and passes through the data unmodified. If the catalog is configured for file-based sessions, the setting has no impact.&lt;/p&gt;
&lt;p&gt;The same compression applies to both sessions and more lists when MoreDB is also set.&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.interchangecommerce.org/&#34;&gt;https://www.interchangecommerce.org/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/interchange/interchange/commit/35c3452bdfa1a192238196c98d12519cf153af1b&#34;&gt;https://github.com/interchange/interchange/commit/35c3452bdfa1a192238196c98d12519cf153af1b&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/IO::Compress::Zstd&#34;&gt;https://metacpan.org/pod/IO::Compress::Zstd&lt;/a&gt;, &lt;a href=&#34;https://metacpan.org/pod/IO::Uncompress::UnZstd&#34;&gt;https://metacpan.org/pod/IO::Uncompress::UnZstd&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/IO::Compress::Gzip&#34;&gt;https://metacpan.org/pod/IO::Compress::Gzip&lt;/a&gt;, &lt;a href=&#34;https://metacpan.org/pod/IO::Uncompress::Gunzip&#34;&gt;https://metacpan.org/pod/IO::Uncompress::Gunzip&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/IO::Compress::Brotli&#34;&gt;https://metacpan.org/pod/IO::Compress::Brotli&lt;/a&gt;, &lt;a href=&#34;https://metacpan.org/pod/IO::Uncompress::Brotli&#34;&gt;https://metacpan.org/pod/IO::Uncompress::Brotli&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

      </content>
    </entry>
  
    <entry>
      <title>Interchange 3rd Party Tax Support</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2023/03/interchange-vend-tax/"/>
      <id>https://www.endpointdev.com/blog/2023/03/interchange-vend-tax/</id>
      <published>2023-03-21T00:00:00+00:00</published>
      <author>
        <name>Mark Johnson</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2023/03/interchange-vend-tax/cloudy-lake.webp&#34; alt=&#34;Looking across a lake, a mountain rises to the left, splitting the dark storm clouds from their reflection with a long, thin triangle of the mountain and its reflection. On the lower side of the hill is a small town, with its light green fields standing out against the otherwise unbroken dark green pine trees. The lake&amp;rsquo;s wind-blown ripply texture is broken up on the far right by a rock and grass outcropping, along with a railing on the right side.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Juan Pablo Ventoso, 2022 --&gt;
&lt;p&gt;New 3rd-party tax API support has been added to core Interchange&lt;sup&gt;1&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;In the wake of the Wayfair court decision&lt;sup&gt;2&lt;/sup&gt;, many businesses running Interchange catalogs lack the necessary tools for full compliance. A new translation layer has been created in &lt;code&gt;Vend::Tax&lt;/code&gt; to connect the standard sales tax structures and routines that operate within Interchange, and the development of vendor-specific 3rd-party tax providers. The goal of the &lt;code&gt;Vend::Tax&lt;/code&gt; framework is to create a space to allow for development of any number of vendor-specific tax services to support tax calculation in Interchange.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Vend::Tax&lt;/code&gt; defines 3 new tags to support its function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[tax-lookup]&lt;/code&gt;: Returns calculated tax amount determined by specific 3rd-party provider. Tax may be estimated or live lookup, depending on settings. Data required to calculate tax will be provider dependent.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[load-tax-averages]&lt;/code&gt;: Requests and stores tax averages for running in estimate mode, for providers that support it. Stores estimates by default in the &lt;code&gt;tax_averages&lt;/code&gt; table. Further, allows for local tracking of jurisdictions with nexus, which can be used by live lookups to determine if a particular lookup can be skipped entirely. See &lt;code&gt;load_tax_averages&lt;/code&gt; Job and &lt;code&gt;tax_averages&lt;/code&gt; table definition in the &lt;a href=&#34;https://github.com/interchange/interchange/tree/master/dist/strap&#34;&gt;Strap demo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[send-tax-transaction]&lt;/code&gt;: Reports to provider the resulting tax transaction for a given order, for providers that support it. By default, operates based on &lt;code&gt;transactions.tax_sent&lt;/code&gt; field. &lt;code&gt;0&lt;/code&gt; or empty indicates transaction not reported. &lt;code&gt;1&lt;/code&gt; indicates transaction successfully reported. &lt;code&gt;-1&lt;/code&gt; indicates an error attempting to report transaction, requiring manual intervention. See &lt;code&gt;send_tax_transactions&lt;/code&gt; Job in Strap demo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Conceptually, &lt;code&gt;Vend::Tax&lt;/code&gt; is patterned off of &lt;code&gt;Vend::Payment&lt;/code&gt; for payment transactions. It provides the defined interface that Interchange will use, via the 3 tags shown above, but must delegate to vendor-specific implementations through tax gateway modules.&lt;/p&gt;
&lt;p&gt;Additionally, connectors have already been constructed for TaxJar&lt;sup&gt;3&lt;/sup&gt; and Avalara&lt;sup&gt;4&lt;/sup&gt; in core Interchange, so that catalogs can convert to use either of these popular services for calculating their sales tax with an upgrade and a few adjustments to their catalog configuration. Both TaxJar and Avalara provide sales tax calculation, product categorization, and reporting tools that allow merchants to comply with Wayfair and keep their focus on their business.&lt;/p&gt;
&lt;p&gt;While currently only TaxJar and Avalara are supported — see &lt;code&gt;Vend::Tax::TaxJar&lt;/code&gt; and &lt;code&gt;Vend::Tax::Avalara&lt;/code&gt; — &lt;code&gt;Vend::Tax&lt;/code&gt; was designed to facilitate and encourage the development of any 3rd-party providers through the creation of new &lt;code&gt;Vend::Tax::&amp;lt;ServiceName&amp;gt;&lt;/code&gt; modules. A review of either of the TaxJar or Avalara tax gateway modules should be instructive for the expected interface with the 3 usertags.&lt;/p&gt;
&lt;p&gt;For merchants who have, or wish to establish, relationships with other tax providers, they are free to build, or contract with consultants to build, their own provider&amp;rsquo;s vendor-specific module and use that to back the work performed within &lt;code&gt;Vend::Tax&lt;/code&gt;. Any such development projects are encouraged to be submitted for inclusion in Interchange core, giving the merchant and developer the benefit of community improvements and core inclusion on future upgrades.&lt;/p&gt;
&lt;h3 id=&#34;configuring-your-catalog-to-use-vendtax&#34;&gt;Configuring Your Catalog to Use Vend::Tax&lt;/h3&gt;
&lt;p&gt;In order to leverage &lt;code&gt;[tax-lookup]&lt;/code&gt; to back the &lt;code&gt;[salestax]&lt;/code&gt; tag, use it with the &lt;code&gt;SalesTax&lt;/code&gt; directive. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SalesTax  [tax-lookup service=TaxJar]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;service&lt;/code&gt; parameter should correspond with the segment of the namespace of the tax gateway module after &lt;code&gt;Vend::Tax&lt;/code&gt;. Here, the example enables TaxJar in module &lt;code&gt;Vend::Tax::TaxJar&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If your business requires sufficient customization, the existing tax gateway modules can be subclassed, keeping all your customizations separate from core code and safe from collisions during upgrades. An example of such an expansion to Avalara would be to create module &lt;code&gt;Vend::Tax::Avalara::MyBiz&lt;/code&gt; to subclass &lt;code&gt;Vend::Tax::Avalara&lt;/code&gt; and change the config setup to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SalesTax  [tax-lookup service=Avalara::MyBiz]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Additionally, using the Strap setup, you can easily enable TaxJar specifically by setting the following variables in variable.txt:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TAXSERVICE TaxJar
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TAXTOKEN [your TaxJar token]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_ADDRESS [business address]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_CITY [business city]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_STATE [business state postal code]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_ZIP [business zip]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_COUNTRY [business ISO two-letter country code]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For Avalara in Strap, you also would set the &lt;code&gt;TAXSERVICE&lt;/code&gt; and &lt;code&gt;NEXUS_&lt;/code&gt; variables, but then set your account API user and password:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TAXSERVICE Avalara
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AVALARA_USER [your API user]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AVALARA_PASSWORD [your API password]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_ADDRESS [business address]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_CITY [business city]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_STATE [business state postal code]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_ZIP [business zip]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NEXUS_COUNTRY [business iso two-letter country code]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, like the payment modules, in order to make a particular tax service available, it must be required from &lt;code&gt;interchange.cfg&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Require module Vend::Tax::TaxJar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://www.interchangecommerce.org/&#34;&gt;https://www.interchangecommerce.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.lbmc.com/blog/wayfair-case-sales-tax/&#34;&gt;https://www.lbmc.com/blog/wayfair-case-sales-tax/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.taxjar.com/&#34;&gt;https://www.taxjar.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.avalara.com/&#34;&gt;https://www.avalara.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

      </content>
    </entry>
  
    <entry>
      <title>Interchange rust_link connector</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2023/03/interchange-rust_link/"/>
      <id>https://www.endpointdev.com/blog/2023/03/interchange-rust_link/</id>
      <published>2023-03-06T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2023/03/interchange-rust_link/20220122_212608-sm.webp&#34; alt=&#34;Photograph of several layers of blacktop road with cracks and shadows&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Jon Jensen --&gt;
&lt;p&gt;The Interchange ecommerce system recently reached 27 years old, measuring from the first public release of its predecessor MiniVend by its creator Mike Heins. It is still hard at work in quite a few ecommerce sites, serving pages, accepting and processing orders, managing warehouse logistics, and more. That is quite an accomplishment in the software world!&lt;/p&gt;
&lt;h3 id=&#34;the-interchange-serverdaemon&#34;&gt;The Interchange server/​daemon&lt;/h3&gt;
&lt;p&gt;Interchange is written in Perl and runs on Linux and other Unix-like servers as a daemon (persistent background process) that listens for requests. Why does it need to run as a daemon?&lt;/p&gt;
&lt;p&gt;Like many applications, Interchange starts with a relatively slow initialization procedure that takes a couple of seconds to compile code, load modules, read configuration, connect to databases, and validate everything. We want it to do that only once when the daemon is started, and not for each user request, so it can make quick responses.&lt;/p&gt;
&lt;h3 id=&#34;web-server-connector&#34;&gt;Web server connector&lt;/h3&gt;
&lt;p&gt;General-purpose web servers normally sit in front of an application server, optimized to make speedy encrypted TLS sessions for HTTPS, control access to resources, log requests, redirect old URLs, route traffic to various applications, and directly serve static files such as HTML, CSS, JavaScript, and images.&lt;/p&gt;
&lt;p&gt;How does the web server talk to the Interchange application server? Several protocols to do that would later become common, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FastCGI (1996)&lt;/li&gt;
&lt;li&gt;Apache JServ Protocol or AJP (1997) for the Java world&lt;/li&gt;
&lt;li&gt;HTTP protocol reverse proxying for Ruby on Rails and eventually most other platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But in 1995 when Interchange was created, there was no widely-used standard, so it used its own custom protocol implemented in a small CGI program.&lt;/p&gt;
&lt;p&gt;The data flows through these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A user makes a request with a web browser to a web server, commonly Apache &lt;code&gt;httpd&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The web server runs a new instance of the link program and passes request information to it.&lt;/li&gt;
&lt;li&gt;The link program reformats the request into Interchange link protocol.&lt;/li&gt;
&lt;li&gt;It then sends the request to the Interchange server.&lt;/li&gt;
&lt;li&gt;It receives back a response from Interchange.&lt;/li&gt;
&lt;li&gt;It sends the response to the web server.&lt;/li&gt;
&lt;li&gt;It exits.&lt;/li&gt;
&lt;li&gt;The web server sends the response back to the user&amp;rsquo;s browser.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;interchange-link-protocol&#34;&gt;Interchange link protocol&lt;/h3&gt;
&lt;p&gt;The Interchange link protocol uses plain text divided into lines to pass its information.&lt;/p&gt;
&lt;p&gt;There are 2 sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;arg&lt;/code&gt; for command-line arguments, now rarely used, but long ago where search text appeared before the advent of HTML forms&lt;/li&gt;
&lt;li&gt;&lt;code&gt;env&lt;/code&gt; for environment variables.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After each section label is the number of items in that section.&lt;/p&gt;
&lt;p&gt;Then that many items are given on one line each, beginning with the number of bytes in the value, followed by a space and then the value.&lt;/p&gt;
&lt;p&gt;Finally an &lt;code&gt;end&lt;/code&gt; section concludes the preliminary values, and then the HTTP response body, if any, follows unmodified after the &lt;code&gt;entity&lt;/code&gt; label and its length in bytes.&lt;/p&gt;
&lt;p&gt;Here is an example request with an empty request body, as the CGI link program passes it to Interchange:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;arg 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;env 29
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;52 CONTEXT_DOCUMENT_ROOT=/opt/homebrew/var/www/cgi-bin/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;24 CONTEXT_PREFIX=/cgi-bin/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;35 DOCUMENT_ROOT=/opt/homebrew/var/www
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;25 GATEWAY_INTERFACE=CGI/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;97 HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;34 HTTP_ACCEPT_ENCODING=gzip, deflate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;35 HTTP_ACCEPT_LANGUAGE=en-US,en;q=0.5
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;26 HTTP_CONNECTION=keep-alive
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;10 HTTP_DNT=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;23 HTTP_HOST=ruka.lan:8080
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;32 HTTP_UPGRADE_INSECURE_REQUESTS=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;86 HTTP_USER_AGENT=Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;50 MINIVEND_SOCKET=/Users/user/interchange/etc/socket
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;18 PATH=/bin:/usr/bin
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;13 QUERY_STRING=
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;25 REMOTE_ADDR=192.168.1.215
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;17 REMOTE_PORT=36534
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;18 REQUEST_METHOD=GET
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;19 REQUEST_SCHEME=http
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;27 REQUEST_URI=/cgi-bin/strap1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;52 SCRIPT_FILENAME=/opt/homebrew/var/www/cgi-bin/strap1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;27 SCRIPT_NAME=/cgi-bin/strap1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;24 SERVER_ADDR=192.168.1.59
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;28 SERVER_ADMIN=you@example.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;20 SERVER_NAME=ruka.lan
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;16 SERVER_PORT=8080
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;24 SERVER_PROTOCOL=HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;17 SERVER_SIGNATURE=
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;36 SERVER_SOFTWARE=Apache/2.4.56 (Unix)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the CGI specification has HTTP request headers passed in environment variables beginning with &lt;code&gt;HTTP_&lt;/code&gt; followed by the header name with &lt;code&gt;-&lt;/code&gt; replaced by &lt;code&gt;_&lt;/code&gt; and all letters capitalized.&lt;/p&gt;
&lt;p&gt;The other environment variables give information about the request and context from the web server.&lt;/p&gt;
&lt;p&gt;The environment variables appear in no particular order, but I sorted them here for readability.&lt;/p&gt;
&lt;h3 id=&#34;interchange-link-implementations&#34;&gt;Interchange link implementations&lt;/h3&gt;
&lt;p&gt;Several implementations of this link functionality exist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two sibling programs named &lt;code&gt;vlink&lt;/code&gt; (for UNIX sockets in the filesystem) and &lt;code&gt;tlink&lt;/code&gt; (for TCP sockets on the IP network), written in C and compiled, giving fast startup time and run speed&lt;/li&gt;
&lt;li&gt;Perl equivalents named &lt;code&gt;vlink.pl&lt;/code&gt; and &lt;code&gt;tlink.pl&lt;/code&gt; which are easy to customize and need no compilation, but are comparatively slow, adding ~100 ms or more to each request&lt;/li&gt;
&lt;li&gt;A Perl module &lt;code&gt;Interchange::Link&lt;/code&gt; that runs precompiled in the Apache &lt;code&gt;httpd&lt;/code&gt; module &lt;code&gt;mod_perl&lt;/code&gt; and supports both UNIX and TCP sockets&lt;/li&gt;
&lt;li&gt;A compiled C module &lt;code&gt;mod_interchange&lt;/code&gt; that also runs in the Apache &lt;code&gt;httpd&lt;/code&gt; server and supports both UNIX and TCP sockets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can even run Interchange entirely inside Apache &lt;code&gt;mod_perl&lt;/code&gt; such that no connector link program is needed. But that has been only rarely used in production because it tightly couples Interchange with the web server and means it runs the application as the web server operating system user, which is not ideal for security.&lt;/p&gt;
&lt;p&gt;After all these years, the original &lt;code&gt;vlink&lt;/code&gt; compiled C program remains the default and popular way to connect a web server via CGI to Interchange. It is simple to use, fairly fast, and works with any web server that supports the CGI protocol.&lt;/p&gt;
&lt;h3 id=&#34;enter-rust&#34;&gt;Enter Rust&lt;/h3&gt;
&lt;p&gt;The C &lt;code&gt;vlink&lt;/code&gt; program isn&amp;rsquo;t broken, so we don&amp;rsquo;t really need to fix it. But occasionally we want to customize some part of the &lt;code&gt;vlink&lt;/code&gt; behavior. Doing so in C can take more work than in more modern languages, depending on the programmer and what changes are needed.&lt;/p&gt;
&lt;p&gt;But more importantly, programming in C introduces risk of bugs that could affect every request. C code is fertile ground for unsafe memory handling bugs that often lead to security problems.&lt;/p&gt;
&lt;p&gt;Tim Hutt reviewed all the documented bugs of the &lt;code&gt;curl&lt;/code&gt; project and found that over half were memory errors, and he noted that Google found that 70% of Chrome’s high-severity security bugs are memory errors. That is despite some of the world&amp;rsquo;s extremely talented and careful programmers working on those projects!&lt;/p&gt;
&lt;p&gt;These are the kinds of errors that the programming language Rust eliminates by disallowing such code at compilation time. Plenty of other new languages have cropped up in the past decade or so, vying to replace C and/or C++ and be safe from memory errors while retaining speed and low-level machine access by producing compiled native machine code: Go, Swift, Zig, Nim, and V are a few competitors, alongside Rust.&lt;/p&gt;
&lt;p&gt;Rust is one of the more complex ones because you have to think about memory management instead of relying on a garbage collector. But that also can make it one of the highest performers using the least amount of memory. Rust gets plenty of press, much of it enthusiastic, so you can go read about it on your own if you&amp;rsquo;d like to learn more!&lt;/p&gt;
&lt;p&gt;So two years ago I decided to port the &lt;code&gt;vlink&lt;/code&gt; program to Rust so I could see how much work it took, how pleasant it was, and how fast and memory-efficient the resulting code was. Porting the link program to one of those other new languages would also be fun, but I haven&amp;rsquo;t done that yet!&lt;/p&gt;
&lt;h3 id=&#34;rust_link&#34;&gt;rust_link&lt;/h3&gt;
&lt;p&gt;I called this program &lt;code&gt;rust_link&lt;/code&gt;. Initially I made it as simple as possible, just a proof of concept: It consisted of one long function, had a hardcoded path to the Interchange UNIX socket file, and it died ungracefully on any error.&lt;/p&gt;
&lt;p&gt;It came together fairly quickly and was fun to write, so I made it configurable via environment variables that can be set in the web server, added error handling, and broke it into smaller functions like the original &lt;code&gt;vlink&lt;/code&gt; has.&lt;/p&gt;
&lt;p&gt;Then I came across the Rust crate (Rust&amp;rsquo;s term for a module) called &lt;code&gt;multisock&lt;/code&gt;, which wraps up UNIX and INET socket support in a single package. Using that made it straightforward to merge &lt;code&gt;vlink&lt;/code&gt; and &lt;code&gt;tlink&lt;/code&gt; into a single program.&lt;/p&gt;
&lt;p&gt;Most of the other link programs retry connecting to the Interchange socket every 1 or 2 seconds for something like 30 seconds. This is to handle gracefully a brief outage while the Interchange server is being restarted. However, if instead the Interchange server was overloaded or entirely down, this could lead to a big backlog of CGI link processes. I implemented this retry functionality too, and decided to have it check twice as often, but only for a maximum of 10 seconds. That still gives plenty of time for the link program to retry during a restart of the Interchange daemon, but lets it fail faster when the daemon isn&amp;rsquo;t likely to come back.&lt;/p&gt;
&lt;p&gt;There is one HTML document in the program, for an HTTP 503 error response body. Putting that HTML text directly in the code would mean using pesky &lt;code&gt;\&lt;/code&gt; string escapes for quotation marks, newlines, etc., and then most text editors are unable to highlight the HTML syntax for readability. Since Rust has the easy &lt;code&gt;include_str!&lt;/code&gt; macro, I put the HTTP 503 error response HTML body into a separate &lt;code&gt;503.html&lt;/code&gt; file for simpler editing.&lt;/p&gt;
&lt;h3 id=&#34;rust-impressions&#34;&gt;Rust impressions&lt;/h3&gt;
&lt;p&gt;I was still new to Rust as I wrote this program, so there were various learning bumps along the way. It was a fun project. &lt;em&gt;The Rust Programming Language&lt;/em&gt; book is excellent, and it is free online and also available in print.&lt;/p&gt;
&lt;p&gt;One developer I read describing the experience of writing Rust code said that it can involve a lot of wrestling with the compiler to make it happy with variable lifetimes (borrowing and ownership), types, traits, and other details, but that once a program compiled, it usually worked without flaw.&lt;/p&gt;
&lt;p&gt;I found that to most often be the case for me with &lt;code&gt;rust_link&lt;/code&gt;. I had to correctly separate different sorts of data with distinct types for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;byte arrays for the data sent from and to the web server over stdin and stdout streams&lt;/li&gt;
&lt;li&gt;&amp;ldquo;OS strings&amp;rdquo; for environment variable names &amp;amp; values and file paths &amp;amp; names&lt;/li&gt;
&lt;li&gt;UTF-8 strings in source code, messages and string-link type conversions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This required more thoughtful consideration and work up front, but eliminated the possibility of things later going wrong because of sloppiness about exactly what kind of data we have in each case.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;rustc&lt;/code&gt; compiler is very helpful in pointing the way with detailed error messages noted in relevant excerpts of the problem code, and gives references to further reading in the documentation.&lt;/p&gt;
&lt;h3 id=&#34;testing-in-rust&#34;&gt;Testing in Rust&lt;/h3&gt;
&lt;p&gt;The Rust ecosystem and documentation aim to make testing a natural part of development, and as easy as possible. So I wrote unit tests to exercise some of my helper functions for representing numbers as strings for the wire protocol, parsing environment variable values into sockets, and getting errors where expected.&lt;/p&gt;
&lt;p&gt;I also wrote a multi-threaded integration test to exercise sending the simplest response over a socket and verifying what is received on the other side. This is still very basic and could be fleshed out with more arguments, a response body with and without a CGI-specified &lt;code&gt;CONTENT_LENGTH&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;Being able to run some tests is nice when first compiling the program for another platform, as I did here:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ cargo test
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   Compiling rust_link v1.0.0 (/Users/user/repos/interchange/dist/src/rust_link)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Finished test [unoptimized + debuginfo] target(s) in 1.48s
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     Running unittests src/main.rs (target/debug/deps/rust_link-d008c1110239f0a1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;running 16 tests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_entity_content_length_bad - should panic ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_socket_addr_from_env_host_ipv4 ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_entity_content_length_zero ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_entity_content_length_empty ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_socket_addr_from_env_host_ipv4_bad_host - should panic ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_entity_content_length_missing ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_socket_addr_from_env_host_ipv4_bad_port - should panic ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_socket_addr_from_env_host_ipv4_alt_port ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::number_to_text_bytes_0 ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::number_to_text_bytes_1 ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::number_to_text_bytes_259 ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_socket_addr_from_env_host_ipv6 - should panic ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_socket_addr_from_env_host_name ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_socket_addr_from_env_missing_vars - should panic ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::get_socket_addr_from_env_unix ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test tests::send_arguments_output ... ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.58s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;performance-and-resources&#34;&gt;Performance and resources&lt;/h3&gt;
&lt;p&gt;I developed &lt;code&gt;rust_link&lt;/code&gt; on Linux (x86_64) and also built and tested it on macOS (ARM 64-bit) and Linux on Raspberry Pi (ARM 32-bit), where it also worked fine, as expected.&lt;/p&gt;
&lt;p&gt;The speed of this new &lt;code&gt;rust_link&lt;/code&gt; compares well to the classic &lt;code&gt;vlink&lt;/code&gt;. Startup time is the main concern as there is no significant processing done once the program is running. Measurements are essentially the same, within the margin of error.&lt;/p&gt;
&lt;p&gt;Some of the link program implementations use a limited buffer size (such as 16 KiB for &lt;code&gt;vlink&lt;/code&gt; and &lt;code&gt;tlink&lt;/code&gt;) to spool data from Interchange back to the web server. It is common practice to send large response bodies as files directly from a web server rather than from dynamic Interchange responses to limit the time an Interchange worker process is occupied. Because of that, and since most servers have far more memory available now than in years past, I let &lt;code&gt;rust_link&lt;/code&gt; read the entire response body into memory rather than using a fixed buffer size.&lt;/p&gt;
&lt;p&gt;Because Rust crates are compiled into the &lt;code&gt;rust_link&lt;/code&gt; executable, not linked as shared libraries as is typical with C programs, the executable program size is bigger for &lt;code&gt;rust_link&lt;/code&gt; than &lt;code&gt;vlink&lt;/code&gt;: 9× more on disk under ARM64 macOS! But that increase is from a small base of ~34 KiB to ~312 KiB. (This is for executables stripped of debugging symbols.) On modern operating systems with copy-on-write behavior, most of that memory is shared so it is a one-time cost, not multiplying per running CGI program instance.&lt;/p&gt;
&lt;h3 id=&#34;publication&#34;&gt;Publication&lt;/h3&gt;
&lt;p&gt;Since it works as well as the other implementations, I figured it was worth finally adding some documentation and releasing it as open source software alongside the other Interchange link programs, available for anyone who wants to avoid the risks of C to deploy with or without customization.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://github.com/interchange/interchange/tree/master/dist/src/rust_link&#34;&gt;code is on GitHub ready to use&lt;/a&gt; and the &lt;code&gt;rust_link/README.md&lt;/code&gt; explains how to build and install it.&lt;/p&gt;
&lt;h3 id=&#34;reference&#34;&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.interchangecommerce.org/&#34;&gt;Interchange ecommerce project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Common_Gateway_Interface&#34;&gt;Common Gateway Interface (CGI)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.rfc-editor.org/rfc/rfc3875.html#section-4.4&#34;&gt;RFC 3875 section 4.4&lt;/a&gt; which defines the ancient and now almost completely defunct convention for CGI script command line arguments to contain an &amp;ldquo;indexed&amp;rdquo; HTTP query&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/FastCGI&#34;&gt;FastCGI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Apache_JServ_Protocol&#34;&gt;Apache JServ Protocol (AJP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Reverse_proxy&#34;&gt;HTTP reverse proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.rust-lang.org/&#34;&gt;Rust programming language&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.timhutt.co.uk/curl-vulnerabilities-rust/&#34;&gt;Would Rust secure cURL?&lt;/a&gt; by Tim Hutt&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://crates.io/crates/multisock&#34;&gt;Rust crate &lt;code&gt;multisock&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://oylenshpeegul.gitlab.io/from-perl-to-rust/&#34;&gt;From Perl to Rust&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Perl Web Frameworks</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/04/perl-web-frameworks/"/>
      <id>https://www.endpointdev.com/blog/2022/04/perl-web-frameworks/</id>
      <published>2022-04-19T00:00:00+00:00</published>
      <author>
        <name>Marco Pessotto</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/04/perl-web-frameworks/nature-wing-black-and-white-web-line-insect-1244977.webp&#34; alt=&#34;Spider webs and spiders&#34;&gt;&lt;/p&gt;
&lt;!-- Photo: https://pxhere.com/en/photo/1244977 CC0 Public Domain --&gt;
&lt;h3 id=&#34;cgi&#34;&gt;CGI&lt;/h3&gt;
&lt;p&gt;When I started programming, back in the day, CGI (the Common Gateway Interface) was still widely
used. Usually the Apache webserver would just execute a script or a
binary with some environment variables set and serve whatever the
executable sent to the standard output, while keeping the standard
error in the logs.&lt;/p&gt;
&lt;p&gt;This simple and straightforward mechanism can still
be used for small programs, but larger applications usually want to
save the start-up time and live longer than just a single request.&lt;/p&gt;
&lt;p&gt;At that time Perl was used far more often than now, and it had (and still
has) the &lt;a href=&#34;https://metacpan.org/pod/CGI&#34;&gt;CGI.pm&lt;/a&gt; module to help the
programmer to get the job done.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;utf8&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;strict&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;warnings&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;CGI&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$q&lt;/span&gt; = &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;CGI&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$q&lt;/span&gt;-&amp;gt;header;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$name&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Marco&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$q&lt;/span&gt;-&amp;gt;p(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Hello $name&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And it will output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./cgi.pl
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: text/html; charset=ISO-8859-1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;p&amp;gt;Hello Marco&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here the script mixes logic and formatting and the encoding it
produces by default tells us that this comes from another age. But
if you want something which is seldom used and gets executed on demand
without persisting in the machine’s memory, this is still an option.&lt;/p&gt;
&lt;p&gt;Please note that there are frameworks which can work in CGI mode, so
there is no reason to use CGI.pm, beside having to maintain legacy
programs.&lt;/p&gt;
&lt;h3 id=&#34;mojolicious&#34;&gt;Mojolicious&lt;/h3&gt;
&lt;p&gt;Fast-forward to 2022.&lt;/p&gt;
&lt;p&gt;Nowadays Perl is just another language among dozens of them. But it
still gets the job done and lets you write nice, maintainable code like
any other modern language.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://mojolicious.org/&#34;&gt;Mojolicious&lt;/a&gt; is currently the top choice if
you want to do web development in Perl. It is an amazing framework,
with a large and active community, and appears to have collected the
best concepts that other web frameworks from other languages have to
offer.&lt;/p&gt;
&lt;p&gt;Let’s hack an app in a couple of minutes in a single file, like during
the CGI days:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;utf8&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;strict&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;warnings&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojolicious::Lite&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;get &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;) = &lt;span style=&#34;color:#369&#34;&gt;@_&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;stash(name =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Marco&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;render(template =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;index&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;app&lt;/span&gt;-&amp;gt;start;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;__DATA__&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;@@&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;index&lt;/span&gt;.html.ep
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello &amp;lt;&lt;span style=&#34;color:#369&#34;&gt;%&lt;/span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;$&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;%&lt;/span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here the structure is a bit different.&lt;/p&gt;
&lt;p&gt;First, there’s a Domain Specific Language (DSL) to give you some sugar.
This is the “Lite” version, while in a well-structured Mojolicious app one
prefers to write class methods. We declare that the root (&lt;code&gt;/&lt;/code&gt;) URL path of the
application is going to execute some code. It populates the “stash”
with some variables, and finally renders a template which can access
the stashed variables. If you execute the script, you get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./mojo.pl cgi 2&amp;gt; /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Status: 200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Length: 12
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: Fri, 08 Apr 2022 12:33:52 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: text/html;charset=UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello Marco&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The logging to the standard error stream is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:33:52.92508] [163133] [debug] [82ae3iV2] GET &amp;#34;/&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:33:52.92532] [163133] [debug] [82ae3iV2] Routing to a callback
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:33:52.92565] [163133] [debug] [82ae3iV2] Rendering template &amp;#34;index.html.ep&amp;#34; from DATA section
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:33:52.92610] [163133] [debug] [82ae3iV2] 200 OK (0.001021s, 979.432/s)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is basically what a modern framework is supposed to do.&lt;/p&gt;
&lt;p&gt;The nice thing in this example is that we created a single-file
prototype and launched it as a CGI. But we can also launch it as
daemon and visit the given address with a browser, which is how you
should normally deploy it, usually behind a reverse proxy like
&lt;a href=&#34;https://nginx.org/en/&#34;&gt;nginx&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./mojo.pl daemon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:42.01827] [163409] [info] Listening at &amp;#34;http://*:3000&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Web application available at http://127.0.0.1:3000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:48.53687] [163409] [debug] [CwM6zoUQ] GET &amp;#34;/&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:48.53715] [163409] [debug] [CwM6zoUQ] Routing to a callback
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:48.53752] [163409] [debug] [CwM6zoUQ] Rendering template &amp;#34;index.html.ep&amp;#34; from DATA section
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:48.53808] [163409] [debug] [CwM6zoUQ] 200 OK (0.001209s, 827.130/s)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you want you can even launch it with HTTPS as well (please note the
syntax to pass the certificates).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./mojo.pl daemon -l &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;https://[::]:8080?cert=./ssl/fullchain.pem&amp;amp;key=./ssl/privkey.pem&amp;#39;&lt;/span&gt; -m production&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For a small application listening on a high port this is already
enough and the whole deployment problem goes away.&lt;/p&gt;
&lt;p&gt;Speaking about deployment, Mojolicious has basically no dependencies
other than the core modules and comes with a lot of goodies, for example
a &lt;a href=&#34;https://docs.mojolicious.org/Mojo/UserAgent&#34;&gt;non blocking user-agent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Recently a legacy application needed to make some API calls. To speed up
the process, we wanted to make the requests in parallel. And here’s
the gist of the code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MyApp::Async&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# ... more modules here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::UserAgent&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::Promise&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# .... other methods here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;example&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$email&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;test@example.com&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$ua&lt;/span&gt; = &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::UserAgent&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;foreach&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$list&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;get_lists) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$promise&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$ua&lt;/span&gt;-&amp;gt;post_p(&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;_url(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/api/v2/endpoint/$list-&amp;gt;{code}&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                  json =&amp;gt; { email =&amp;gt; &lt;span style=&#34;color:#369&#34;&gt;$email&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          -&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;then&lt;/span&gt;(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$tx&lt;/span&gt; = &lt;span style=&#34;color:#038&#34;&gt;shift&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$res&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$tx&lt;/span&gt;-&amp;gt;result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$res&lt;/span&gt;-&amp;gt;code =~ &lt;span style=&#34;color:#080;background-color:#fff0ff&#34;&gt;m/^2/&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                         &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;_update_db(&lt;span style=&#34;color:#369&#34;&gt;$data&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                         &lt;span style=&#34;color:#038&#34;&gt;die&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$tx&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;req&lt;/span&gt;-&amp;gt;url . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; . &lt;span style=&#34;color:#369&#34;&gt;$res&lt;/span&gt;-&amp;gt;code;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#038&#34;&gt;push&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;@promises&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$promise&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$return&lt;/span&gt; = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::Promise&lt;/span&gt;-&amp;gt;all(&lt;span style=&#34;color:#369&#34;&gt;@promises&lt;/span&gt;)-&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;then&lt;/span&gt;(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; { &lt;span style=&#34;color:#369&#34;&gt;$return&lt;/span&gt; = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt; }, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; { &lt;span style=&#34;color:#369&#34;&gt;$return&lt;/span&gt; = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;})-&amp;gt;&lt;span style=&#34;color:#038&#34;&gt;wait&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So a bunch of requests are run in parallel and then synced before
returning. Does it remind you of JavaScript? Of course. A lot of
common paradigms taken from other languages and frameworks were
implemented here, and you can find the best of them in this nice
package.&lt;/p&gt;
&lt;p&gt;But the point here is that it doesn’t need dozens of new modules
installed or upgraded. It’s just a single module in pure Perl that you
can even install in your application tree. This is a huge advantage if
you’re dealing with a legacy application which uses an old Perl tree
and you want to play safe.&lt;/p&gt;
&lt;p&gt;So, if you’re starting from scratch, go with Mojolicious. It lets you
prototype fast and doesn’t let you down later.&lt;/p&gt;
&lt;p&gt;However, starting from scratch is not always an option. Actually, it’s
a rare opportunity. There’s a whole world of legacy applications and
they generate real money every day. It’s simply not possible or even
desirable to throw away something that works for something that would
do the same thing but in a “cooler” way. In ten years, the way we’re
coding will look old anyway.&lt;/p&gt;
&lt;h3 id=&#34;interchange&#34;&gt;Interchange&lt;/h3&gt;
&lt;p&gt;Wait. Isn’t &lt;a href=&#34;https://www.interchangecommerce.org&#34;&gt;Interchange&lt;/a&gt; an old
e-commerce framework? Yes, it’s not exactly a generic web framework,
on the contrary, it’s a specialized one, but it’s still a framework and
you can still do things in a maintainable fashion. The key is using
the so-called action maps:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ActionMap jump &amp;lt;&amp;lt;EOR
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sub {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # get the path parameters
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    my ($action, @args) = split(/\//, shift);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # get the query/body parameters
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    my $param = $CGI-&amp;gt;{param};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # redirect to another page
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $Tag-&amp;gt;deliver({ location =&amp;gt; $final });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # or serve JSON
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $Tag-&amp;gt;deliver({ type =&amp;gt; &amp;#39;application/json&amp;#39;, body =&amp;gt; $json_string });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # or serve a file
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $Tag-&amp;gt;deliver({ type =&amp;gt; &amp;#39;text/plain&amp;#39;, body =&amp;gt; $bigfile });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # or populate the &amp;#34;stash&amp;#34; and serve a template page
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $Tag-&amp;gt;tmp(stash_variable =&amp;gt; &amp;#34;Marco&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $CGI-&amp;gt;{mv_nextpage} = &amp;#34;test.html&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EOR&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;pages/test.html&lt;/code&gt; you would put this template:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Hello [scratch stash_variable]&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, I can’t show you a simple script which demonstrates this and
you’ll have to take my word for it since we can’t go through the
installation process here for a demo.&lt;/p&gt;
&lt;p&gt;Interchange is old, and it shows its
years, but it is actively maintained. It lacks many of Mojo’s
goodies, &lt;em&gt;but&lt;/em&gt; you can still do things in a reasonable way.&lt;/p&gt;
&lt;p&gt;In the example the code will execute
when a path starting with &lt;code&gt;/jump/&lt;/code&gt; is requested. The whole path is
passed to the routine, so you can split at &lt;code&gt;/&lt;/code&gt;, apply your logic, and
finally either set &lt;code&gt;$CGI-&amp;gt;{mv_nextpage}&lt;/code&gt; to a file in the &lt;code&gt;pages&lt;/code&gt;
directory or output the response body directly with &lt;code&gt;deliver&lt;/code&gt;. This
way you can easily build, as a classical example, an API.&lt;/p&gt;
&lt;p&gt;It’s a bit of a poor man’s
&lt;a href=&#34;https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller&#34;&gt;MVC&lt;/a&gt;
but it works. That’s basically the core of what a framework like
&lt;a href=&#34;https://metacpan.org/pod/Dancer2&#34;&gt;Dancer&lt;/a&gt; does.&lt;/p&gt;
&lt;h3 id=&#34;dancer-1--2&#34;&gt;Dancer (1 &amp;amp; 2)&lt;/h3&gt;
&lt;p&gt;Dancer is basically Ruby’s
&lt;a href=&#34;https://github.com/sinatra/sinatra&#34;&gt;Sinatra&lt;/a&gt; ported to Perl. As
already mentioned, ideas developed in other languages and frameworks
are often ported to Perl, and this is no exception.&lt;/p&gt;
&lt;p&gt;Let’s see it in an action:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;strict&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;warnings&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Dancer2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;get &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$name&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Marco&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Hello $name\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;start;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Start the script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Dancer2 v0.400000 server 22969 listening on http://0.0.0.0:3000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Try it with &lt;code&gt;curl&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -D - http://0.0.0.0:3000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HTTP/1.0 200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: Mon, 11 Apr 2022 07:22:18 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Server: Perl Dancer2 0.400000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Server: Perl Dancer2 0.400000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Length: 12
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: text/html; charset=UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello Marco&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If in the script you say &lt;code&gt;use Dancer;&lt;/code&gt; instead of &lt;code&gt;use Dancer2&lt;/code&gt;, you get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -D - http://0.0.0.0:3000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HTTP/1.0 200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Server: Perl Dancer 1.3513
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Length: 12
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: text/html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;X-Powered-By: Perl Dancer 1.3513
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello Marco&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dancer’s core doesn’t do much more than routing. And you’ll also
notice that the syntax is very similar to Mojolicious::Lite. So to
get something done you need to start installing plugins which will
provide the needed glue to interact with a database, work with your
template system of choice, and more.&lt;/p&gt;
&lt;p&gt;Today you would wonder why you should use Dancer and not Mojolicious,
but when Dancer was at the peak of its popularity the games were still
open. There were plenty of plugins being written and published on CPAN.&lt;/p&gt;
&lt;p&gt;Around 2013 Dancer’s development team decided to rewrite it to make
it better. The problem was that plugins and templates needed to be
adapted as well. I’m under the impression that the energy got divided
and the momentum was lost. Now there are two codebases and two
plugin namespaces which do basically the same thing, because for
the end user there is not much difference.&lt;/p&gt;
&lt;h3 id=&#34;catalyst&#34;&gt;Catalyst&lt;/h3&gt;
&lt;p&gt;So what was attracting people to Dancer? When Dancer came out, Perl
had a great MVC framework, which is still around,
&lt;a href=&#34;http://catalyst.perl.org/&#34;&gt;Catalyst&lt;/a&gt;. (And note that the main
Mojolicious developer was on the Catalyst team.)&lt;/p&gt;
&lt;p&gt;Now, the problem is that to get started with Catalyst, even if it has
plenty of documentation, you need to be already acquainted with a lot
of concepts and technologies. For example, the
&lt;a href=&#34;https://metacpan.org/dist/Catalyst-Manual/view/lib/Catalyst/Manual/Tutorial/03_MoreCatalystBasics.pod&#34;&gt;tutorial&lt;/a&gt;
starts to talk about
&lt;a href=&#34;http://www.template-toolkit.org/&#34;&gt;Template Toolkit&lt;/a&gt; and
the &lt;a href=&#34;https://metacpan.org/pod/DBIx::Class&#34;&gt;DBIx::Class&lt;/a&gt; ORM very early.&lt;/p&gt;
&lt;p&gt;These two modules are great and powerful and they deserve to be
studied, but for someone new to modern web development, or even to
Perl, it feels (and actually is) overwhelming.&lt;/p&gt;
&lt;p&gt;So, why would you choose Catalyst today? Catalyst has the stability
which Mojo, at least at the beginning, lacked, while
backward compatibility is a priority for Catalyst. The other way to look
at this is that Catalyst doesn’t see much current
&lt;a href=&#34;https://metacpan.org/dist/Catalyst-Runtime/changes&#34;&gt;development&lt;/a&gt;, but
someone could see this as a feature.&lt;/p&gt;
&lt;p&gt;Even if Catalyst predates all the hyper-modern features that Mojo has,
it’s still a modern framework, and a good one. I can’t show you a self
contained script (you need a tree of files), but I’d like to show you
what makes it very nice and powerful:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MyApp::Controller::Root&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Moose&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;namespace::autoclean&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;BEGIN&lt;/span&gt; { extends &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Catalyst::Controller&amp;#39;&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# start the chain with /foo/XX&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;foo&lt;/span&gt; :Chained(&amp;#39;/&amp;#39;) CaptureArgs(1) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$arg&lt;/span&gt;) = &lt;span style=&#34;color:#369&#34;&gt;@_&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;stash(name =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;$arg&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# /foo/XX/bar/YY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;bar&lt;/span&gt; :Chained(&amp;#39;foo&amp;#39;) Args(1) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$arg&lt;/span&gt;) = &lt;span style=&#34;color:#369&#34;&gt;@_&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;detach(&lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;view(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;JSON&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# /foo/XX/another/YY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;another&lt;/span&gt; :Chained(&amp;#39;foo&amp;#39;) Args(1) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$arg&lt;/span&gt;) = &lt;span style=&#34;color:#369&#34;&gt;@_&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;detach(&lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;view(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;HTML&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So, if you hit &lt;code&gt;/foo/marco/bar/test&lt;/code&gt; the second path fragment will be
processed by the first method (&lt;code&gt;CaptureArgs(1)&lt;/code&gt;) and saved in the
stash. Then the second &lt;code&gt;bar&lt;/code&gt; method will be chained to it and the
&lt;code&gt;name&lt;/code&gt; will be available in the stash. The last method will be hit
with &lt;code&gt;/foo/marco/another/test2&lt;/code&gt;. (Incidentally, please note that
Mojolicious has
&lt;a href=&#34;https://docs.mojolicious.org/Mojolicious/Guides/Routing#Nested-routes&#34;&gt;nested&lt;/a&gt;
routes as well.)&lt;/p&gt;
&lt;p&gt;Now, I think it’s clear that in this way you can build deep hierarchies
of paths with reusable components. This works really great with the
DBIx::Class ORM, where you can
chain queries as well. As you can imagine, this is far from a simple
setup. On the contrary, this is an advanced setup for people who
already know their way around web frameworks.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;So, to sum up this excursion in the amazing land of Perl web
frameworks: If you build something from scratch, go with Mojolicious.
It’s your best bet. If nothing else, it’s super-easy to install, with
basically no dependencies.&lt;/p&gt;
&lt;p&gt;However, there’s no need to make a religion
out of it. Rewriting code without a clear gain is a waste of time and
money. A good developer should still be able to write maintainable
code with the existing tools.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Our Vue Storefront “Proof of Concept” Experience</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/08/vue-storefront-magento-integration/"/>
      <id>https://www.endpointdev.com/blog/2020/08/vue-storefront-magento-integration/</id>
      <published>2020-08-10T00:00:00+00:00</published>
      <author>
        <name>Kürşat Kutlu Aydemir</name>
      </author>
      <content type="html">
        &lt;p&gt;Recently we experimented internally with integrating &lt;a href=&#34;https://www.vuestorefront.io/&#34;&gt;Vue Storefront&lt;/a&gt; and &lt;a href=&#34;https://business.adobe.com/products/magento/open-source.html&#34;&gt;Magento&lt;/a&gt; 2.3. Vue Storefront is an open source Progressive Web App (PWA) that aims to work with many ecommerce platforms.&lt;/p&gt;
&lt;p&gt;What initially piqued our interest was the possibility of integrating Vue Storefront with the venerable ecommerce back-end platform &lt;a href=&#34;https://www.interchangecommerce.org/i/dev&#34;&gt;Interchange&lt;/a&gt;, which many of our clients use. Vue Storefront’s promise of ease of integration with any ecommerce backend made us curious to see whether it would make a good modern front-end for Interchange.&lt;/p&gt;
&lt;p&gt;Since Vue Storefront seems to be most commonly used with Magento, we decided to start our experiment with a standard Vue Storefront/​Magento 2.3 proof-of-concept integration.&lt;/p&gt;
&lt;h3 id=&#34;poc-of-vue-storefrontmagento-23&#34;&gt;PoC of Vue Storefront/​Magento 2.3&lt;/h3&gt;
&lt;p&gt;OK, to be honest, at the beginning we blindly expected that Vue Storefront would be a copy/​paste front-end template solution that would fairly easily be made to work with its standard integration to a Magento backend. Sadly, this was not the case for us.&lt;/p&gt;
&lt;p&gt;Before beginning our journey here, to summarize the Vue Storefront integration with Magento let’s have a look at this diagram to see what components are included:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/08/vue-storefront-magento-integration/GitHub-Architecture-VS.png&#34; alt=&#34;VS Architecture&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 1&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At first, we wanted to see how all these components can be installed and run on a single server with modest resources.&lt;/p&gt;
&lt;p&gt;I walked through the &lt;a href=&#34;https://docs.vuestorefront.io/guide/&#34;&gt;Vue Storefront documentation&lt;/a&gt; and a few &lt;a href=&#34;https://medium.com/the-vue-storefront-journal/proof-of-concept-how-to-run-pwa-for-magento-in-a-week-c0fa04fadd3d&#34;&gt;blog posts&lt;/a&gt; to figure out Vue Storefront and Magento integration.&lt;/p&gt;
&lt;h3 id=&#34;preparing-the-environment&#34;&gt;Preparing the Environment&lt;/h3&gt;
&lt;p&gt;I downloaded and installed the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OS: CentOS Linux 7 (64-bit)&lt;/li&gt;
&lt;li&gt;PHP 7.2.26&lt;/li&gt;
&lt;li&gt;Magento 2.3 with sample data&lt;/li&gt;
&lt;li&gt;Elasticsearch 5.6&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Vue Storefront API&lt;/li&gt;
&lt;li&gt;Vue Storefront&lt;/li&gt;
&lt;li&gt;mage2vuestorefront bridge&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Installation of those components is fairly easy. We started our virtual server with 4 GB of memory, which we thought should be plenty for a toy setup.&lt;/p&gt;
&lt;h3 id=&#34;indexing-elasticsearch-with-mage2vuestorefront&#34;&gt;Indexing Elasticsearch with mage2vuestorefront&lt;/h3&gt;
&lt;p&gt;PHP and Magento 2.3 had a series of memory usage issues. While running the mage2vuestorefront indexer Magento used most of the memory and caused Elasticsearch to go down each time I tried to index Elasticsearch. Then I configured PHP and Apache httpd server to use a very modest amount memory and made Magento 2.3 work on this server without making it crash due to unavailable memory but the performance became a nightmare on the PHP &amp;amp; Magento 2.3 side. mage2vuestorefront ran without issue, but very slowly.&lt;/p&gt;
&lt;p&gt;I am not very familiar with the Magento 2 API, but while indexing Elasticsearch with mage2vuestorefront it makes several individual API calls. Especially if you have several products in Magento 2, mage2vuestorefront calls the Magento API for almost all products. Due to PHP and Magento’s high memory usage available memory might become a problem during indexing.&lt;/p&gt;
&lt;p&gt;The Vue Storefront developers &lt;a href=&#34;https://docs.vuestorefront.io/guide/cookbook/elastic.html#_0-introduction&#34;&gt;explain&lt;/a&gt; a big reason why they chose Elasticsearch as one of the essential components of this integration:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Vue Storefront&lt;/em&gt; defines itself backend-agnostic PWA e-commerce solution where &lt;em&gt;Vue Storefront&lt;/em&gt; is a storefront as the name dictates, and &lt;em&gt;Elasticsearch&lt;/em&gt; works as a datastore for &lt;em&gt;catalog&lt;/em&gt; and its sibling data such as &lt;em&gt;taxrule&lt;/em&gt;, &lt;em&gt;products&lt;/em&gt; and so on.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;I am unsure if the backend-agnostic approach even really applies to the suggested implementation of Vue Storefront with Magento 2, or if does apply with significant headaches. I’ll detail my concerns about this later.&lt;/p&gt;
&lt;h3 id=&#34;viewing-the-vue-storefront-application&#34;&gt;Viewing the Vue Storefront Application&lt;/h3&gt;
&lt;p&gt;The responsive and offline supportive design of Vue Storefront is elegant. It uses Elasticsearch via vue-storefront-api as its indexing search engine to provide a seamless user experience. Although the overall integration and the produced data structure looks complex there are only a few catalog data stores that need to be indexed on Elasticsearch. Also, since this is an ecommerce integration, you can expect that only products and product-related data are going to be available in such an integration.&lt;/p&gt;
&lt;p&gt;In the Vue Storefront and Vue Storefront API app home directories (&lt;code&gt;vue-storefront/&lt;/code&gt; and &lt;code&gt;vue-storefront-api/&lt;/code&gt;) I ran &lt;code&gt;npm start&lt;/code&gt; to start the Node.js apps, then navigated to the Vue Storefront homepage to view its default template:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/08/vue-storefront-magento-integration/storefront_temphost_net.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 2&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There is a hamburger menu in the top-left which opens the categories menu of Vue Storefront:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/08/vue-storefront-magento-integration/storefronttemphostnet_cats_menu.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 3&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As I mentioned, the data being viewed is relatively simple according to the count of the catalog types (essentially Products, Categories, Attributes, and Tax Rules) which are already categorized and these are a bunch of specific sets of data.&lt;/p&gt;
&lt;p&gt;By searching through the categories Women &amp;gt; Tops &amp;gt; Jackets, the search output list is shown by Vue Storefront:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/08/vue-storefront-magento-integration/storefronttemphostnet_cats_search.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 4&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;designing-a-new-approach-for-interchange&#34;&gt;Designing a New Approach for Interchange&lt;/h3&gt;
&lt;p&gt;When exploring the features and components of Vue Storefront we discussed how we can adapt Interchange to use Vue Storefront. We ended up with two main options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Keep the same approach of Vue Storefront’s current integration with Magento and other ecommerce backends using Elasticsearch and vue-storefront-api as the backend gateways&lt;/li&gt;
&lt;li&gt;Remove the Elasticsearch and vue-storefront-api dependencies and create a new API in Interchange producing similar API outputs to the ones vue-storefront-api &amp;amp; Elasticsearch are creating. I also found a similar approach where they integrated SAP Hybris with Vue Storefront. I thought that this way would fit our intention.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;inspired-by-a-similar-approach-of-vue-storefront--sap-commerce&#34;&gt;Inspired By A Similar Approach of Vue Storefront + SAP Commerce&lt;/h4&gt;
&lt;p&gt;&lt;a href=&#34;https://hybrismart.com/2019/02/13/vue-storefront-sap-commerce-open-source-pwa-storefront-integration-demo/&#34;&gt;Vue Storefront + SAP Commerce: Open-source PWA Storefront Integration (+DEMO)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This article discusses several approaches, including &lt;a href=&#34;https://github.com/DivanteLtd/storefront-integration-sdk&#34;&gt;Vue Storefront developers’ boilerplate integration&lt;/a&gt; and their custom solutions. In the following table they show different options and comments on their resolutions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/08/vue-storefront-magento-integration/t1-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 5&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As you can see in Figure 5, they decided to go with the 4th option as their most suitable and least problematic solution. Here is their statement of why:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;After some analysis, we have come to believe that using custom middleware only for a bunch of simple requests is an additional burden and thick layer of complexity. We decided to get rid of it as well.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;So, option #4 turned out to be the best choice. For the option #4, we need to parse Magento and Elasticsearch APIs and generate the compatible responses.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here is how their solution looks according to option #4:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/08/vue-storefront-magento-integration/p2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 6&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Using this approach, they decided to remove the Elasticsearch and Vue Storefront API components from the Vue Storefront boilerplate integration and developed their SAP Commerce custom Vue Storefront API to produce responses similar to the responses taken from Elasticsearch through Vue Storefront API to pretend that Vue Storefront is taking its responses from Elasticsearch API + Magento API.&lt;/p&gt;
&lt;p&gt;We decided to discard Elasticsearch when integrating Interchange with Vue Storefront for simplicity, and to cut down on the cost such additional search solutions create.&lt;/p&gt;
&lt;p&gt;After our discussions, I was inspired by this SAP Commerce + Vue Storefront integration demo. We finally decided to move forward with creating an Interchange API to produce the similar responses to those from the Elasticsearch &amp;amp; Magento APIs, so we could make the Interchange API pretend to be the Elasticsearch API + Magento API.&lt;/p&gt;
&lt;p&gt;We need to mention the Magento 2 API, or at least the Magento 2 data structure, because the Vue Storefront API and Vue Storefront boilerplate code is heavily designed to be taking ecommerce data (Products, Categories, Product Attributes, Tax Rules etc.) almost identical to the Magento API’s provided data.&lt;/p&gt;
&lt;p&gt;So our Interchange + Vue Storefront integration looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/08/vue-storefront-magento-integration/Vsinterchange_arch.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 7&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Compare Figure 1 with Figure 7 to see the difference between this approach and Vue Storefront’s suggested solution.&lt;/p&gt;
&lt;h4 id=&#34;interchange-api-prototype-design&#34;&gt;Interchange API Prototype Design&lt;/h4&gt;
&lt;p&gt;In order to achieve the API prototyping for our Interchange/​Vue Storefront integration, I cloned vue-storefront and vue-storefront-api, which I had previously integrated with Magento 2.3, into a new user, keeping all settings the same. This cloned Vue Storefront application started initially with the same configuration as vue-storefront-api and Elasticsearch were not excluded yet. So I decided to replace the vue-storefront-api endpoints that vue-storefront is calling with endpoints from the new Interchange API.&lt;/p&gt;
&lt;p&gt;As the default SAP Commerce/​Vue Storefront integration suggests, I walked through the outputs of search results in “product”, “category”, and “attributes”. Product data JSON output has some universal attributes like name or title, description, or price, but it also has some custom product attributes adapted from the Magento 2 API (specifically, the data structure of Magento is applied).&lt;/p&gt;
&lt;p&gt;At this point I started questioning if Vue Storefront is not really platform agnostic, or if it is, but with some headaches. The next section will detail these headaches but to summarize, adapting the data structure of new ecommerce backends to Vue Storefront is a real pain. It forces you to use its data structure since that’s a part of core development, and they rely mainly on Elasticsearch, and I guess because of that the developers of Vue Storefront believe that Elasticsearch is an essential part of this “agnostic” approach. Refer to &lt;a href=&#34;https://docs.vuestorefront.io/guide/data/elastic-queries.html#getting-data-from-elasticsearch&#34;&gt;Getting data from Elasticsearch&lt;/a&gt; for custom implementations using the &lt;a href=&#34;https://github.com/vuestorefront/vue-storefront/tree/master/core/lib&#34;&gt;core Elasticsearch lib&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, I moved on and produced product and product categories outputs that Vue Storefront expects as the search results at the homepage and the search box output results.&lt;/p&gt;
&lt;p&gt;Documentation is another pain. This is discussed further in the next section, but briefly, it’s hard to say that you could clearly pick the data structure that all kind of output results from the documentation. You need to dig into the TypeScript code, and the exact search output result is what is actually being expected.&lt;/p&gt;
&lt;p&gt;One conflict was that the identifiers are required to be numeric, not strings. If your ecommerce data structure doesn’t provide its identifiers as numbers you’ll need to alter and add a new numeric unique identifier along with the existing one, which is what I did.&lt;/p&gt;
&lt;p&gt;Product attributes also need to be adapted to Vue Storefront (really to Magento’s data structure). The attribute names, attribute lists, attributes’ assigned values, and categories attached to the products and configurable child products all come along with the product list on a search output. If your ecommerce data is not structured similarly to Magento’s data structure, you need to dig and restructure even for minimal changes like product attributes and configurable child products. For example, if your data structure doesn’t allow configurable child products then you can probably only provide each option (such as product color or size options) as a new product, which will be a real pain.&lt;/p&gt;
&lt;p&gt;Most of Vue Storefront’s app endpoints are defined in &lt;code&gt;config/default.json&lt;/code&gt; which is the default config on a new install. On the other hand, the ecommerce catalog data (which is indexed in Elasticsearch) is available through the Elasticsearch implementation which is by default defined in &lt;code&gt;lib/search/adapter/api/searchAdapter.ts&lt;/code&gt;. In my case I needed to alter this implementation, which is generating the Elasticsearch (actually the Vue Storefront API) endpoints dynamically by looking up the related catalog index (product, category, attributes, taxes, etc.), and change the Elasticsearch definition in the Vue Storefront config file to the new Interchange API, adapted to emulate the Vue Storefront API and Elasticsearch:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;elasticsearch&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;httpAuth&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;host&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/api/catalog&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;index&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;vue_storefront_catalog&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I needed to set the Interchange API URL to the host property and not necessarily set an API-defining URL sub-path name to the index property above. This way our API would pretend to be the Elasticsearch endpoint as long as it can generate the JSON output that Vue Storefront expects.&lt;/p&gt;
&lt;p&gt;These configuration changes generally satisfied Vue Storefront’s expectations.&lt;/p&gt;
&lt;h3 id=&#34;common-and-exceptional-issues&#34;&gt;Common and Exceptional Issues&lt;/h3&gt;
&lt;p&gt;Many concerns and challenges are discussed in &lt;a href=&#34;https://hybrismart.com/2019/02/13/vue-storefront-sap-commerce-open-source-pwa-storefront-integration-demo/#h.4jhhd7ba1u4i&#34;&gt;this article on Vue Storefront + SAP Commerce integration&lt;/a&gt; which I mentioned earlier.&lt;/p&gt;
&lt;p&gt;A review of some issues I detailed earlier:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Required numeric unique IDs for all catalogs (products, categories, etc.)&lt;/li&gt;
&lt;li&gt;Poor documentation&lt;/li&gt;
&lt;li&gt;Catalog data structure is managed on the Vue Storefront side, heavily relying on its own data types and models. Your backend may need to be adapted to meet Vue Storefront’s expectations, even if you are using Elasticsearch.&lt;/li&gt;
&lt;li&gt;A structure which is constantly being reorganized, creating a big maintenance concern.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Despite my concerns, I thought we could still implement our Interchange API with a front-end framework, but I hadn’t asked the question: Is it reusable enough to mantain? Each of our clients would have different UI and UX expectations. One UI template model could never be enough, which would mean continuous front-end template design and enough maintenance to match the operational cost of existing Interchange front-end solutions. Adapting existing data to Vue Storefront’s new data model would bring more costs along with it.&lt;/p&gt;
&lt;p&gt;While Vue Storefront’s offline working model is well designed, for the purpose of integrating with a non-Magento backend, this is overshadowed by its many issues. You would be better off looking elsewhere for a more modular, truly platform-agnostic, easier-maintained, component- and data model-independent front-end.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Ecommerce sales tax primer</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/01/ecommerce-sales-tax-primer/"/>
      <id>https://www.endpointdev.com/blog/2020/01/ecommerce-sales-tax-primer/</id>
      <published>2020-01-13T00:00:00+00:00</published>
      <author>
        <name>Elizabeth Garrett Christensen</name>
      </author>
      <content type="html">
        &lt;p&gt;Co-authored by &lt;a href=&#34;/team/greg-hanson/&#34;&gt;Greg Hanson&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/01/ecommerce-sales-tax-primer/image-0.jpg&#34; alt=&#34;Roman tax burning&#34;&gt;
&lt;a href=&#34;https://www.flickr.com/photos/internetarchivebookimages/14591980537&#34;&gt;Source image&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tax collection is one of the topics &lt;em&gt;du jour&lt;/em&gt; for those of us in the ecommerce industry. Since state and local authorities are now able to levy taxes on ecommerce goods, taxation for online stores has become quite complicated. The purpose of this post is to give you some next steps and ideas on implementation if you’re new to the topic and need to know how to get started on tax collection for your ecommerce business.&lt;/p&gt;
&lt;p&gt;Current ecommerce sales tax policy stems from the 2018 U.S. Supreme Court decision South Dakota v. Wayfair, Inc. Since that decision, favoring South Dakota, 30 states have enacted legislation to require ecommerce stores to pay sales tax if they fit the definition of having an ‘economic nexus’, that is, they do enough business in the state to be worth taxing.&lt;/p&gt;
&lt;h3 id=&#34;talk-to-your-tax-accountant&#34;&gt;Talk to your Tax Accountant&lt;/h3&gt;
&lt;p&gt;So the first and most important note is to get your own legal counsel in regards to your taxes. There are many rules and things are changing every month with local and state authorities, so you’ll need reliable counsel on the topic.&lt;/p&gt;
&lt;p&gt;If you’re looking for someone to help, make sure this person has:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Knowledge about product variants. For example, clothing may not be taxed in some areas.&lt;/li&gt;
&lt;li&gt;Familiarity with tax policy in the entire country, and not just one local retail area.&lt;/li&gt;
&lt;li&gt;The ability to help you determine in which states you have a tax ‘nexus’ and need to pay sales tax. For many small businesses, you might only do enough business to pay sales tax in your home state and a few large ones like California or New York.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;research-software&#34;&gt;Research Software&lt;/h3&gt;
&lt;p&gt;Luckily for anyone starting to collect sales tax, there are some very good SaaS (software as a service) systems out there to make tax collection, reporting, and compliance easy. These software systems typically integrate with your ecommerce store by providing the store with the correct tax amount for the sale and collecting information for you on your reports and filing information to get the taxes paid to the correct authorities. Some systems might even file things for you.&lt;/p&gt;
&lt;p&gt;After you’ve worked with your tax accountant on what you need, start looking at the available software. The two main companies End Point has worked with are &lt;a href=&#34;https://www.taxjar.com/&#34;&gt;TaxJar&lt;/a&gt; and &lt;a href=&#34;https://www.avalara.com/us/en/index.html&#34;&gt;Avalara&lt;/a&gt;, though there are a number of other vendors in the market.&lt;/p&gt;
&lt;img src=&#34;/blog/2020/01/ecommerce-sales-tax-primer/image-1.svg&#34; alt=&#34;TaxJar&#34; width=&#34;48%&#34; style=&#34;padding-right: 2%&#34; /&gt;
&lt;img src=&#34;/blog/2020/01/ecommerce-sales-tax-primer/image-2.svg&#34; alt=&#34;Avalara&#34; width=&#34;48%&#34; style=&#34;padding-left: 2%&#34; /&gt;
&lt;h4 id=&#34;why-use-an-automated-tax-solution&#34;&gt;Why use an automated tax solution?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;They automatically update tax rates as they change with local and state regulation.&lt;/li&gt;
&lt;li&gt;They can integrate into your checkout processes via API or plugin to automatically calculate the correct tax for the right location and product.&lt;/li&gt;
&lt;li&gt;They can have product-specific taxes, for things that might not be taxed like food and clothing in certain states.&lt;/li&gt;
&lt;li&gt;They give you end-of-year reports and help with your payments.&lt;/li&gt;
&lt;li&gt;You can customize settings, in case there are some states where you have a ‘nexus’ (are subject to sales tax) and other places where sales tax payments are not required.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;what-to-look-for-in-automated-tax-software&#34;&gt;What to look for in automated tax software:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Choose a reputable company with large brand presence. You don’t want to do anything experimental and unreliable here.&lt;/li&gt;
&lt;li&gt;Pick something with documentation you can understand. You don’t want working with your tax software to be a pain, and you might need to refer to their documentation when configuring it or changing things in the future. Make sure that company speaks your language and you can easily get answers to your questions.&lt;/li&gt;
&lt;li&gt;This company should be able to guide you through all aspects of tax collections, filing and payment. You will legally be liable for taxes on all sales in states where you have nexus, &lt;em&gt;whether you collect the taxes or not&lt;/em&gt;! So make sure the company you select can work with you not only to set up the system, but on an ongoing basis providing support in filing reports and making payments.&lt;/li&gt;
&lt;li&gt;Integration with your platform is a key component of what you choose. Both Avalara and TaxJar have existing plugins for sites running on WordPress, Shopify, BigCommerce, Magento, and others. Keep in mind though that the integration might be different depending on your platform. If you’re on a custom platform, talk to your development team about integrations; they can read the docs and give you an estimate and recommendation for ease of implementation (that’s where we come in for many clients). For Interchange stores, we have integration code for both TaxJar and Avilara to leverage.&lt;/li&gt;
&lt;li&gt;Consider how your inventory or ERP system might be affected. Many of our ecommerce clients sell in-house or over the phone. Consider how your other systems might need to tie into this new tax system.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;implementation&#34;&gt;Implementation&lt;/h3&gt;
&lt;p&gt;So you’ve done the hard part, right? You sorted out what states you need to be compliant with and picked a software solution. Now all you need to do is get it working. This is really where your software consultant, such as us at End Point, would come in and get you to the finish line. The steps to implementation are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set up your account and pay for your tax solution software.&lt;/li&gt;
&lt;li&gt;Work with the tax solution provider to set up any required bank accounts or payment channels.&lt;/li&gt;
&lt;li&gt;Configure your settings.&lt;/li&gt;
&lt;li&gt;Share the API key and information with your developer.&lt;/li&gt;
&lt;li&gt;Test the implementation. I recommend you do this with several orders in different scenarios: products that do and don’t have sales tax, locations that do and don’t have tax, locations that tax shipping, etc. Test all the variants you know about.&lt;/li&gt;
&lt;li&gt;Go live with your tax solution on your site.&lt;/li&gt;
&lt;li&gt;Make sure to check back for your reports and filings for later in the year.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Need help picking a system or looking at implementation? &lt;a href=&#34;/contact/&#34;&gt;Call us today&lt;/a&gt; and we can help.&lt;/p&gt;
&lt;h3 id=&#34;other-resources&#34;&gt;Other resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.salestaxinstitute.com/sales_tax_faqs/wayfair-economic-nexus&#34;&gt;https://www.salestaxinstitute.com/sales_tax_faqs/wayfair-economic-nexus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.thebalancesmb.com/how-to-collect-report-and-pay-state-sales-taxes-399043&#34;&gt;https://www.thebalancesmb.com/how-to-collect-report-and-pay-state-sales-taxes-399043&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.bigcommerce.com/blog/ecommerce-sales-tax/&#34;&gt;https://www.bigcommerce.com/blog/ecommerce-sales-tax/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.taxjar.com/guides/intro-to-sales-tax/&#34;&gt;https://www.taxjar.com/guides/intro-to-sales-tax/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>National Braille Press Releases Redesigned Website</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/08/national-braille-press-redesigned-website/"/>
      <id>https://www.endpointdev.com/blog/2019/08/national-braille-press-redesigned-website/</id>
      <published>2019-08-28T00:00:00+00:00</published>
      <author>
        <name>Elizabeth Garrett Christensen</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2019/08/national-braille-press-redesigned-website/image-0.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;End Point congratulates National Braille Press on the recent release of their &lt;a href=&#34;https://www.nbp.org/&#34;&gt;redesigned website&lt;/a&gt;, launched earlier this week! National Braille Press (NBP) supports the blind and low-vision community with events, initiatives, resources, children’s programs, and a bookstore. NBP has been working with End Point since 2002 and hosts and maintains their website, which is based on the Interchange open-source ecommerce platform.&lt;/p&gt;
&lt;h3 id=&#34;project-phases&#34;&gt;Project Phases&lt;/h3&gt;
&lt;p&gt;End Point started working with NBP on this redesign project in late 2017. We began with a user experience (UX) review &amp;amp; content strategy project which resulted in design directives for our in-house team. Design and plans were completed in mid-2018 and implementation began then. End Point built a new, more powerful search feature and developed a number of ‘cornerstone’ pages while NBP staff worked tirelessly in-house to convert the rest of the pages on their website to the new designs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/08/national-braille-press-redesigned-website/image-1.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;leveraging-nbp-in-house-resources&#34;&gt;Leveraging NBP In-House Resources&lt;/h3&gt;
&lt;p&gt;NBP’s wide reach in their community and long history means their website has hundreds of content pages. This can be costly to convert, and for some non-profits, can make a redesign project cost-prohibitive. End Point engineers Greg Davidson and Marco Pessotto worked alongside NBP staff helping them learn the Bootstrap web development toolkit and the steps they needed to take to integrate each page into the new designs. Using these in-house resources dramatically reduced the overall project cost while allowing the content to be converted carefully by hand with accuracy and care.&lt;/p&gt;
&lt;h3 id=&#34;wcag-20-aa-standard&#34;&gt;WCAG 2.0, AA Standard&lt;/h3&gt;
&lt;p&gt;NBP’s user base is heavily centered in the low-vision community, so compliance with user accessibility standards was a main focus of the redesign effort. End Point worked with NBP to make sure all of the engineers on the project were trained in the Web Content Accessibility Guidelines (WCAG 2.0) AA standards and guidelines. Testing for compliance was done with &lt;a href=&#34;https://chrome.google.com/webstore/detail/axe/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US&#34;&gt;AXE&lt;/a&gt; and &lt;a href=&#34;https://wave.webaim.org/&#34;&gt;WAVE&lt;/a&gt; Chrome accessibility plugins. NBP also completed direct user testing with screen readers and devices.&lt;/p&gt;
&lt;p&gt;Congrats again to NBP on completing this project with us!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Client Case Study: Vervante—​Publishing, Production and Fulfillment Services</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/08/client-case-study-vervante-publishing/"/>
      <id>https://www.endpointdev.com/blog/2017/08/client-case-study-vervante-publishing/</id>
      <published>2017-08-28T00:00:00+00:00</published>
      <author>
        <name>Greg Hanson</name>
      </author>
      <content type="html">
        &lt;h3 id=&#34;a-real-life-scenario&#34;&gt;A real-life scenario&lt;/h3&gt;
&lt;p&gt;The following is a real-life example of services we have provided for one of our clients.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://store.vervante.com/c/affil/index.html&#34;&gt;Vervante Corporation&lt;/a&gt; provides a print on demand and order fulfillment service for thousands of customers, in their case, “Authors”. Vervante needed a way for these authors to keep track of their products. Essentially they needed an &lt;a href=&#34;https://en.wikipedia.org/wiki/Inventory_management_software&#34;&gt;Inventory management system&lt;/a&gt;. So we designed a complete system from the ground up that allows Vervante’s authors many custom functions that simply are not offered in a pre-built package anywhere.&lt;/p&gt;
&lt;p&gt;This is also a good time to mention that &lt;strong&gt;you should always view your web presence, in fact your business itself, as a process&lt;/strong&gt;, not a one time “setup”. Your products will change, your customers will change, the web will change, &lt;strong&gt;everything&lt;/strong&gt; will change. &lt;strong&gt;If you want your business to be successful, you will change.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;some-specifics&#34;&gt;Some Specifics&lt;/h3&gt;
&lt;p&gt;While it is beyond the scope of this case study to describe all of the programs that were developed for Vervante, it will be valuable for the reader to sample just a few of the areas to understand how diverse a single business can be. Here are a few of the functions we have built from scratch, over several years to continue to provide Vervante, their authors, and even their vendors with efficient processes to achieve their daily business needs.&lt;/p&gt;
&lt;h3 id=&#34;requirements&#34;&gt;Requirements&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Author Requirement—​First, in some cases, the best approach to a problem is to use someone else’s solution! Vervante’s authors have large data files that are converted to a product, and then shipped on demand as the orders come in. So we initially provided a custom file transfer process so that customers could directly upload their files to a server we set up for Vervante. Soon Vervante’s rapid growth outpaced the efficacy of this system, so we investigated and determined the most efficient and cost-effective approach was to incorporate a 3rd party service. So we recommended a well known file transfer service and wrote a program to communicate with the file transfer service API. Now a client can easily describe and upload large files to Vervante.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;object class=&#34;BLOG_video_class&#34; classid=&#34;clsid:D27CDB6E-AE6D-11cf-96B8-444553540000&#34; codebase=&#34;http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0&#34; height=&#34;266&#34; id=&#34;BLOG_video-f735687e5d91b6e5&#34; width=&#34;320&#34;&gt;&lt;param name=&#34;movie&#34; value=&#34;https://www.youtube.com/get_player&#34;/&gt;
&lt;param name=&#34;bgcolor&#34; value=&#34;#FFFFFF&#34;/&gt;
&lt;param name=&#34;allowfullscreen&#34; value=&#34;true&#34;/&gt;
&lt;param name=&#34;flashvars&#34; value=&#34;flvurl=https://redirector.googlevideo.com/videoplayback?requiressl%3Dyes%26id%3Df735687e5d91b6e5%26itag%3D5%26source%3Dblogger%26app%3Dblogger%26cmo%3Dsecure_transport%253Dyes%26cmo%3Dsensitive_content%253Dyes%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1508990116%26sparams%3Drequiressl,id,itag,source,ip,ipbits,expire%26signature%3D99FD6C851283A8BC063EB63455AA30622B1A8437.3D4F2FF3F744A46AD71889580CB2708476EA61F4%26key%3Dck2&amp;iurl=https://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Df735687e5d91b6e5%26offsetms%3D5000%26itag%3Dw160%26sigh%3DIi_vgaZOeZV9xdyNjeqlp9MvQPM&amp;autoplay=0&amp;ps=blogger&#34;/&gt;
&lt;embed allowfullscreen=&#34;true&#34; bgcolor=&#34;#FFFFFF&#34; flashvars=&#34;flvurl=https://redirector.googlevideo.com/videoplayback?requiressl%3Dyes%26id%3Df735687e5d91b6e5%26itag%3D5%26source%3Dblogger%26app%3Dblogger%26cmo%3Dsecure_transport%253Dyes%26cmo%3Dsensitive_content%253Dyes%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1508990116%26sparams%3Drequiressl,id,itag,source,ip,ipbits,expire%26signature%3D99FD6C851283A8BC063EB63455AA30622B1A8437.3D4F2FF3F744A46AD71889580CB2708476EA61F4%26key%3Dck2&amp;iurl=https://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Df735687e5d91b6e5%26offsetms%3D5000%26itag%3Dw160%26sigh%3DIi_vgaZOeZV9xdyNjeqlp9MvQPM&amp;autoplay=0&amp;ps=blogger&#34; height=&#34;266&#34; src=&#34;https://www.youtube.com/get_player&#34; type=&#34;application/x-shockwave-flash&#34; width=&#34;320&#34;/&gt;&lt;/object&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href=&#34;https://drive.google.com/file/d/0B_fTO4RaXomtd3REUWR3U1hERmM/view&#34;&gt;View File Save Process&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Storage Requirement—​The remote storage of these large files caused Vervante a dramatic inefficiency as relates to access times, as they worked daily on these files to format, organize, and create product masters. So we needed to provide Vervante with a local server to act as a file server that was on their local network (LAN), where it could be rapidly accessed and manipulated. This was a challenge, as Vervante did not have IT personnel on site. So we purchased an appropriate server, set up everything in our offices, and shipped the complete server to them! They plugged the server into their local network, and with a long phone call, we had the server up and running and remotely managed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Author Requirement—​On the website, the authors first wanted to see what they had in inventory. Some customers provided Vervante with some product components that needed to be included with a complete product, while others relied on Vervante to build all components of their products. They also requested a way to set minimum inventory stock requirements.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So we built an interface that would allow authors to:&lt;/p&gt;
&lt;p&gt;(a) See their current stock levels for all products,&lt;/p&gt;
&lt;p&gt;(b) View outstanding orders for these items,&lt;/p&gt;
&lt;p&gt;(c) Set minimum inventory levels that they would like to have maintained at the fulfillment warehouse.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;margin-top:20px;&#34;&gt;&lt;a href=&#34;/blog/2017/08/client-case-study-vervante-publishing/image-0-big.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; data-original-height=&#34;441&#34; data-original-width=&#34;1211&#34; height=&#34;233&#34; src=&#34;/blog/2017/08/client-case-study-vervante-publishing/image-0.png&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;For example a finished product may consist of a book, a CD, and a DVD. A customer may supply the CD and require Vervante to produce the book and the DVD “on demand” for the product. We created a system that tracked all items at a “base” item level, and then allowed Vervante to “build” products with as many of these “base” items as necessary, to create the final product. The base items could be combined to create an item, and two or more items could be combined to produce yet another item. It is a recursive item inventory system, built from scratch specifically for Vervante.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2017/08/client-case-study-vervante-publishing/image-1.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; data-original-height=&#34;437&#34; data-original-width=&#34;1227&#34; src=&#34;/blog/2017/08/client-case-study-vervante-publishing/image-1.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Vervante Vendor (fulfillment warehouse) Requirement—​Additionally, the &lt;em&gt;fulfillment warehouse&lt;/em&gt; that receives, stores, builds and ships end user products, needed access to this system. They had several needs including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Retrieving pending orders for shipment&lt;/li&gt;
&lt;li&gt;Creating packing / pick slips for the orders&lt;/li&gt;
&lt;li&gt;Create shipping labels for orders&lt;/li&gt;
&lt;li&gt;Manage returns&lt;/li&gt;
&lt;li&gt;Input customer supplied inventory&lt;/li&gt;
&lt;li&gt;Input fulfillment created inventory&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In our administrative user interface for the fulfillment house, we developed a series of customer specific processes to address the above needs. Here is a high level example of how a few of the items on the list above are achieved:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- The fulfillment house logs into the user admin first thing in the morning, and prints the outstanding orders.
- The “orders” are formatted similar to a packing slip, and each slip has all line items of the order, and a bar code imprinted on the slip.
- This document is used as a “pick” slip, and is placed in a “pick” basket. The user then goes through the warehouse, gathers the appropriate items, and when complete the order is placed on a feed belt to the shipper location.
- When the basket lands in front of the shipper, that person checks the contents of the basket against the slip, and then uses a bar code scanner to scan the order. That scan triggers a query into our system that returns all applicable shipping data into an Endicia or UPS system. 
- A shipping label is created, and the shipping cost and tracking information is returned to the our system.
- Additionally the inventory is decremented accordingly when the order receives a shipping label and tracking number.
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Requirements: administrative / accounting—​Vervante also needed an administrative / accounting arm, designed to control all of the accounting functions such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recording customers’ fulfillment charges&lt;/li&gt;
&lt;li&gt;Recording customers’ sales (Vervante sells product for the customers as well as fulfilling outside orders)&lt;/li&gt;
&lt;li&gt;Determining fulfillment vendor fees and payments&lt;/li&gt;
&lt;li&gt;Tracking shipping costs&lt;/li&gt;
&lt;li&gt;Monthly billing of all customers&lt;/li&gt;
&lt;li&gt;Monthly payments for all customers.&lt;/li&gt;
&lt;li&gt;Interface with in-house accounting systems and keeping systems in sync&lt;/li&gt;
&lt;li&gt;Tracking and posting outside order transactions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The above described processes are just a few of the processes that we developed from scratch, and matched to Vervante’s needs. It is also a tiny portion of their system.&lt;/p&gt;
&lt;h3 id=&#34;last-but-not-least&#34;&gt;Last, but not least&lt;/h3&gt;
&lt;p&gt;Oh, and one other interesting fact: When Vervante first came to us several years ago, they had fewer than 20 customers. Today, they provide order fulfillment and print on demand services for nearly 4000 customers. So when we say to plan ahead for growth, we have experience in that area.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Zero Pricing in Interchange using CommonAdjust</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/06/zero-pricing-in-interchange-using/"/>
      <id>https://www.endpointdev.com/blog/2017/06/zero-pricing-in-interchange-using/</id>
      <published>2017-06-26T00:00:00+00:00</published>
      <author>
        <name>Mark Johnson</name>
      </author>
      <content type="html">
        &lt;p&gt;Product pricing can be quite complex. A typical Interchange catalog will have at least one table in the ProductFiles directive (often products plus either options or variants) and those tables will often have one or more pricing fields (usually price and sales_price). But usually a single, static price isn’t sufficient for more complex needs, such as accessory adjustments, quantity pricing, product grouping&amp;ndash;not to mention promotions, sales, or other conditional features that may change a product’s price for a given situation, dependent on the user’s account or session.&lt;/p&gt;
&lt;p&gt;Typically to handle these variety of pricing possibilities, a catalog developer will implement a CommonAdjust algorithm. CommonAdjust can accommodate all the above pricing adjustments and more, and is a powerful tool (yet can become quite arcane when reaching deeper complexity).  CommonAdjust is enabled by setting the PriceField directive to a non-existent field value in the tables specified in ProductFiles.&lt;/p&gt;
&lt;p&gt;To give an adequate introduction and treatise on CommonAdjust would be at a minimum its own post, and likely a series. There are many elements that make up a CommonAdjust string, and subtle operator nuances that instruct it to operate in differing patterns. It is even possible for elements themselves to return new CommonAdjust atoms (a feature we will be leveraging in this discussion). So I will assume for this writing that the reader is familiar generally with CommonAdjust and we will implement a very simple example to demonstrate henceforth.&lt;/p&gt;
&lt;p&gt;To start, let’s create a CommonAdjust string that simply replaces the typical PriceField setting, and we’ll allow it to accommodate a static sales price:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ProductFiles products
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PriceField 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CommonAdjust :sale_price ;:price&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The above, in words, indicates that our products live in the products table, and we want CommonAdjust to handle our pricing by setting PriceField to a non-existent field (0 is a safe bet not to be a valid field in the products table). Our CommonAdjust string is comprised of two atoms, both of which are settors of type database lookup. In the products table, we have 2 fields: sale_price and price. If sale_price is “set” (meaning a non-zero numeric value or another CommonAdjust atom) it will be used as it comes first in the list. The semicolon indicates to Interchange “if a previous atom set a price by this point, we’re done with this iteration” and, thus, the price field will be ignored.  Otherwise, the next atom is checked (the price field), and as long as the price field is set, it will be used instead.&lt;/p&gt;
&lt;p&gt;A few comments here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The bare colon indicates that the field is not restricted to a particular table. Typically, to specify the field, you would have a value like “products:price” or “variants:price”. But part of the power of ProductFiles holding products in different tables is you can pick up a sku from any of them. And at that point, you don’t know whether you’re looking at a sku from products, variants, or as many additional tables as you’d like to grab products from. But if all of them have a price and sales_price field, then you can pick up the pricing from any of them by leaving the table off. You can think of “:price” as “*:price” where asterisk is “table this sku came from”.&lt;/li&gt;
&lt;li&gt;The only indicator that CommonAdjust recognizes as a terminal value is a non-zero numeric value. The proposed price is coerced to numeric, added on to the accumulated price effects of the rest of the CommonAdjust string (if applicable), and the final value is tested for truth. If it is false (empty, undef, or 0) then the process repeats.&lt;/li&gt;
&lt;li&gt;What happens if &lt;em&gt;none&lt;/em&gt; of the atoms produce a non-zero numeric value? If Interchange reaches the end of the original CommonAdjust string without hitting a set atom, it will relent and return a zero cost.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, we finally introduce our situation, and one that is not at all uncommon. What if I &lt;em&gt;want&lt;/em&gt; a zero price? Let’s say I have a promotion for buy one product, get this other product for free. Typically, a developer would be able to expect to override the prices from the database optionally by leveraging the “mv_price” parameter in the cart. So, let’s adjust our CommonAdjust to accommodate that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CommonAdjust $ ;:sale_price ;:price&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The $ settor in the first atom means “look in the line-item hash for the mv_price parameter and use that, if it’s set”. But as we’ve discussed above, we “set” an atom by making it a non-zero numeric value or another CommonAdjust atom. So if we set mv_price to 0, we’ve gained nothing. CommonAdjust will move on to the next atom (sale_price database settor) and pick up that product’s pricing from the database.  And even if we set that product’s sale_price and price fields to 0, it means &lt;em&gt;everyone&lt;/em&gt; purchasing that item would get it for free (not just our promotion that allows the item to be free with the specific purchase of another item).&lt;/p&gt;
&lt;p&gt;In the specific case of using the $ settor in CommonAdjust, we &lt;em&gt;can&lt;/em&gt; set mv_price to the keyword “free”, and that will allow us to price the item for 0. But this restricts us to &lt;em&gt;only&lt;/em&gt; be able to use $ and mv_price to have a free item. What if the price comes from a complex calculation, out of a usertag settor? Or out of a calc block settor? The special “free” keyword doesn’t work there.&lt;/p&gt;
&lt;p&gt;Fortunately, there is a rarely used CommonAdjust settor that will allow for a 0 price item in a general solution. As I mentioned above, CommonAdjust calculations can themselves return other CommonAdjust atoms, which will then be operated on in a subsequent iteration. This frees us from just the special handling that works on $ and mv_price as such an atom can be returned from &lt;em&gt;any&lt;/em&gt; of the CommonAdjust atoms and work.&lt;/p&gt;
&lt;p&gt;The settor of interest is &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;, and according to what documentation there is on it, it was never even intended to be used as a pricing settor! Rather, it was to be a way of redirecting to additional modes for shipping or tax calculations, which can also leverage CommonAdjust for their particular purposes. However, the key to its usefulness here is thus: it does not perform any test on the value tied to it. It is set, untested, into the final result of this call to the chain_cost() routine and returned. And with no test, the fact that it’s Perly false as numeric 0 is irrelevant.&lt;/p&gt;
&lt;p&gt;So building on our current CommonAdjust, let’s leverage &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; to allow our companion product to have a zero cost (assuming it is the 2nd line item in the cart):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[calcn]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $Items-&amp;gt;[1]{mv_price} = &amp;#39;&amp;gt;&amp;gt;0&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    return;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/calcn]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now what happens is, $ in the first atom picks up the value out of mv_price and, because it’s a CommonAdjust atom, is processed in a second iteration. But this CommonAdjust atom is very simple: take the value tied to &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; and return it, untested.&lt;/p&gt;
&lt;p&gt;Perhaps our pricing is more complex than we can (or would like to) support with using $. So we want to write a usertag, where we have the full power of global Perl at our disposal, but we still have circumstances where that usertag may need to return zero-cost items. Using the built-in “free” solution, we’re stuck, short of setting mv_price in the item hash within the usertag, which we may not want to do for a variety of reasons. But using &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;, we have no such restriction. So let’s change CommonAdjust:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CommonAdjust $ ;[my-special-pricing] ;:sale_price ;:price&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now instead of setting mv_price in the item, let’s construct [my-special-pricing] to do some heavy lifting:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UserTag my-special-pricing Routine &amp;lt;&amp;lt;EOR
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sub {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # A bunch of conditional, complicated code, but then ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    elsif (buy_one_get_one_test($item)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        # This is where we know this normally priced item is supposed to be
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        # free because of our promotion. Excellent!
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        return &amp;#39;&amp;gt;&amp;gt;0&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # remaining code we don&amp;#39;t care about for this discussion
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EOR&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we haven’t slapped a zero cost onto the line item in a sticky fashion, like we do by setting mv_price. So presumably, above, if the user gets sneaky and removes the “buy one” sku identified by our promotion, our equally clever buy_one_get_one_test() sniffs it out, and the 0 price is no longer in effect.&lt;/p&gt;
&lt;p&gt;For more information on CommonAdjust, see the &lt;a href=&#34;http://www.icdevgroup.org/docs/glossary/price.html&#34;&gt;Custom Pricing section of ‘price’ glossary entry&lt;/a&gt;. And for more examples of leveraging CommonAdjust for quantity and attribute pricing adjustments, see the &lt;a href=&#34;http://www.icdevgroup.org/docs/confs/CommonAdjust.html#CommonAdjust_examples&#34;&gt;Examples section of the CommonAdjust document entry&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Reach customers and drive sales with MailChimp</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2016/08/reach-customers-and-drive-sales-with/"/>
      <id>https://www.endpointdev.com/blog/2016/08/reach-customers-and-drive-sales-with/</id>
      <published>2016-08-22T00:00:00+00:00</published>
      <author>
        <name>Josh Lavin</name>
      </author>
      <content type="html">
        &lt;p&gt;It’s a good idea for ecommerce stores to regularly contact their customers. This not only reminds customers that your business exists, but also allows the sharing of new products and resources that can enrich the lives of your customers and clients. One of the easiest ways to stay in touch is by using an email newsletter service, such as &lt;a href=&#34;http://mailchimp.com/&#34;&gt;MailChimp&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img alt=&amp;quot;&amp;quot; border=&amp;quot;0&amp;quot; height=&amp;quot;200&amp;quot; src=&amp;quot;/blog/2016/08/reach-customers-and-drive-sales-with/image-0.png&amp;quot; title=&amp;quot;Freddie, the MailChimp mascot&amp;quot; width=&amp;quot;191&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MailChimp offers the regular suite of email newsletter services: lists, campaigns, and reports—​but in addition, they allow an ecommerce store to &lt;a href=&#34;http://mailchimp.com/connect-your-store/&#34;&gt;integrate sales data back into MailChimp&lt;/a&gt;. When you have &lt;strong&gt;detailed shopping statistics&lt;/strong&gt; for each subscriber, it opens new possibilities for customized marketing campaigns.&lt;/p&gt;
&lt;h3 id=&#34;endless-possibilities&#34;&gt;Endless possibilities&lt;/h3&gt;
&lt;p&gt;For example, imagine you have an email mailing list with 1,000 recipients. Instead of mailing the same generic newsletter to each subscriber, what if you could segment the list to identify your 100 best customers, and email them a special campaign?&lt;/p&gt;
&lt;p&gt;Additional ideas could include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reach out to inactive subscribers, offering a coupon&lt;/li&gt;
&lt;li&gt;Invite your best customers to a secret sale&lt;/li&gt;
&lt;li&gt;Re-engage customers who placed items in their cart, but left without purchasing&lt;/li&gt;
&lt;li&gt;Offer complementary products to purchasers of &lt;em&gt;Product X&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;automatic-marketing&#34;&gt;Automatic marketing&lt;/h3&gt;
&lt;p&gt;Once your store has sales data for subscribers, and you’ve decided on the campaigns you want to run with this data, the next step is to &lt;strong&gt;automate the process&lt;/strong&gt;. This is where MailChimp’s &lt;a href=&#34;http://mailchimp.com/features/automation/&#34;&gt;Automation&lt;/a&gt; feature comes in. Spend some time up-front to craft the automated campaigns, then sit back and let MailChimp run them for you; day in, day out.&lt;/p&gt;
&lt;h3 id=&#34;steps-to-implement&#34;&gt;Steps to implement&lt;/h3&gt;
&lt;p&gt;There are several off-the-shelf &lt;a href=&#34;https://connect.mailchimp.com/collections/e-commerce&#34;&gt;integrations for ecommerce stores&lt;/a&gt;, including Magento and BigCommerce.&lt;/p&gt;
&lt;p&gt;Users of Perl and &lt;a href=&#34;/expertise/perl-interchange/&#34;&gt;Interchange&lt;/a&gt; can use our newly-released toolsets of the &lt;a href=&#34;http://p3rl.org/Mail::Chimp3&#34;&gt;Mail::Chimp3 CPAN module&lt;/a&gt; and the &lt;a href=&#34;https://github.com/jdigory/interchange-extras/tree/master/mailchimp&#34;&gt;integration for Interchange5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/contact/&#34;&gt;Contact us&lt;/a&gt; today for expert help integrating one of these solutions with your ecommerce store.&lt;/p&gt;
&lt;h3 id=&#34;go-beyond-the-simple-newsletter&#34;&gt;Go beyond the simple newsletter&lt;/h3&gt;
&lt;p&gt;Most businesses already have an email newsletter. Hopefully, you are sending regular email campaigns with it. This is a great first step. Going beyond this to segment your email list and reach out to these segments with relevant information to each of them, is the next step. Not only can this &lt;strong&gt;increase your sales&lt;/strong&gt;, but it also &lt;strong&gt;respects your clients’ and customers’ time and preferences&lt;/strong&gt;. It’s a win-win for all.&lt;/p&gt;
&lt;p&gt;Additional resource: &lt;a href=&#34;http://mailchimp.com/resources/guides/mailchimp-for-online-sellers/&#34;&gt;MailChimp for Online Sellers&lt;/a&gt;&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Improve SEO URLs for Interchange search pages</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2016/01/improve-seo-urls-for-interchange-search/"/>
      <id>https://www.endpointdev.com/blog/2016/01/improve-seo-urls-for-interchange-search/</id>
      <published>2016-01-27T00:00:00+00:00</published>
      <author>
        <name>Jeff Boes</name>
      </author>
      <content type="html">
        &lt;p&gt;This is an article aimed at beginner-to-intermediate Interchange developers.&lt;/p&gt;
&lt;p&gt;A typical approach to a hierarchical Interchange site is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Categories -&amp;gt; Category -&amp;gt; Product&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I.e., you list all your categories as links, each of which opens up a search results page filtering the products by category, with links to the individual product pages via the flypage.&lt;/p&gt;
&lt;p&gt;Recently I upgraded a site so the category URLs were a bit more SEO-friendly. The original category filtering search produced these lovely specimens:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/search.html?fi=products&amp;amp;st=db&amp;amp;co=1&amp;amp;sf=category&amp;amp;se=Shoes&amp;amp;op=rm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;amp;sf=inactive&amp;amp;se=yes&amp;amp;op=ne&amp;amp;tf=category&amp;amp;ml=100&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;but what I really wanted was:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/cat/Shoes.html&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Such links are easier to communicate to users, more friendly to search engines, less prone to breakage (e.g., by getting word-wrapped in email clients), and avoid exposing details of your application (here, we&amp;rsquo;ve had to admit publicly that we have a table called “products” and that some items are “inactive”; a curious user might decide to see what happens if they change “sf=inactive&amp;amp;se=yes” to some other expression).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I attacked this.&lt;/p&gt;
&lt;h3 id=&#34;creating-a-category-listing-page&#34;&gt;Creating a category listing page&lt;/h3&gt;
&lt;p&gt;First, I copied my “results.html” page to “catpage.html”. That way, my original search results page can continue to serve up ad hoc search results.&lt;/p&gt;
&lt;p&gt;The search results were displayed via:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[search-region]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/search-region]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I converted this to a database query:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[query sql=&amp;#34;SELECT * FROM products WHERE NOT inactive AND category = [sql-quote][cgi category][/sql-quote]&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; type=list prefix=item]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/query]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I chose to use a prefix other than the default since it would avoid having to change so many tags in the page, and now both the original search page and new catpage would look much the same internally (and thus, if desired, I could refactor them in the future).&lt;/p&gt;
&lt;p&gt;Note that I&amp;rsquo;ve defined part of the API for this page: the category to be searched is set in a CGI variable called “category”.&lt;/p&gt;
&lt;p&gt;In my specific case, there was additional tinkering with this tag, because I had nested [query] tags already in the page within the search-region.&lt;/p&gt;
&lt;h3 id=&#34;creating-a-cat-actionmap&#34;&gt;Creating a “cat” actionmap&lt;/h3&gt;
&lt;p&gt;In order to translate a URL containing SEO-friendly “/cat/Shoes.html” into my search, I need an actionmap. Here&amp;rsquo;s mine; it&amp;rsquo;s very simple.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Actionmap cat &amp;lt;&amp;lt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;CODE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$url&lt;/span&gt; = &lt;span style=&#34;color:#038&#34;&gt;shift&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;@url_parts&lt;/span&gt; = &lt;span style=&#34;color:#038&#34;&gt;split&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#369&#34;&gt;$url&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#038&#34;&gt;shift&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;@url_parts&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$url_parts&lt;/span&gt;[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#080&#34;&gt;eq&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;cat&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$CGI&lt;/span&gt;-&amp;gt;{mv_nextpage} = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;catpage.html&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$CGI&lt;/span&gt;-&amp;gt;{category} = &lt;span style=&#34;color:#038&#34;&gt;shift&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;@url_parts&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CODE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Actionmaps are called when Interchange detects that a URL begins with the actionmap&amp;rsquo;s name; here “cat”. They are passed a parameter containing the URL fragment (after removing all the site stuff). Here, that would be (e.g.) “/cat/Shoes”. We massage the URL to get our category code, and set up the page to be called along with the CGI parameter(s) it expects.&lt;/p&gt;
&lt;h3 id=&#34;cleaning-up-the-links&#34;&gt;Cleaning up the links&lt;/h3&gt;
&lt;p&gt;At the start of this article I noted that I may have a page listing all my categories. In my original setup, this generated links using a construction like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;[area href=search form=|fi=products
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;         st=db
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;         sf=category
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;         se=Shoes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;         tf=category
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;         ml=100|]&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Shoes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now my links are the much simpler:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;[area cat/Shoes]&amp;#34;&lt;/span&gt;&amp;gt;Shoes&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In my specific case, these links were generated within a [query] loop, but the approach is the same.&lt;/p&gt;
&lt;p&gt;Note: the &lt;a href=&#34;http://demo.icdevgroup.org/demo1/&#34;&gt;Strap demo&lt;/a&gt; supports SEO-friendly URLs out-of-the-box, and that it is included with the latest &lt;a href=&#34;http://www.icdevgroup.org/i/dev/news?mv_arg=00060&#34;&gt;Interchange 5.10&lt;/a&gt; release.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>2015 Perl Dancer Conference videos</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/12/2015-perl-dancer-conference-videos/"/>
      <id>https://www.endpointdev.com/blog/2015/12/2015-perl-dancer-conference-videos/</id>
      <published>2015-12-30T00:00:00+00:00</published>
      <author>
        <name>Sam Batschelet</name>
      </author>
      <content type="html">
        &lt;p&gt;The 2015 &lt;a href=&#34;https://www.perl.dance&#34;&gt;Perl Dancer Conference&lt;/a&gt; has recently released the presentation videos. This year the conference was hosted in beautiful Vienna, Austria. Josh Lavin and I were both honored to attend the conference as well as give talks. Earlier, Josh wrote summaries of the conference:&lt;/p&gt;
&lt;h3 id=&#34;conference-recap&#34;&gt;Conference Recap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/blog/2015/10/perl-dancer-conference-2015-report/&#34;&gt;Training Days&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/blog/2015/10/perl-dancer-conference-2015-report_30/&#34;&gt;Conference Days&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;conference-presentations&#34;&gt;Conference Presentations&lt;/h3&gt;
&lt;h4 id=&#34;spacecamps-the-final-frontier&#34;&gt;SpaceCamps “The Final Frontier”&lt;/h4&gt;
&lt;p&gt;I gave a talk exploring new technologies for End Point’s own &lt;a href=&#34;http://devcamps.org&#34;&gt;DevCamps&lt;/a&gt; development tool. During the presentation I detailed my research into containers and what a cloud-based development environment might look like.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=IYKs8FF8--Y&#34;&gt;SpaceCamps Presentation Video&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&#34;angularjs--dancer-for-modern-web-development&#34;&gt;AngularJS &amp;amp; Dancer for Modern Web Development&lt;/h4&gt;
&lt;p&gt;Josh detailed his experience migrating legacy applications utilizing Dancer, AngularJS, and modern Perl techniques. Josh highlighted the challenges he faced during the process, as well as lessons he learned along the way.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=m7q6QbSHrkA&#34;&gt;AngularJS &amp;amp; Dancer for Modern Web Development Presentation Video&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;lightning-talks&#34;&gt;Lightning Talks&lt;/h3&gt;
&lt;p&gt;Josh and I both gave short “lightning talks.” Josh’s was on &lt;a href=&#34;https://youtu.be/-eg21qxxIAA?t=12m47s&#34;&gt;Writing Unit Tests for a Legacy App (Interchange 5)&lt;/a&gt;, and mine was on &lt;a href=&#34;https://youtu.be/-eg21qxxIAA?t=19m11s&#34;&gt;Plack &amp;amp; Interchange 5.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To review the rest of the presentations please checkout the &lt;a href=&#34;https://www.youtube.com/channel/UCWtnsx6yRuHvO2e-xX2Yyng&#34;&gt;Perl Dancer Conference YouTube channel&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;
&lt;p&gt;The Perl Dancer community continues to flourish and the conference this year hosted a record 5 core &lt;a href=&#34;http://perldancer.org&#34;&gt;Dancer&lt;/a&gt; developers. Dancer is about to release the finalized version of its long awaited plugin infrastructure for &lt;a href=&#34;https://github.com/PerlDancer/Dancer2&#34;&gt;Dancer2&lt;/a&gt;. A lot of work on this was completed during the conference. Being an organizer of the conference, it brings me great joy to see this success. This news along with the release of Perl 6, I am certain 2016 will be a wonderful year for not only Dancer but the entire &lt;a href=&#34;https://www.perl.org/&#34;&gt;Perl&lt;/a&gt; community.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Perl Dancer Conference 2015 Report—​Conference Days</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/10/perl-dancer-conference-2015-report_30/"/>
      <id>https://www.endpointdev.com/blog/2015/10/perl-dancer-conference-2015-report_30/</id>
      <published>2015-10-30T00:00:00+00:00</published>
      <author>
        <name>Josh Lavin</name>
      </author>
      <content type="html">
        &lt;p&gt;In my &lt;a href=&#34;/blog/2015/10/perl-dancer-conference-2015-report/&#34;&gt;last post&lt;/a&gt;, I shared about the Training Days from the &lt;a href=&#34;https://www.perl.dance/&#34;&gt;Perl Dancer&lt;/a&gt; 2015 conference, in Vienna, Austria. This post will cover the two days of the conference itself.&lt;/p&gt;
&lt;p&gt;While there were &lt;em&gt;several&lt;/em&gt; wonderful talks, &lt;a href=&#34;https://www.perl.dance/users/21&#34;&gt;Gert van der Spoel&lt;/a&gt; did a great job of writing recaps of all of them (&lt;a href=&#34;https://www.perl.dance/wiki/node/2015%20Day%201%20Summary&#34;&gt;Day 1&lt;/a&gt;, &lt;a href=&#34;https://www.perl.dance/wiki/node/2015%20Day%202%20Summary&#34;&gt;Day 2&lt;/a&gt;), so here I’ll cover the ones that stood out most to me.&lt;/p&gt;
&lt;h3 id=&#34;day-one&#34;&gt;Day One&lt;/h3&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center; float:right&#34;&gt;&lt;a href=&#34;/blog/2015/10/perl-dancer-conference-2015-report_30/image-0-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2015/10/perl-dancer-conference-2015-report_30/image-0.jpeg&#34;/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;
&lt;small&gt;&lt;a href=&#34;https://twitter.com/sukria/status/657098210989776896&#34;&gt;Dancer Conference, by Alexis Sukrieh&lt;/a&gt; (used with permission)&lt;/small&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/PerlSawyer&#34;&gt;Sawyer X&lt;/a&gt; spoke on the &lt;em&gt;&lt;a href=&#34;https://www.perl.dance/talks/17-state-of-dancer&#34;&gt;State of Dancer&lt;/a&gt;&lt;/em&gt;. One thing mentioned, which came up again later in the conference, was: &lt;strong&gt;Make the effort, move to Dancer 2! Dancer 1 is frozen.&lt;/strong&gt; There have been some recent changes to Dancer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Middlewares for static files, so these are handled outside of Dancer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New &lt;a href=&#34;http://p3rl.org/Hash::MultiValue&#34;&gt;Hash::MultiValue&lt;/a&gt; parameter keywords (route_parameters, query_parameters, body_parameters; covered in my &lt;a href=&#34;/blog/2015/10/perl-dancer-conference-2015-report/&#34;&gt;earlier post&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/Dancer2::Manual#Delayed-responses-Async-Streaming&#34;&gt;Delayed responses&lt;/a&gt; (asynchronous) with delayed keyword:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Runs on the server after the request has finished.&lt;/li&gt;
&lt;li&gt;Streaming is also asynchronous, feeding the user chunks of data at a time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Items coming soon to Dancer may include: Web Sockets (supported in &lt;a href=&#34;http://p3rl.org/Plack&#34;&gt;Plack&lt;/a&gt;), per-route &lt;a href=&#34;https://metacpan.org/pod/Dancer2::Manual#Serializers1&#34;&gt;serialization&lt;/a&gt; (currently enabling a serializer such as JSON affects the entire app—​later on, &lt;a href=&#34;https://twitter.com/veryrusty&#34;&gt;Russell&lt;/a&gt; &lt;a href=&#34;http://p3rl.org/Dancer2::Plugin::SendAs&#34;&gt;released a module&lt;/a&gt; for this, which may make it back into the core), Dancer2::XS, and &lt;a href=&#34;https://github.com/PerlDancer/perl-lint-policy-dancer2&#34;&gt;critic/linter policies&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/domm_favors_irc&#34;&gt;Thomas Klausner&lt;/a&gt; shared about &lt;em&gt;&lt;a href=&#34;https://www.perl.dance/talks/18-oauth2%2C-resty-apis%2C-microservices&#34;&gt;OAuth &amp;amp; Microservices&lt;/a&gt;&lt;/em&gt;. Microservices are a good tool to manage complexity, but you might want to aim for “monolith first”, &lt;a href=&#34;http://martinfowler.com/bliki/MonolithFirst.html&#34;&gt;according to Martin Fowler&lt;/a&gt;, and only later break up your app into microservices. In the old days, we had “fat” back-ends, which did everything and delivered the results to a browser. Now, we have “fat” front-ends, which take info from a back-end and massage it for display. One advantage of the microservice way of thinking is that mobile devices (or even third parties) can access the same APIs as your front-end website.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://oauth.net/&#34;&gt;OAuth&lt;/a&gt; allows a user to login at your site, using their credentials from another site (such as Facebook or Google), so they don’t need a password for your site itself. This typically happens via JavaScript and cookies. However, to make your back-end “stateless”, you could use &lt;a href=&#34;https://jwt.io/&#34;&gt;JSON Web Tokens&lt;/a&gt; (JWT). Thomas showed some examples of all this in action, using the &lt;a href=&#34;http://p3rl.org/OX&#34;&gt;OX Perl module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One thing I found interesting that Thomas mentioned: &lt;em&gt;Plack middleware is the correct place to implement most of the generic part of a web app. The framework is the wrong part.&lt;/em&gt; I think this mindset goes along with Sawyer’s comments about &lt;em&gt;Web App + App&lt;/em&gt; in the Training Days.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/0xMickey&#34;&gt;Mickey Nasriachi&lt;/a&gt; &lt;a href=&#34;https://www.perl.dance/talks/25-ponapi%3A-eliminate-the-bikesheding&#34;&gt;shared&lt;/a&gt; his development on &lt;a href=&#34;https://github.com/mickeyn/ponapi&#34;&gt;PONAPI&lt;/a&gt;, which implements the &lt;a href=&#34;http://jsonapi.org/&#34;&gt;JSON API&lt;/a&gt; specification in Perl. The JSON API spec is a standard for creating APIs. It essentially absolves you from having to make decisions about how you should structure your API.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center; float:right&#34;&gt;&lt;a href=&#34;/blog/2015/10/perl-dancer-conference-2015-report_30/image-1-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2015/10/perl-dancer-conference-2015-report_30/image-1.jpeg&#34;/&gt;&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;
&lt;small&gt;Panorama from the south tower of St. Stephen’s cathedral, by this author&lt;/small&gt;&lt;/div&gt;
&lt;p&gt;Gert presented on &lt;em&gt;&lt;a href=&#34;https://www.perl.dance/talks/9-social-logins-for-e-commerce-sites&#34;&gt;Social Logins &amp;amp; eCommerce&lt;/a&gt;&lt;/em&gt;. This built on the earlier OAuth talk by Thomas. Here are some of the pros/cons to social login which Gert presented:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros—​customer:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Alleviates “password fatigue”&lt;/li&gt;
&lt;li&gt;Convenience&lt;/li&gt;
&lt;li&gt;Brand familiarity (with the social login provider)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pros—​eCommerce website:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Expected customer retention&lt;/li&gt;
&lt;li&gt;Expected increase in sales&lt;/li&gt;
&lt;li&gt;Better target customers&lt;/li&gt;
&lt;li&gt;“Plug &amp;amp; Play” (if you pay)—​some services exist to make it simple to integrate social logins, where you just integrate with them, and then you are effectively integrated with whatever social login providers they support. These include Janrain and LoginRadius&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons—​customer:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Privacy concerns (sharing their social identity with your site)&lt;/li&gt;
&lt;li&gt;Security concerns (if their social account is hacked, so are all their accounts where they have used their social login)&lt;/li&gt;
&lt;li&gt;Confusion (especially on how to leave a site)&lt;/li&gt;
&lt;li&gt;Usefulness (no address details are provided by the social provider in the standard scope, so the customer still has to enter extra details on your site)&lt;/li&gt;
&lt;li&gt;Social account hostages (if you’ve used your social account to login elsewhere, you are reluctant to shut down your social account)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons—​eCommerce website:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Legal implications&lt;/li&gt;
&lt;li&gt;Implementation hurdles&lt;/li&gt;
&lt;li&gt;Usefulness&lt;/li&gt;
&lt;li&gt;Provider problem is your problem (e.g., if the social login provider goes down, all your customers who use it to login are unable to login to your site)&lt;/li&gt;
&lt;li&gt;Brand association (maybe you don’t want your site associated with certain social sites)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons—​social provider:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;???&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Šimun Kodžoman spoke on &lt;em&gt;&lt;a href=&#34;https://www.perl.dance/talks/22-dancer-%2B-meteor-%3D-mobile-app&#34;&gt;Dancer + Meteor = mobile app&lt;/a&gt;&lt;/em&gt;. &lt;a href=&#34;https://www.meteor.com/&#34;&gt;Meteor&lt;/a&gt; is a JavaScript framework for both server-side and client-side. It seems one of the most interesting aspects is you can use Meteor with the Android or iOS SDK to auto-generate a true mobile app, which has many more advantages than a simple HTML “app” created with &lt;a href=&#34;http://phonegap.com/&#34;&gt;PhoneGap&lt;/a&gt;. Šimun is using Dancer as a back-end for Meteor, because the server-side Meteor aspect is still new and unstable, and is also dependent on &lt;a href=&#34;https://www.mongodb.org/&#34;&gt;MongoDB&lt;/a&gt;, which cannot be used for everything.&lt;/p&gt;
&lt;p&gt;End Point’s own Sam Batschelet shared his work on &lt;em&gt;&lt;a href=&#34;https://www.perl.dance/talks/4-space-camp---the-final-frontier&#34;&gt;Space Camp&lt;/a&gt;&lt;/em&gt;, a new container-based setup for development environments. This pulls together several pieces, including &lt;a href=&#34;https://coreos.com/&#34;&gt;CoreOS&lt;/a&gt;, &lt;a href=&#34;http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html&#34;&gt;systemd-nspawn&lt;/a&gt;, and &lt;a href=&#34;https://coreos.com/etcd/&#34;&gt;etcd&lt;/a&gt; to provide a futuristic version of &lt;a href=&#34;http://www.devcamps.org/&#34;&gt;DevCamps&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;day-two&#34;&gt;Day Two&lt;/h3&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center; float:right&#34;&gt;&lt;a href=&#34;/blog/2015/10/perl-dancer-conference-2015-report_30/image-2-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2015/10/perl-dancer-conference-2015-report_30/image-2.jpeg&#34;/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;small&gt;&lt;a href=&#34;https://twitter.com/sbatschelet/status/657493819135541248&#34;&gt;Conference goers, by Sam&lt;/a&gt;&lt;br/&gt;(used with permission)&lt;/small&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/pullingshots&#34;&gt;Andrew Baerg&lt;/a&gt; spoke on &lt;em&gt;&lt;a href=&#34;https://www.perl.dance/talks/20-taming-a-thousand-pound-gorilla&#34;&gt;Taming the 1000-lb Gorilla&lt;/a&gt;&lt;/em&gt; that is &lt;a href=&#34;https://www.interchangecommerce.org/&#34;&gt;Interchange 5&lt;/a&gt;. He shared how they have endeavored to manage their Interchange development in more modern ways, such as using unit tests and &lt;a href=&#34;http://p3rl.org/DBIx::Class&#34;&gt;DBIC&lt;/a&gt;. One item I found especially interesting was the use of &lt;a href=&#34;http://p3rl.org/DBIx::Class::Fixtures&#34;&gt;DBIx::Class::Fixtures&lt;/a&gt; to allow saving bits of information from a database to keep with a test. This is helpful when you have a bug from some database entry which you want to fix and ensure stays fixed, as databases can change over time, and without a “fixture” your test would not be able to run.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/veryrusty&#34;&gt;Russell Jenkins&lt;/a&gt; shared &lt;em&gt;&lt;a href=&#34;https://www.perl.dance/talks/24-howto-contributeto-dancer2&#34;&gt;HowTo Contribute to Dancer 2&lt;/a&gt;&lt;/em&gt;. He went over the use of &lt;a href=&#34;https://git-scm.com/&#34;&gt;Git&lt;/a&gt;, including such helpful commands and tips as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;git status &amp;ndash;short &amp;ndash;branch&lt;/li&gt;
&lt;li&gt;Write good commit messages: one line summary, less than 50 characters; longer description, wrapped to 72 characters; refer to and/or close issues&lt;/li&gt;
&lt;li&gt;Work in a branch (you shall not commit to master)&lt;/li&gt;
&lt;li&gt;“But I committed to master” &amp;ndash;&amp;gt; branch and reset&lt;/li&gt;
&lt;li&gt;git log &amp;ndash;oneline &amp;ndash;since=2.weeks&lt;/li&gt;
&lt;li&gt;git add &amp;ndash;fixup &lt;SHA1 hash&gt;&lt;/li&gt;
&lt;li&gt;The use of branches named with “feature/whatever” or “bugfix/whatever” can be helpful (this is Russell’s convention)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are several &lt;a href=&#34;https://github.com/PerlDancer/Dancer2/issues?q=is%3Aopen+is%3Aissue+label%3A%22Beginner+Suitable%22&#34;&gt;Dancer 2 issues tagged “beginner suitable”&lt;/a&gt;, so it is easy for nearly anyone to contribute. The &lt;a href=&#34;http://perldancer.org/&#34;&gt;Dancer website&lt;/a&gt; is &lt;a href=&#34;https://github.com/PerlDancer/perldancer-website&#34;&gt;also on GitHub&lt;/a&gt;. You can even make simple edits directly in GitHub!&lt;/p&gt;
&lt;p&gt;It was great to have the author of Dancer, &lt;a href=&#34;https://twitter.com/sukria&#34;&gt;Alexis Sukrieh&lt;/a&gt;, in attendance. He shared his original vision for Dancer, which filled a gap in the Perl ecosystem back in 2009. The goal for Dancer was to create a DSL (&lt;a href=&#34;https://en.wikipedia.org/wiki/Domain-specific_language&#34;&gt;Domain-specific language&lt;/a&gt;) to provide a very simple way to develop web applications. The DSL provides “&lt;a href=&#34;https://metacpan.org/pod/Dancer2::Manual#DSL-KEYWORDS&#34;&gt;keywords&lt;/a&gt;” for use in the Dancer app, which are specific to Dancer (basically extra functionality for Perl). One of the core aspects of keeping it simple was to avoid the use of $self (a standby of object-oriented Perl, one of the things that you just “have to do”, typically).&lt;/p&gt;
&lt;p&gt;Alexis mentioned that &lt;strong&gt;Dancer 1 is frozen—​Dancer 2 full-speed ahead!&lt;/strong&gt; He also shared some of his learnings along the way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fill a gap (define clearly the problem, present your solution)&lt;/li&gt;
&lt;li&gt;Stick to your vision&lt;/li&gt;
&lt;li&gt;Code is not enough (opensource needs attention; marketing matters)&lt;/li&gt;
&lt;li&gt;Meet in person (collaboration is hard; online collaboration is very hard)&lt;/li&gt;
&lt;li&gt;Kill the ego—​you are not your code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While at the conference, Alexis even wrote a Dancer2 plugin, &lt;a href=&#34;https://metacpan.org/pod/Dancer2::Plugin::ProbabilityRoute&#34;&gt;Dancer2::Plugin::ProbabilityRoute&lt;/a&gt;, which allows you to do &lt;a href=&#34;https://en.wikipedia.org/wiki/A/B_testing&#34;&gt;A/B Testing&lt;/a&gt; in your Dancer app. (Another similar plugin is &lt;a href=&#34;https://metacpan.org/pod/Dancer2::Plugin::Sixpack&#34;&gt;Dancer2::Plugin::Sixpack&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Also check out &lt;a href=&#34;https://web.archive.org/web/20151108214937/http://blog.sukria.net/2015/10/22/perl-dancer-2015-report/&#34;&gt;Alexis’ recap&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, I was privileged to speak as well, on &lt;em&gt;&lt;a href=&#34;https://www.perl.dance/talks/11-angularjs-%26-dancer-for-modern-web-development&#34;&gt;AngularJS &amp;amp; Dancer for Modern Web Development&lt;/a&gt;&lt;/em&gt;. Since this post is already pretty long, I’ll save the details for &lt;a href=&#34;/blog/2015/10/angularjs-dancer-for-modern-web/&#34;&gt;another post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;
&lt;p&gt;In summary, the Perl Dancer conference was a great time of learning and building community. If I had to wrap it all up in one insight, it would be: &lt;strong&gt;Web App + App&lt;/strong&gt;—​that is, your &lt;strong&gt;application should be a compilation of: Plack middleware, Web App (Dancer), and App (Perl classes and methods)&lt;/strong&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Perl Dancer Conference 2015 Report — Training Days</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/10/perl-dancer-conference-2015-report/"/>
      <id>https://www.endpointdev.com/blog/2015/10/perl-dancer-conference-2015-report/</id>
      <published>2015-10-28T00:00:00+00:00</published>
      <author>
        <name>Josh Lavin</name>
      </author>
      <content type="html">
        &lt;p&gt;I just returned from the &lt;a href=&#34;https://www.perl.dance/&#34;&gt;Perl Dancer Conference&lt;/a&gt;, held in Vienna, Austria. It was a jam-packed schedule of two days of training and two conference days, with five of the nine Dancer core developers in attendance.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; float:right; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2015/10/perl-dancer-conference-2015-report/image-0-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&#34; title=&#34;Vienna&#34;&gt;&lt;img alt=&#34;[image of Vienna]&#34; border=&#34;0&#34; src=&#34;/blog/2015/10/perl-dancer-conference-2015-report/image-0.jpeg&#34;/&gt;&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;&lt;small&gt;Kohlmarkt street, Wien, by this author&lt;/small&gt;&lt;/div&gt;
&lt;p&gt;If you aren’t familiar with &lt;a href=&#34;http://www.perldancer.org/&#34;&gt;Perl Dancer&lt;/a&gt;, it is a modern framework for Perl for building web applications. Dancer1 originated as a port of Ruby’s Sinatra project, but has officially been replaced with a rewrite called Dancer2, based on &lt;a href=&#34;https://metacpan.org/pod/Moo&#34;&gt;Moo&lt;/a&gt;, with Dancer1 being frozen and only receiving security fixes. The Interchange 5 e-commerce package is gradually being replaced by Dancer plugins.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 1&lt;/strong&gt; began with a training on Dancer2 by &lt;a href=&#34;https://twitter.com/PerlSawyer&#34;&gt;Sawyer X&lt;/a&gt; and &lt;a href=&#34;https://twitter.com/0xMickey&#34;&gt;Mickey Nasriachi&lt;/a&gt;, two Dancer core devs. During the training, the attendees worked on adding functionality to a &lt;a href=&#34;https://github.com/xsawyerx/dancer-training-vienna&#34;&gt;sample Dancer app&lt;/a&gt;. Some of my takeaways from the training:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Think of your app as a Dancer Web App &lt;em&gt;plus&lt;/em&gt; an App. These should ideally be two separate things, where the Dancer web app provides the URL routes for interaction with your App.&lt;/li&gt;
&lt;li&gt;The lib directory contains all of your application. The recommendation for large productions is to separate your app into separate namespaces and classes. Some folks use a routes directory just for routing code, with lib reserved for the App itself.&lt;/li&gt;
&lt;li&gt;It is recommended to add an empty .dancer file to your app’s directory, which indicates that this is a Dancer app (other Perl frameworks do similarly).&lt;/li&gt;
&lt;li&gt;When running your Dancer app in development, you can use plackup -R lib bin/app.psgi which will restart the app automatically whenever something changes in lib.&lt;/li&gt;
&lt;li&gt;Dancer handles all the standard HTTP verbs, except note that we must use del, not &lt;em&gt;delete&lt;/em&gt;, as &lt;em&gt;delete&lt;/em&gt; conflicts with the Perl keyword.&lt;/li&gt;
&lt;li&gt;There are new keywords for retrieving parameters in your routes. Whereas before we only had param or params, it is now recommended to use:
&lt;ul&gt;
&lt;li&gt;route_parameters,&lt;/li&gt;
&lt;li&gt;query_parameters, or&lt;/li&gt;
&lt;li&gt;body_parameters&lt;/li&gt;
&lt;li&gt;all of which can be used with -&amp;gt;get(&amp;lsquo;foo&amp;rsquo;) which is always a single scalar, or -&amp;gt;get_all(&amp;lsquo;foo&amp;rsquo;) which is always a list.&lt;/li&gt;
&lt;li&gt;These allow you to specify which area you want to retrieve parameters from, instead of being unsure which param you are getting, if identical names are used in multiple areas.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Day 2&lt;/strong&gt; was &lt;a href=&#34;https://metacpan.org/pod/DBIx::Class&#34;&gt;DBIx::Class&lt;/a&gt; training, led by &lt;a href=&#34;https://twitter.com/PerlRacke&#34;&gt;Stefan Hornburg&lt;/a&gt; and Peter Mottram, with assistance from &lt;a href=&#34;https://twitter.com/ribasushi&#34;&gt;Peter Rabbitson&lt;/a&gt;, the DBIx::Class maintainer.&lt;/p&gt;
&lt;p&gt;DBIx::Class (a.k.a. DBIC) is an &lt;a href=&#34;https://en.wikipedia.org/wiki/Object-relational_mapping&#34;&gt;Object Relational Mapper&lt;/a&gt; for Perl. It exists to provide a standard, object-oriented way to deal with SQL queries. I am new to DBIC, and it was a lot to take in, but at least one advantage I could see was helping a project be able to change database back-ends, without having to rewrite code (cue PostgreSQL vs MySQL arguments).&lt;/p&gt;
&lt;p&gt;I took copious notes, but it seems that the true learning takes place only as one begins to implement and experiment. Without going into too much detail, some of my notes included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Existing projects can use dbicdump to quickly get a DBIC schema from an existing database, which can be modified afterwards. For a new project, it is recommended to write the schema first.&lt;/li&gt;
&lt;li&gt;DBIC allows you to place business logic in your application (not your web application), so it is easier to test (once again, the recurring theme of &lt;em&gt;Web App + App&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;ResultSet&lt;/em&gt; is a representation of a query before it happens. On any ResultSet you can call -&amp;gt;as_query to find the actual SQL that is to be executed.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://p3rl.org/DBIx::Class::Schema::Config&#34;&gt;DBIx::Class::Schema::Config&lt;/a&gt; provides credential management for DBIC, and allows you to move your DSN/username/password out of your code, which is especially helpful if you use Git or a public GitHub.&lt;/li&gt;
&lt;li&gt;DBIC is all about relationships (belongs_to, has_many, might_have, and has_one). many_to_many is not a relationship per se but a convenience.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://p3rl.org/DBIx::Class::Candy&#34;&gt;DBIx::Class::Candy&lt;/a&gt; provides prettier, more modern metadata, but cannot currently be generated by dbicdump.&lt;/li&gt;
&lt;li&gt;For deployment or migration, two helpful tools are &lt;a href=&#34;http://sqitch.org/&#34;&gt;Sqitch&lt;/a&gt; and &lt;a href=&#34;http://p3rl.org/DBIx::Class::DeploymentHandler&#34;&gt;DBIx::Class::DeploymentHandler&lt;/a&gt;. Sqitch is better for raw SQL, while DeploymentHandler is for DBIC-managed databases. These provide easy ways to migrate, deploy, upgrade, or downgrade a database.&lt;/li&gt;
&lt;li&gt;Finally, &lt;a href=&#34;http://www.rapidapp.info/&#34;&gt;RapidApp&lt;/a&gt; can read a database file or DBIC schema and provide a nice web interface for interacting with a database. As long as you define your columns properly, RapidApp can generate image fields, rich-text editors, date-pickers, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The training days were truly like drinking from a firehose, with so much good information. I am looking forward to putting this into practice!&lt;/p&gt;
&lt;p&gt;Stay tuned for my &lt;a href=&#34;/blog/2015/10/perl-dancer-conference-2015-report_30/&#34;&gt;next blog post&lt;/a&gt; on the Conference Days.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Old Dog &amp; New Tricks — Giving Interchange a New Look with Bootstrap</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/09/old-dog-new-tricks-giving-interchange/"/>
      <id>https://www.endpointdev.com/blog/2015/09/old-dog-new-tricks-giving-interchange/</id>
      <published>2015-09-02T00:00:00+00:00</published>
      <author>
        <name>Greg Hanson</name>
      </author>
      <content type="html">
        &lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;So your &lt;a href=&#34;http://www.icdevgroup.org&#34;&gt;Interchange&lt;/a&gt; based website looks like it walked out of a disco&amp;hellip; but you spent a fortune getting the back end working with all those custom applications&amp;hellip;. what to do?&lt;/p&gt;
&lt;p&gt;Interchange is currently being used in numerous very complex implementations. Trying to adapt another platform to replace Interchange is a formidable task, and in many cases the “problem” that users are trying to “solve” by replacing Interchange, can be remedied by a relatively simple face lift to the front end. One of the main attractions to Interchange in the past was its ability to scale from a small mom &amp;amp; pop eCommerce application, to a mid level support system for a larger company and its related back end systems. Once the connection to those back end systems has been created, and for as long as you use those related systems, Interchange will continue to be the most economic choice for the job. But that leaves the front end, the one that your customers see and use (with their phones and tablets) that becomes the most immediate target for “modernization”.&lt;/p&gt;
&lt;p&gt;Granted, there are new and alternate ways of accessing and presenting data and views to users, but many of those alternatives are also accessible and inter-operable with Interchange. So while there are several topics that can be investigated at length, we will focus first on a popular front end framework that can help Interchange present a modern responsive theme to the end users. That framework is &lt;a href=&#34;http://getbootstrap.com/&#34;&gt;Bootstrap&lt;/a&gt;. Bootstrap is a good basic start for breathing new life into your Interchange application. Bootstrap uses a reasonably generic approach to HTML, CSS, and JavaScript frameworking. This blends nicely with the Interchange application approach: providing a basic, repeatable, broad based and well supported foundation that can then be crafted into whatever the developer and their client may need.&lt;/p&gt;
&lt;p&gt;My intent in this post, is to give you links to the tools available to implement Bootstrap into your Interchange application, and in subsequent posts explain our development process which may help you in how you use the tools.  Like any development, there are many ways to accomplish various tasks, and knowing why certain things were done, can help.&lt;/p&gt;
&lt;p&gt;Alternatively, you may simply want to just download the “strap” catalog, and get started. At the time of this writing, Josh Lavin has been reviewing and refining the package, and the most recent version can be found at his Github account: &lt;a href=&#34;https://github.com/jdigory/strap&#34;&gt;Bootstrap template for Interchange&lt;/a&gt;. It will soon become the standard catalog template in Interchange. So, if you are not really interested in why and how this template was developed, feel free to go to the git repository in the link, clone a copy and get busy!  The &lt;a href=&#34;https://github.com/jdigory/strap/blob/master/README.md&#34;&gt;README&lt;/a&gt; is very informative, and if you are an Interchange developer you probably won’t have much trouble implementing this.&lt;/p&gt;
&lt;p&gt;If you are interested in some of the major changes that we made to the old “standard” templating approach, and how you can apply those changes to an existing catalog without having to start from scratch, stay tuned in for my next post.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Perusion has joined End Point!</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/07/perusion-has-joined-end-point/"/>
      <id>https://www.endpointdev.com/blog/2015/07/perusion-has-joined-end-point/</id>
      <published>2015-07-31T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;We are excited to announce an expansion of End Point’s ecommerce clientele and our developer ranks! The ecommerce consulting company Perusion has joined End Point. Perusion was founded in 2002 by Mike Heins and Greg Hanson. It quickly became a small powerhouse in the open source ecommerce space, focusing on Interchange, Perl, and MySQL on Linux. We were pleased to welcome Perusion in a merger with End Point at the beginning of July.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2015/07/perusion-has-joined-end-point/image-0.jpg&#34; style=&#34;width: 110px; height: 110px; float: left; margin-right: 1em&#34;/&gt;&lt;strong&gt;Mike Heins&lt;/strong&gt; is the original creator of MiniVend in 1996. In 2000, Mike’s consultancy and MiniVend were acquired by the ecommerce startup Akopia. With numerous improvements, including the addition of a new full-featured administrative back-office, the new open source ecommerce platform Interchange was created. Akopia was acquired by Red Hat in 2001, and in 2002 the Interchange project became independent, led by its creators and a group of other open source developers who maintain it to this day.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2015/07/perusion-has-joined-end-point/image-1.jpg&#34; style=&#34;width: 110px; height: 110px; float: left; margin-right: 1em&#34;/&gt;&lt;strong&gt;Greg Hanson&lt;/strong&gt; is a serial entrepreneur and business manager and has worked extensively with data systems of many types. In the mid-1990s he started a computer products ecommerce company, Valuemedia, and oversaw every aspect of its evolution. Greg joined Mike to launch Perusion in 2002, and is now a client consultant and developer. He has shepherded several businesses from small mom &amp;amp; pop operations into strong companies providing goods and services around the world.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2015/07/perusion-has-joined-end-point/image-2.jpg&#34; style=&#34;width: 110px; height: 110px; float: left; margin-right: 1em&#34;/&gt;&lt;strong&gt;Josh Lavin&lt;/strong&gt; began creating websites professionally in 1998 at his consultancy Kingdom Design. He grew his work into the ecommerce space, helping many companies sell their products online for the first time. Josh joined Perusion in 2007, bringing skills in marketing and user experience that are just as important as his development abilities. In recent years he has enjoyed moving client sites over to responsive front-end designs that work well on desktop, tablet, and mobile phone.&lt;/p&gt;
&lt;p&gt;Perusion’s development and hosting clients have joined End Point as well. Some of the noteworthy sites that bear mentioning here are &lt;a href=&#34;https://www.aws.org/&#34;&gt;American Welding Society&lt;/a&gt;, &lt;a href=&#34;https://www.bluestoneperennials.com/&#34;&gt;Bluestone Perennials&lt;/a&gt;, &lt;a href=&#34;http://store.vervante.com/&#34;&gt;Vervanté&lt;/a&gt;, &lt;a href=&#34;https://www.bulkherbstore.com/&#34;&gt;Bulk Herb Store&lt;/a&gt;, &lt;a href=&#34;http://www.northernsun.com/&#34;&gt;Northern Sun&lt;/a&gt;, &lt;a href=&#34;http://www.pennherb.com/&#34;&gt;Penn Herb Company&lt;/a&gt;, &lt;a href=&#34;http://www.airdelights.com/&#34;&gt;Air Delights&lt;/a&gt;, and &lt;a href=&#34;https://www.solarpathfinder.com/&#34;&gt;Solar Pathfinder&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At End Point we got our start in the industry in 1995 by creating dynamic database-backed websites for our clients. Our tools of choice in those early days were Linux, Apache, msql and MySQL, and Perl. In the late 1990s we began focusing more on ecommerce websites specifically, and we added MiniVend and Interchange to the mix.&lt;/p&gt;
&lt;p&gt;Later we branched out into PostgreSQL, Ruby on Rails and Spree, Python and Django, Perl Dancer, NodeJS, and other platforms, while we continued to support and enhance Interchange and the sites running on it. Today we still host and develop many successful ecommerce sites running Interchange, taking many thousands of orders worth millions of dollars every day. So we are delighted to have Perusion join us as we to continue to grow the Interchange-based part of our business.&lt;/p&gt;
&lt;p&gt;We have already seen constructive collaboration in this merger, with longtime End Point employees able to add more helping hands to Perusion projects and further breadth of capabilities and depth of support, Perusion developers bringing their expertise to bear, and Perusion contacts leading to new projects and new business.&lt;/p&gt;
&lt;p&gt;Perusion has always believed in going above and beyond the call of duty and has made their clients’ success their own goal. Likewise, we at End Point feel this is more than a business, and we value the personal relationships we have developed with each other and our clients over the years. We look forward to the new possibilities that are now available through this change, and are glad to welcome Perusion on board at End Point!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Protect Interchange Passwords with Bcrypt</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/04/protect-interchange-passwords-with/"/>
      <id>https://www.endpointdev.com/blog/2015/04/protect-interchange-passwords-with/</id>
      <published>2015-04-28T00:00:00+00:00</published>
      <author>
        <name>Mark Johnson</name>
      </author>
      <content type="html">
        &lt;p&gt;Interchange default configurations have not done a good job of keeping up with the best available password security for its user accounts. Typically, there are two account profiles associated with a standard Interchange installation: one for admin users (access table) where the password is stored using Perl’s crypt() command (bad); and one for customers (userdb) where the password isn’t encrypted at all (even worse). Other hashing algorithms have long been available (MD5, salted MD5, SHA1) but are not used by default and have for some time not been useful protection. Part of this is convenience (tools for retrieving passwords and ability to distribute links into user assets) and part is inertia. And no small part was the absence of a strong cryptographic option for password storage until the &lt;a href=&#34;/blog/2014/07/interchange-582-release-with-bcrypt/&#34;&gt;addition of Bcrypt&lt;/a&gt; to the account management module.&lt;/p&gt;
&lt;p&gt;The challenge we face in protecting passwords is that hardware continues to advance at a rapid rate, and with more computational power and storage capacity, brute-force attacks become increasingly effective and widely available. Jeff Jarmoc’s &lt;a href=&#34;http://chargen.matasano.com/chargen/2015/3/26/enough-with-the-salts-updates-on-secure-password-schemes.html&#34;&gt;Enough with the Salts&lt;/a&gt; provides some excellent discussion and background on the subject. To counter the changing landscape, the main line of defense moves toward ensuring that the work required to create and test a given stored password is too expensive, too time-consuming, for brute-force attacks to be profitable.&lt;/p&gt;
&lt;p&gt;One of the best options for handling encrypted password storage with a configurable “hardware cost” is Bcrypt. We chose to integrate Bcrypt into Interchange over other options primarily because of its long history of operation with no known exploits, and its cost parameter that allows an administrator to advance the work required to process a password slowly over time as hardware continues to increase in efficiency. The cost feature introduces an exponential increase in calculation iterations (i.e., required processing power and time) as powers of 2, from 1 (2 iterations, essentially no cost) to 31 (2^31, or 2,147,483,648 iterations). Ideally an administrator would want to identify a cost that causes no perceptible penalty to end users, but would be such a burden to any brute-force attack as to have no worthwhile return on investment to crack.&lt;/p&gt;
&lt;p&gt;Converting an existing user base from any of the existing encryption schemes to Bcrypt is trivial in Interchange. The existing UserDB profile is changed to the “bcrypt” option and the “promote” boolean set to true. Promote allows your users to continue to validate against their existing stored password, but after the next access will upgrade their storage to the Bcrypt password. In the mean time, a backend process could be developed using the construct_bcrypt() routine in Vend::UserDB to update all outstanding accounts prior to being updated organically.&lt;/p&gt;
&lt;p&gt;If the switch on the front end involves going from no encryption to any encrypted storage, including Bcrypt, and your front end uses the default tools for retrieving lost passwords, you’ll also need to construct some new code for resetting passwords instead. There is no such facility for the admin, and since the admin accounts are typically far more valuable than the front end accounts, making the change for the admin should be the first priority and have the least effort involved.&lt;/p&gt;
&lt;p&gt;Switching accounts to Bcrypt password storage is a simple, effective means for increasing protection on your users’ and business’ information. Every bit as importantly, it also helps protect your business’ reputation, that can be severely damaged by a data breech. Lastly, in particular for your admin accounts, Bcrypt password storage is useful in meeting PCI DSS requirements for strong password hashing.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Advanced Product Filtering in Ecommerce</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/03/advanced-product-filtering-in-ecommerce/"/>
      <id>https://www.endpointdev.com/blog/2015/03/advanced-product-filtering-in-ecommerce/</id>
      <published>2015-03-18T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;One of my recent projects for &lt;a href=&#34;https://www.papersource.com/&#34;&gt;Paper Source&lt;/a&gt; has been to introduce advanced product filtering (or faceted filtering). Paper Source runs on &lt;a href=&#34;http://www.icdevgroup.org/i/dev&#34;&gt;Interchange&lt;/a&gt;, a perl-based open source ecommerce platform that End Point has been involved with (as core developers &amp;amp; maintainers) for many years.&lt;/p&gt;
&lt;p&gt;In the case of Paper Source, personalized products such as &lt;a href=&#34;https://www.papersource.com/personalized/wedding-invitations.html&#34;&gt;wedding invitations&lt;/a&gt; and &lt;a href=&#34;https://www.papersource.com/personalized/wedding-save-the-dates/photo-save-the-dates.html&#34;&gt;save the dates&lt;/a&gt; have advanced filtering to filter by print method, number of photos, style, etc. Advanced product filtering is a very common feature in ecommerce systems with a large number of products that allows a user to narrow down a set of products to meet their needs. Advanced product filtering is not unlike faceted filtering offered by many search engines, which similarly allows a user to narrow down products based on specific tags or facets (e.g. see many Amazon filters on the left column). In the case of Paper Source, I wrote the filtering code layered on top of the current navigation. Below I’ll go through some of the details with small code examples.&lt;/p&gt;
&lt;h3 id=&#34;data-model&#34;&gt;Data Model&lt;/h3&gt;
&lt;p&gt;The best place to start is the data model. A simplified existing data model that represents product taxonomy might look like the following:&lt;/p&gt;
&lt;img border=&#34;0&#34; src=&#34;/blog/2015/03/advanced-product-filtering-in-ecommerce/image-0.png&#34;/&gt;
&lt;p&gt;Basic data model linking categories to products.&lt;/p&gt;
&lt;p&gt;The existing data model links products to categories via a &lt;a href=&#34;https://en.wikipedia.org/wiki/Many-to-many_%28data_model%29&#34;&gt;many-to-many&lt;/a&gt; relationship. This is fairly common in the ecommerce space – while looking at a specific category often identified by URL slug or id, the products tied to that category will be displayed.&lt;/p&gt;
&lt;p&gt;And here’s where we go with the filtering:&lt;/p&gt;
&lt;img border=&#34;0&#34; src=&#34;/blog/2015/03/advanced-product-filtering-in-ecommerce/image-1.png&#34;/&gt;
&lt;p&gt;Data model with filtering layered on top of existing category to product relationship.&lt;/p&gt;
&lt;p&gt;Some notes on the above filtering data model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;filters contains a list of all the files. Examples of entries in this table include “Style”, “Color”, “Size”&lt;/li&gt;
&lt;li&gt;filters_categories links filters to categories, to allow finite control over which filters show on which category pages, in what order. For example, this table would link category “Shirts” to filters “Style”, “Color”, “Size” and the preferred sort order of those filters.&lt;/li&gt;
&lt;li&gt;filter_options includes all the options for a specific filter. Examples here for various options include “Large”, “Medium”, and “Small”, all linked to the “Size” filter.&lt;/li&gt;
&lt;li&gt;filter_options_products links filter options to a specific product id with a many to many relationship.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;filter-options-exclusivity&#34;&gt;Filter Options Exclusivity&lt;/h3&gt;
&lt;p&gt;One thing to consider before coding are the business rules pertaining to filter option exclusivity. If a product is assigned to one filter option, can it also have another filter option for that same filter type? IE, if a product is marked as blue, can it also be marked as red? When a user filters by color, can they filter to select products that are both blue and red? Or, if a product is is blue, can it not have any other filter options for that filter? In the case of Paper Source product filtering, we went with the former, where filter options are not exclusive to each other.&lt;/p&gt;
&lt;p&gt;A real-life example of filter non-exclusivity is how &lt;a href=&#34;https://www.papersource.com/&#34;&gt;Paper Source&lt;/a&gt; filters &lt;a href=&#34;https://www.papersource.com/personalized/wedding-invitations.html&#34;&gt;wedding invitations&lt;/a&gt;. Products are filtered by print method and style. Because some products have multiple print methods and styles, non-exclusivity allows a user to narrow down to a specific combination of filter options, e.g. a wedding invitation that is both tagged as “foil &amp;amp; embossed” and “vintage”.&lt;/p&gt;
&lt;img border=&#34;0&#34; src=&#34;/blog/2015/03/advanced-product-filtering-in-ecommerce/image-2.png&#34;/&gt;
&lt;h3 id=&#34;url-structure&#34;&gt;URL Structure&lt;/h3&gt;
&lt;p&gt;Another thing to determine before coding is the URL structure. The URL must communicate the current category of products and current filter options (or what I refer to as active/activated filters).&lt;/p&gt;
&lt;p&gt;I designed the code to recognize one component of the URL path as the category slug, and the remaining paths to map to the various filter option url slugs. For example, a URL for the category of shirts is “/shirts”, a URL for large shirts “/shirts/large”, and the URL for large blue shirts “/shirts/blue/large”. The code not only has to accept this format, but it also must create consistently ordered URLs, meaning, we don’t want both “/shirts/blue/large” and “/shirts/large/blue” (representing the same content) to be generated by the code. Here’s what simplified pseudocode might look like to retrieve the category and set the activated filters:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;@url_paths&lt;/span&gt; = &lt;span style=&#34;color:#038&#34;&gt;split&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$request_url&lt;/span&gt;); &lt;span style=&#34;color:#888&#34;&gt;#url paths is e.g. /shirts/blue/large&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$category_slug&lt;/span&gt; = &lt;span style=&#34;color:#038&#34;&gt;shift&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;@url_paths&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# find Category where slug = $category_slug&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# redirect if not found&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# @url_paths is active filters&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;applying-the-filtering&#34;&gt;Applying the Filtering&lt;/h3&gt;
&lt;p&gt;Next, we need a couple things to happen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If there is an activated filter for any filter option, apply it.&lt;/li&gt;
&lt;li&gt;Generate URLs to toggle filter options.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, all products are retrieved in this category with a query like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SELECT products.*,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  COALESCE((SELECT GROUP_CONCAT(fo.url_slug) FROM filters_options_item foi
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    JOIN filters_options fo ON fo.id = foi.filter_option_id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    WHERE foi.product_id = products.id), &amp;#39;&amp;#39;) AS filters
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FROM products
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;JOIN categories_products cp ON cp.product_id = products.id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;JOIN categories c ON c.id = cp.category_id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WHERE c.url_slug = ?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next is where the code gets pretty hairy, so instead I’ll try to explain with pseudocode:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#@filters = all applicable filters for current category&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# loop through @filters&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;# loop through filter options for this filter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;# filter product results to include any selected filter options for this filter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;# if there are no filter options selected for this filter, include all products&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;# build the url for each filter option, to toggle the filter option (on or off)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# loop through @filters (yes, a second time)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;# loop through filter options for this filter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;# count remaining products for each filter option, if none, set filter option to inactive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;# build the final url for each filter option, based on all filters turned on and off&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My pseudocode shows that I iterate through the filters twice, first to apply the filter and determine the base URL to toggle each filter option, and second to count the remaining filtered products and build the URL to toggle each filter. The output of this code is a) a set of filtered products and b) a set of ordered filters and filter options with corresponding counts and links to toggle on or off.&lt;/p&gt;
&lt;p&gt;Here’s a more specific example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let’s say we have a set of shirts, with the following filters &amp;amp; options: Style (Long Sleeve, Short Sleeve), Color (Red, Blue), Size (Large, Medium, Small).&lt;/li&gt;
&lt;li&gt;A URL request comes in for /shirts/blue/large&lt;/li&gt;
&lt;li&gt;The code recognizes this is the shirts category and retrieves all shirts.&lt;/li&gt;
&lt;li&gt;First, we look at the style filter. No style filter is active in this request, so to toggle these filters on, the activation URLs must include “longsleeve” and “shortsleeve”. No products are filtered out here.&lt;/li&gt;
&lt;li&gt;Next, we look at the color filter. The blue filter option is active because it is present in the URL. In this first loop, products not tagged as blue are removed from the set of products. To toggle the red option on, the activation URL must include “red”, and to toggle the blue filter off, the URL must not include “blue”, which is set here.&lt;/li&gt;
&lt;li&gt;Next, we look at the size filter. Products not tagged as large are removed from the set of products. Again, the large filter has to be toggled off in the URL because it is active, and the medium and small filter need to be toggled on.&lt;/li&gt;
&lt;li&gt;In the second pass through filters, the remaining items applicable to each filter option are counted, for long sleeve, short sleeve, red, medium, and small options. And the URLs are built to turn on and off all filter options (e.g. applying the longsleeve filter will yield the URL “/shirts/longsleeve/blue/large”, applying the red filter will yield the URL “/shirts/blue/red/large”, turning off the blue filter will yield the URL “/shirts/large”).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The important thing to note here is the double pass through filters is required to build non-duplicate URLs and to determine the product count after all filter options have been applied. This isn’t simple logic, and of course changing the business rules like exclusivity will change the loop behavior and URL logic.&lt;/p&gt;
&lt;h3 id=&#34;alternative-approaches&#34;&gt;Alternative Approaches&lt;/h3&gt;
&lt;p&gt;Finally, a few notes regarding alternative approaches here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rather than going with the blacklist approach described here, one could go with a whitelist approach where a set of products is built up based on the filter options set.&lt;/li&gt;
&lt;li&gt;Filtering could be done entirely via AJAX, in which case URL structure may not be a concern.&lt;/li&gt;
&lt;li&gt;If the data is simple enough, products could potentially be filtered in the database query itself. In our case, this wasn’t feasible since we generate product filter option details from a number of product attributes, not just what is shown in the simplified product filter data model above.&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Interchange Loop Optimization</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/02/interchange-loop-optimization/"/>
      <id>https://www.endpointdev.com/blog/2015/02/interchange-loop-optimization/</id>
      <published>2015-02-09T00:00:00+00:00</published>
      <author>
        <name>Mark Johnson</name>
      </author>
      <content type="html">
        &lt;p&gt;It’s important to understand both how loops work in Interchange and the (very) fundamental differences between interpolating Interchange tag language (ITL) and the special loop tags (typically referred to as [PREFIX-*] in the literature). Absent this sometimes arcane knowledge, it is very easy to get stuck with inefficient loops even with relatively small loop sets. I’ll discuss both the function of loops and interpolation differences between the tag types while working through a [query] example. While all loop tags&amp;ndash;[item-list], [loop], [search-list], and [query]&amp;ndash;process similarly, it is to [query] where most complex loops will gravitate over time (to optimize the initiation phase of entering the loop) and where we have the most flexibility for coming up with alternative strategies to mitigate sluggish loop-processing.&lt;/p&gt;
&lt;h3 id=&#34;loop-processing&#34;&gt;Loop Processing&lt;/h3&gt;
&lt;p&gt;All loop tags are &lt;em&gt;container&lt;/em&gt; tags in Interchange, meaning they have an open and close tag, and in between is the body. Only inside this body is it valid to define [PREFIX-&lt;em&gt;] tags (notable exception of [PREFIX-quote] for the sql arg of [query]). This is because the [PREFIX-&lt;/em&gt;] tags are not true ITL. They are tightly coupled with the structure of the underlying rows of data and they are processed by distinct, optimized regular expressions serially. Outside the context of the row data from a result set, they are meaningless.&lt;/p&gt;
&lt;p&gt;Moreover, the body of a loop tag is slurped into a scalar variable (as all bodies of container tags are handled via the ITL parser) and for each row in the record set of the loop, the contents are acted upon according to the [PREFIX-&lt;em&gt;] tags defined within the body. The first important distinction to recognize here is, the per-row action on this scalar is limited to &lt;em&gt;only&lt;/em&gt; the [PREFIX-&lt;/em&gt;] tags. The action occurring at loop time ignores any embedded ITL.&lt;/p&gt;
&lt;p&gt;At the end of each row’s processing, the copy of the body tied to that one row is then concatenated to the results of all previous rows thus processed. For a loop with N rows (assuming no suppression by [if-PREFIX-*] conditionals) that means every instance of ITL originally placed into the loop body is now present N times in the fully assembled body string. Simple example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[loop list=&amp;#39;1 2 3&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[tmp junk][loop-code][/tmp]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if scratch junk == 2]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;I declare [loop-code] to be special!
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[else]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Meh. [loop-code] is ordinary.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/else]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/loop]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once this result set with N=3 is processed, but &lt;strong&gt;before&lt;/strong&gt; Interchange returns the results, the assembled return looks like the following string:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[tmp junk]1[/tmp]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if scratch junk == 2]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;I declare 1 to be special!
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[else]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Meh. 1 is oridinary
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/else]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[tmp junk]2[/tmp]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if scratch junk == 2]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;I declare 2 to be special!
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[else]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Meh. 2 is oridinary
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/else]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[tmp junk]3[/tmp]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if scratch junk == 2]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;I declare 3 to be special!
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[else]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Meh. 3 is oridinary
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/else]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Some important observations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It doesn’t take much ITL to turn a loop body into a monster interpolation process. One must consider the complexity of the ITL in the body by a factor of the number of rows (total, or the “ml” matchlimit value).&lt;/li&gt;
&lt;li&gt;ITL does &lt;em&gt;nothing&lt;/em&gt; to short-circuit action of the [PREFIX-*] tags. Having [PREFIX-param], [PREFIX-calc], etc. inside an ITL [if] means all those loop tags parse regardless of the truth of the if condition.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;itl-vs-loop-tags&#34;&gt;ITL vs. Loop Tags&lt;/h3&gt;
&lt;p&gt;ITL maps to routines, both core and user-defined, determined at compile time. They are processed in order of discovery within the string handed to the ::interpolate_html() routine and have varied and complex attributes that must be resolved for each individual tag. Further, for many (if not most) tags, the return value is itself passed through a new call to ::interpolate_html(), acting on all embedded tags, in an action referred to as &lt;em&gt;reparse&lt;/em&gt;. There is, relatively speaking, a good deal of overhead in processing through ::interpolate_html(), particularly with reparse potentially spawning off a great many more ::interpolate_html() calls.&lt;/p&gt;
&lt;p&gt;Loop tags, by contrast, map to a pre-compiled set of regular expressions. In contrast to crawling the string and acting upon the tags in the order of discovery, each regex in turn is applied globally to the string. The size of the string is limited to the exact size of the single loop body, and there is no analogue to ITL’s reparse. Further, given this processing pattern, a careful observer might have noted that the order of operations can impact the structure. Specifically, tags processed earlier cannot depend on tags processed later. E.g., [PREFIX-param] processes ahead of [PREFIX-pos], and so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if-PREFIX-pos 2 eq [PREFIX-param bar]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;will work, but:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if-PREFIX-param bar eq [PREFIX-pos 2]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;will not. While the above is a somewhat contrived exampled, the impacts of loop tag processing can be more easily seen in an example using [PREFIX-next]:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[PREFIX-next][PREFIX-param baz][/PREFIX-next]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Code I only want to run when baz is false, like this [PREFIX-exec foo][/PREFIX-exec] call&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Because [PREFIX-next] is the absolute last loop tag to run, &lt;em&gt;every other loop tag in the block is run&lt;/em&gt; before the next condition is checked. All [PREFIX-next] does is suppress the resulting body from the return, unlike Perl’s next, which short-circuits the remaining code in the loop block.&lt;/p&gt;
&lt;h3 id=&#34;an-optimization-example&#34;&gt;An Optimization Example&lt;/h3&gt;
&lt;p&gt;As long as you’re familiar with the idiosyncracies of [PREFIX-*] tags, you should make every effort to use them instead of ITL because they are substantially lighter weight and faster to process. A classic case that can yield remarkable performance gains is to directly swap an embedded [perl] or [calc] block with an equivalent [PREFIX-calc] block.&lt;/p&gt;
&lt;p&gt;Let’s take a typical query with little consideration given to whether we use loop tags or ITL, not unlike many I’ve seen where the resource has just become unusably slow. This code originally was developed processing 50 records per page view, but the team using it has requested over time to increase that count.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[query
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    list=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ml=500 [comment]Ouch! That&amp;#39;s a big N[/comment]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sql=&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SELECT *
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        FROM transactions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        WHERE status = &amp;#39;pending&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ORDER BY order_date DESC
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Order [sql-param order_number]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if global DEVELOPMENT]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Show [sql-exec stats_crunch][sql-param stats][/sql-exec], only of interest to developers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: [convert-date format=&amp;#34;%b %d, %Y at %T&amp;#34;][sql-param order_date][/convert-date]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if cgi show_inventory]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Inv:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if cgi show_inventory eq all]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Shipped: [either][sql-param is_shipped][or]pending[/either]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Count: [inventory type=shipped sku=[sql-param sku]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* On Hand: [inventory type=onhand sku=[sql-param sku]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Sold: [inventory type=sold sku=[sql-param sku]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Shipping: [inventory type=shipping sku=[sql-param sku]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Order details:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;a href=&amp;#34;[area
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                href=order_view
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                form=&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    order=[sql-param order_number]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    show_status=[either][cgi show_status][or]active[/either]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    show_inventory=[cgi show_inventory]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ]&amp;#34;&amp;gt;View [sql-param order_number]&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/query]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Considering this block out of context, it doesn’t seem all that unreasonable. However, let’s look at some of the pieces individually and see what can be done.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We use [if] in 3 different circumstances in the block. However, those values they test are static. They don’t change on any iteration. (We are excluding the potential of any other ITL present in the block from changing their values behind the scenes.)&lt;/li&gt;
&lt;li&gt;[convert-date] may be convenient, but it is only one of a number of ways to address date formatting. Our database itself almost certainly has date-formatting routines, but one of the benefits of [convert-date] is you could have a mixed format underlying the data and it can make sense out of the date to some degree. So perhaps that’s why the developer has used [convert-date] here.&lt;/li&gt;
&lt;li&gt;Good chance that stats_crunch() is pretty complicated and that’s why the developer wrote a catalog or global subroutine to handle it. Since we only want to see it in the development environment, it’d be nice if it only ran when it was needed. Right now, because of ITL happening on reparse, stats_crunch() fires for every row even if we have no intention of using its output.&lt;/li&gt;
&lt;li&gt;We need that link to view our order, but on reparse it means ::interpolate_html() has to parse 500 [area] tags along with [either] and [cgi] x 500. All of these tags are lightweight, but parsing numbers are really going to catch up to us here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our goal here is to replace any ITL we can with an equivalent use of a loop tag or, absent the ability to remove ITL logically, to wrap that ITL into a subroutine that can itself be called in loop context with [PREFIX-exec]. The first thing I want to address are those [if] and [either] tags, the lowest hanging fruit:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[query
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    list=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ml=500
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sql=&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;b&amp;gt;SELECT *,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;#39;[if global DEVELOPMENT]1[/if]&amp;#39; AS is_development,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            [sql-quote][cgi show_inventory][/sql-quote] AS show_inventory,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            COALESCE(is_shipped,&amp;#39;pending&amp;#39;) AS show_inventory_shipped&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        FROM transactions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        WHERE status = &amp;#39;pending&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ORDER BY order_date DESC
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Order [sql-param order_number]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;b&amp;gt;[if-sql-param is_development]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Show [sql-exec stats_crunch][sql-param stats][/sql-exec], only of interest to developers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;b&amp;gt;[/if-sql-param]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: [convert-date format=&amp;#34;%b %d, %Y at %T&amp;#34;][sql-param order_date][/convert-date]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;b&amp;gt;[if-sql-param show_inventory]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Inv:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;b&amp;gt;[if-sql-param show_inventory eq all]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Shipped: &amp;lt;b&amp;gt;[sql-param show_inventory_shipped]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Count: [inventory type=shipped sku=[sql-param sku]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;b&amp;gt;[/if-sql-param]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* On Hand: [inventory type=onhand sku=[sql-param sku]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Sold: [inventory type=sold sku=[sql-param sku]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Shipping: [inventory type=shipping sku=[sql-param sku]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;b&amp;gt;[/if-sql-param]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Order details:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;a href=&amp;#34;[area
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                href=order_view
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                form=&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    order=[sql-param order_number]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    show_status=[either][cgi show_status][or]active[/either]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    show_inventory=[cgi show_inventory]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ]&amp;#34;&amp;gt;View [sql-param order_number]&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/query]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By moving those evaluations into the SELECT list of the query, we’ve reduced the number of interpolations to arrive at those static values to 1 or, in the case of the [either] tag, 0 as we’ve offloaded the calculation entirely to the database. If is_shipped could be something perly false but not null, we would have to adjust our field accordingly, but in either case could still be easily managed as a database calculation. Moreover, by swapping in [if-sql-param is_development] for [if global DEVELOPMENT], we have kept stats_crunch() from running at all when in the production environment.&lt;/p&gt;
&lt;p&gt;Next, we’ll consider [convert-date]:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: [convert-date format=&amp;#34;%b %d, %Y at %T&amp;#34;][sql-param order_date][/convert-date]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My first attempt would be to address this similarly to the [if] and [either] conditions, and try to render the formatted date from a database function as an aliased field. However, let’s assume the underlying structure of the data varies and that’s not easily accomplished, and we still want [convert-date]. Luckily, Interchange supports that same tag as a filter, and [PREFIX-filter] is a loop tag:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: &amp;lt;b&amp;gt;[sql-filter convert_date.&amp;#34;%b %d, %Y at %T&amp;#34;]&amp;lt;/b&amp;gt;[sql-param order_date]&amp;lt;b&amp;gt;[/sql-filter]&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;[PREFIX-filter] is very handy to keep in mind as many transformation tags have a filter wrapper for them. E.g., [currency] -&amp;gt; [PREFIX-filter currency]. And if the one you’re looking at doesn’t, you can build your own, easily.&lt;/p&gt;
&lt;p&gt;Now to look at that [inventory] tag. The most direct approach assumes that the code inside [inventory] can be run in Safe, which often it can even if [inventory] is global. However, if [inventory] does run-time un-Safe things (such as creating an object) then it may not be possible. In such a case, we would want to create a global sub, like our hypothetical stats_crunch(), and invoke it via [PREFIX-exec]. However, let us assume we can safely (as it were) invoke it via the $Tag object to demonstrate another potent loop option: [PREFIX-sub].&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if-sql-param show_inventory]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;b&amp;gt;[sql-sub show_inventory]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    my $arg = shift;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    return $Tag-&amp;gt;inventory({ type =&amp;gt; $arg, sku =&amp;gt; $Row-&amp;gt;{sku} });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/sql-sub]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Inv:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if-sql-param show_inventory eq all]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Shipped: [sql-param show_inventory_shipped]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Count: &amp;lt;b&amp;gt;[sql-exec show_inventory]shipped[/sql-exec]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if-sql-param]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* On Hand: &amp;lt;b&amp;gt;[sql-exec show_inventory]on_hand[/sql-exec]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Sold: &amp;lt;b&amp;gt;[sql-exec show_inventory]sold[/sql-exec]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Shipping: &amp;lt;b&amp;gt;[sql-exec show_inventory]shipping[/sql-exec]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if-sql-param]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let’s go over what this gives us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[PREFIX-sub] creates an in-line catalog sub that is compiled at the start of processing, before looping actually begins. As such, the [PREFIX-sub] definitions can occur anywhere within the loop body and are then removed from the body to be parsed.&lt;/li&gt;
&lt;li&gt;The body of the [PREFIX-exec] is passed to the sub as the first argument. We use that here for our static values to the &amp;ldquo;type&amp;rdquo; arg. If we also wanted to access [sql-param sku] from the call, we would have to include that in the body and set up a parser to extract it out of the one (and only) arg we can pass in. Instead, we can reference the $Row hash within the sub body just as we can do when using a [PREFIX-calc], with one minor adjustment to our [query] tag&amp;ndash;we have to indicate to [query] we are operating on a row-hash basis instead of the default row-array basis. We do that by adding the hashref arg to the list:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[query
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    list=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ml=500
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;b&amp;gt;hashref=1&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;We still have access to the full functionality of [inventory] but we’ve removed the impact of having to parse that tag 2000 times (in the worst-case scenario) if left as ITL in the query body. If we run into Safe issues, that same sub body can either be created as a pre-compiled global sub or, if available, we can set our catalog AllowGlobal in which case catalog subs will no longer run under Safe.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, all we have left to address is [area] and its args which themselves have ITL. I will leverage [PREFIX-sub] again as an easy way to manage the issue:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;b&amp;gt;[sql-sub area_order_view]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    my $show_status = $CGI-&amp;gt;{show_status} || &amp;#39;active&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    return $Tag-&amp;gt;area({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        href =&amp;gt; &amp;#39;order_view&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        form =&amp;gt; &amp;#34;order=$Row-&amp;gt;{order_number}\n&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              . &amp;#34;show_status=$show_status\n&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              . &amp;#34;show_inventory=$CGI-&amp;gt;{show_inventory}&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/sql-sub]&amp;lt;/b&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Order details:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;a href=&amp;#34;&amp;lt;b&amp;gt;[sql-exec area_order_view][/sql-exec]&amp;lt;/b&amp;gt;&amp;#34;&amp;gt;View [sql-param order_number]&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By packaging all of [area]’s requirements into the sub body, I can address all of the ITL at once.&lt;/p&gt;
&lt;p&gt;So now, let’s put together the entire [query] rewrite to see the final product:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[query
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    list=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ml=500
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hashref=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sql=&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SELECT *,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;#39;[if global DEVELOPMENT]1[/if]&amp;#39; AS is_development,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            [sql-quote][cgi show_inventory][/sql-quote] AS show_inventory,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            COALESCE(is_shipped,&amp;#39;pending&amp;#39;) AS show_inventory_shipped
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        FROM transactions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        WHERE status = &amp;#39;pending&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ORDER BY order_date DESC
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Order [sql-param order_number]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if-sql-param is_development]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Show [sql-exec stats_crunch][sql-param stats][/sql-exec], only of interest to developers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if-sql-param]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: [sql-filter convert_date.&amp;#34;%b %d, %Y at %T&amp;#34;][sql-param order_date][/sql-filter]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if-sql-param show_inventory]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[sql-sub show_inventory]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    my $arg = shift;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    return $Tag-&amp;gt;inventory({ type =&amp;gt; $arg, sku =&amp;gt; $Row-&amp;gt;{sku} });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/sql-sub]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Inv:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[if-sql-param show_inventory eq all]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Shipped: [sql-param show_inventory_shipped]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Count: [sql-exec show_inventory]shipped[/sql-exec]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if-sql-param]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* On Hand: [sql-exec show_inventory]on_hand[/sql-exec]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Sold: [sql-exec show_inventory]sold[/sql-exec]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Shipping: [sql-exec show_inventory]shipping[/sql-exec]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/if-sql-param]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[sql-sub area_order_view]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    my $show_status = $CGI-&amp;gt;{show_status} || &amp;#39;active&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    return $Tag-&amp;gt;area({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        href =&amp;gt; &amp;#39;order_view&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        form =&amp;gt; &amp;#34;order=$Row-&amp;gt;{order_number}\n&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              . &amp;#34;show_status=$show_status\n&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              . &amp;#34;show_inventory=$CGI-&amp;gt;{show_inventory}&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/sql-sub]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Order details:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;a href=&amp;#34;[sql-exec area_order_view][/sql-exec]&amp;#34;&amp;gt;View [sql-param order_number]&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/query]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Voila! Our new query body is functionally identical to the original body, though admittedly a little more complicated to set up. However, the trade-off in efficiency is likely to be substantial.&lt;/p&gt;
&lt;p&gt;I recently worked on a refactor for a client that was overall very similar to the above example, with a desired N value of 250. The code prior to refactoring took ~70s to complete. Once we had completed the refactor using the same tools as I’ve identified here, we brought down processing time to just under 3s, losing no functionality.&lt;/p&gt;
&lt;p&gt;Time taken optimizing Interchange loops will almost always pay dividends.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Interchange 5.8.2 Release with Bcrypt Encryption Support</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/07/interchange-582-release-with-bcrypt/"/>
      <id>https://www.endpointdev.com/blog/2014/07/interchange-582-release-with-bcrypt/</id>
      <published>2014-07-09T00:00:00+00:00</published>
      <author>
        <name>Mark Johnson</name>
      </author>
      <content type="html">
        &lt;p&gt;The most recent release of Interchange contains support for adding encryption to user passwords with &lt;a href=&#34;http://bcrypt.sourceforge.net/&#34;&gt;bcrypt&lt;/a&gt;. This provides for a significant improvement over the previously supported encryption options. Whether you are starting with a new user table, or have a long-established user table with any of the other supported encryption types, you can take advantage of this enhancement to Vend::UserDB.&lt;/p&gt;
&lt;p&gt;In order to utilize bcrypt, you will need to have successfully installed the &lt;a href=&#34;http://search.cpan.org/~jaitken/Digest-Bcrypt-1.0.1/lib/Digest/Bcrypt.pm&#34;&gt;Digest::Bcrypt&lt;/a&gt; and &lt;a href=&#34;http://search.cpan.org/~vipul/Crypt-Random-1.25/lib/Crypt/Random.pm&#34;&gt;Crypt::Random&lt;/a&gt; modules. Once installed, it is recommended (though not required, ironically) you add these as Required modules to interchange.cfg to ensure Interchange can find them and will report back immediately if not:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Require module Digest::Bcrypt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Require module Crypt::Random&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To use bcrypt, configure your UserDB directive with the following options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bcrypt—​set to 1&lt;/li&gt;
&lt;li&gt;cost—​desired cost between 1 and 31 (optional; current default is 13)&lt;/li&gt;
&lt;li&gt;bcrypt_pepper—​random string used to apply a unique padding pattern (also optional, but strongly recommended)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To encourage adoption of bcrypt, there are two mechanisms available that allow a user table already using another encryption type to convert to using bcrypt. The first option is to add the “promote” directive—​set to 1—​to your UserDB configuration. This will cause passwords stored by any other encryption type to update to bcrypt upon the next successful access of that user account. This causes no disruption to site operations or your users, but it does mean you have to allow full adoption of bcrypt to advance organically as your users return to your site over time.&lt;/p&gt;
&lt;p&gt;The second option builds on “promote” by allowing the developer to construct a background process to slurp in the existing passwords stored by another encryption type and generate a bcrypt storage value of the &lt;em&gt;encrypted&lt;/em&gt; password, by utilizing the construct_bcrypt() subroutine in Vend::UserDB. construct_bcrypt() takes a single hashref argument with a required key “password” and optional keys “type” and “profile”. In this use case, password holds the encrypted password extracted from the existing user table and type contains one of “sha1”, “md5”, “md5_salted”, or “crypt”, corresponding to the encryption algorithm used to produce the password originally. The profile key maps to the profile name assigned to control this user table in the UserDB directive. Normally, this will be either “default”, which controls the userdb table, or “ui”, which controls the access table. Note that the profile used &lt;em&gt;must&lt;/em&gt; already be configured to use bcrypt, or construct_bcrypt() will die. If profile is omitted, defaults to “default”. It is anticipated the developer would construct an Interchange job to query all the users and encrypted passwords still stored by the previous algorithm, pass each into construct_bcrypt(), and update the source record’s password field with the return value from construct_bcrypt().&lt;/p&gt;
&lt;p&gt;For any configuration of UserDB utilizing bcrypt, it is recommended to enable “promote”. Not only is it a painless way to begin the transition of your encrypted user table to use bcrypt, it also ensures continuing access for your users even if you advance with the second option. For a large user table, it may take weeks for your background process to complete the transition. In the mean time, any users who access the site before their passwords are updated will still have access to the site using promote. Lastly, over time you will likely want to increase the cost parameter of bcrypt to keep pace with hardware advances. By using promote, users returning to your site will have their passwords updated to use the current cost in addition to new users being stored at the current cost. In short, if you bcrypt, use promote.&lt;/p&gt;
&lt;p&gt;Example configuration changes for UserDB using bcrypt for the access table. Replace:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UserDB  ui  crypt 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UserDB  ui  bcrypt         1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UserDB  ui  promote        1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UserDB  ui  cost           13
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UserDB  ui  bcrypt_pepper  mrPIQ4$.qgJrbvD(5CStB.p8b8ABbpl+:0.`I`*J`)j{=7KY*b(M@QXXg+9B*-b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course, your bcrypt_pepper will be some suitable random string you generate and keep private. The above is just an example.&lt;/p&gt;
&lt;p&gt;Also be aware that the password storage will be larger than any previous stored passwords, up to 63 characters in length. So be sure your user table’s password field is sized (or resized, if necessary) to hold at least 63 characters.&lt;/p&gt;
&lt;h3 id=&#34;peppering-your-padding&#34;&gt;Peppering your Padding&lt;/h3&gt;
&lt;p&gt;To provide additional support to protect weaker passwords against brute-force attacks, the process pads out password length to at least 72 characters. This padding is considerably more effective by adding a secret pepper that ensures the pattern cannot be reproduced without knowing the pepper value. This is, however, no substitute for enforcing good minimum standards on your passwords to ensure they are not weak in the first place, and does absolutely nothing to protect against a hacker guessing passwords through the front end.&lt;/p&gt;
&lt;p&gt;Once a password hash is created with a particular pepper (which includes the possibility of no pepper) it is permanently dependent on that value. That is to say, the pepper should be settled on prior to introducing the use of bcrypt. If the pepper is changed, any hashes created and stored with the previous pepper (or no pepper) will become inaccessible. Even “promote” won’t help here because in order for promote to function, both encryption types (the original and the target) must be able to work against the supplied password. Once a new value of “bcrypt_pepper” is introduced, Vend::UserDB loses access to the old pepper and, thus, cannot use it to test the validity of the password proffered by the user against the existing stored data structure.&lt;/p&gt;
&lt;p&gt;If it becomes unavoidable to change a pepper, a developer would have to create a new UserDB profile with the old pepper, write custom code to use the new profile to validate the password obtained from the user and, upon success, update the password field in the user table in the custom code with the new structure built with the profile holding the new pepper. This could be done using construct_bcrypt() but is in no way natively supported as a promotable option. Moreover, there is no mechanism to allow such an update to occur as a background process, as is available to migrate the other encryption types to bcrypt.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Interchange form pitfalls</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/05/interchange-form-pitfalls/"/>
      <id>https://www.endpointdev.com/blog/2014/05/interchange-form-pitfalls/</id>
      <published>2014-05-13T00:00:00+00:00</published>
      <author>
        <name>Jeff Boes</name>
      </author>
      <content type="html">
        &lt;p&gt;I’ll warn you going in: this is obscure Interchange internal form-wrangling voodoo, and even if you are familiar with that, it’s not going to be an easy trip to get from here to the end of the story. Fair warning!&lt;/p&gt;
&lt;p&gt;Interchange form-handling has many, many features. Some of them are pretty obscure. One such is the concept of a “click map”: a snippet of named code, stored on the server. Specifically, it is stored within the user’s session, which is a chunk of storage associated with a browser cookie, and can contain all sorts of stuff—​but which is primarily used for things like the shopping cart, the user’s identity, and so on.&lt;/p&gt;
&lt;p&gt;But the tricky business here is when we put something into the session that says, in effect, “when we do &lt;em&gt;this&lt;/em&gt;, do &lt;em&gt;that&lt;/em&gt;, too”.&lt;/p&gt;
&lt;p&gt;(Note: you don’t &lt;em&gt;have&lt;/em&gt; to store your click map code in the session. In fact, it’s probably better not to, for a number of good reasons. You can put it in a sort of global configuration file, in which each individual snippet needs a unique name – which is precisely the requirement that would have prevented this bug, had I or one of my predecessors chosen to embrace that restriction.)&lt;/p&gt;
&lt;p&gt;An Interchange &lt;em&gt;page&lt;/em&gt; is both code and presentation (that is, variable assignments, ITL and/or Perl, plus HTML). No, this isn’t a great design feature—​but it was considered so in an earlier, more innocent time.&lt;/p&gt;
&lt;p&gt;I had one application that continued to present bugs that resisted solution, in fact they were virtually impossible to reproduce. Until one day, when I was testing a theory (which was totally wrong, but led me into the right spot): I landed on a page, then refreshed the page—​and saw a totally different page. Here’s what happened, as simply as I can represent it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Page1.html:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;form ...&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[set Continue]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mv_nextpage=Page2.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/set]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;input type=&amp;#34;submit&amp;#34; name=&amp;#34;mv_click&amp;#34; value=&amp;#34;Continue&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/form&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Page2.html:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;form ...&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[set Continue]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mv_nextpage=Page3.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[/set]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;input type=&amp;#34;submit&amp;#34; name=&amp;#34;mv_click&amp;#34; value=&amp;#34;Continue&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When you request Page1.html, before the page is delivered to the browser, the session variable “Continue” is initialized with the code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mv_nextpage=Page2.html&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In Interchange terms, this tells Interchange where to go when form processing is complete. When we click on the “Continue” button, form processing happens on the server, and the “mv_click” button’s code is referenced * by name * as “Continue”. That takes us to “Page2.html”. When that page is prepared, Interchange overwrites the “Continue” variable with its new data, which is also a next-page setup.&lt;/p&gt;
&lt;p&gt;Now, if we click the button on Page1.html, but then refresh the page on the resulting Page2, form processing is run again—​but this time, our call-by-name to “Continue” executes the code that Page2 set up for its Continue button—​not the Page1 setup. In other words: clicking the button on Page1.html sends us to page 2, but refreshing Page2.html * sends us to Page3.html*.&lt;/p&gt;
&lt;p&gt;I can’t tell you what a relief this was to finally reproduce this bug. The simple fix was to just change the names involved, to “Continue1” and “Continue2”. There’s certainly a lot of power in Interchange form processing, but hoo boy—​with great power comes great potential to shoot yourself in the foot.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Significant Whitespace in an Interchange UserTag</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/03/heres-quick-issue-i-ran-into-with/"/>
      <id>https://www.endpointdev.com/blog/2014/03/heres-quick-issue-i-ran-into-with/</id>
      <published>2014-03-18T00:00:00+00:00</published>
      <author>
        <name>David Christensen</name>
      </author>
      <content type="html">
        &lt;p&gt;Here’s a quick issue I ran into with an Interchange UserTag definition; I’d made some changes to a custom UserTag, but upon restarting the Interchange daemon, I ended up with a message like the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UserTag &amp;#39;foo_user_tag&amp;#39; code is not a subroutine reference&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This was odd, as I’d verified via perl -cw that the code in the UserTag itself was valid.  After hunting through the changes, I noticed that the UserTag definition file was itself in DOS line-ending mode, but the changes I’d made were in normal Unix line-ending mode. This was apparently sufficient reason to confuse the UserTag parser.&lt;/p&gt;
&lt;p&gt;Sure enough, changing all line endings in the file to match resulted in the successful use of the UserTag.  For what it’s worth, it did not matter whether the line endings in the file were Unix or DOS, so long as it was consistent within the file itself.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Interchange table hacking</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/03/interchange-table-hacking/"/>
      <id>https://www.endpointdev.com/blog/2014/03/interchange-table-hacking/</id>
      <published>2014-03-11T00:00:00+00:00</published>
      <author>
        <name>Jeff Boes</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;a href=&#34;https://www.interchangecommerce.org/i/dev/about&#34;&gt;Interchange&lt;/a&gt; has a powerful but terribly obscure table administration tool called the Table Editor. You can create, update, and delete rows, and even upload whole spreadsheets of data, but the Table Editor isn’t the most flexible thing in the world, so sometimes it just flat-out refuses to do what you want.&lt;/p&gt;
&lt;p&gt;So you trick it.&lt;/p&gt;
&lt;p&gt;A client wanted to upload data to a table that had a single-column primary key (serial), but also had a unique three-column key that was only used in the upload process (because the uploaded data was intended to replace rows with identical three-column combinations). Example:&lt;/p&gt;
&lt;p&gt;In the table:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;code: 243
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;field1: AAA
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;field2: BBB
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;field3: CCC
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;data-fields: ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the spreadsheet:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;field1  field2  field3  data-fields...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; AAA     BBB     CCC     ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the database definition for this table, I had to add a secondary key definition for Interchange’s use:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database  my_table  COMPOSITE_KEY  field1 field2 field3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;in addition to the original key:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database  my_table  KEY  code&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here’s the problem this presents: when you add a COMPOSITE_KEY to a table, the table editor refuses to show per-row checkboxes that allow you to delete rows. I thought I might have to write a custom admin page to carry this off, but then I had an inspiration—​the REAL_NAME attribute of the Database directive:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database  my_table_edit my_table_edit.txt __SQLDSN__
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database  my_table_edit REAL_NAME my_table
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database  my_table_edit KEY code
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database  my_table_edit  PREFER_NULL code
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database  my_table_edit  AUTO_SEQUENCE &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This chunk of config-code tells Interchange that a second table can be accessed in the database, which is in fact the same table as the first (not a view, not a copy, but the actual table), but it doesn’t have the COMPOSITE_KEY. When Interchange is restarted with this new definition in place, the Table Editor will show this second table with the familiar per-row checkboxes, even as it refuses to show them for the original table.&lt;/p&gt;
&lt;p&gt;Phew. I dodged a bullet with that, as I didn’t want to have to write a special page just to mimic the Table Editor.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Accepting Bitcoin with BitPay</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/02/accepting-bitcoin-with-bitpay/"/>
      <id>https://www.endpointdev.com/blog/2014/02/accepting-bitcoin-with-bitpay/</id>
      <published>2014-02-25T00:00:00+00:00</published>
      <author>
        <name>Mark Johnson</name>
      </author>
      <content type="html">
        &lt;p&gt;End Point recently began discussions with long-time Interchange client &lt;a href=&#34;http://www.frozencpu.com/&#34;&gt;FrozenCPU&lt;/a&gt; about their desire to accept bitcoin payments. Because of the newness of bitcoin, as well as its non-traditional nature as a &lt;a href=&#34;http://en.wikipedia.org/wiki/Cryptocurrency&#34;&gt;cryptocurrency&lt;/a&gt;, traditional payment gateways do not support it. While ideally suited to function as an exchange medium for ecommerce, we had to seek solutions that allowed for its integration into the checkout process.&lt;/p&gt;
&lt;h3 id=&#34;bitpay-payment-gateway&#34;&gt;BitPay Payment Gateway&lt;/h3&gt;
&lt;p&gt;After investigating their options, FrozenCPU elected to partner with &lt;a href=&#34;https://bitpay.com/&#34;&gt;BitPay&lt;/a&gt; to handle bitcoin payments. BitPay provides merchants with a solution similar in function to PayPal, where users are either redirected to the BitPay servers to complete payment, or the interaction with BitPay can be embedded directly into the merchant site via an iframe. In either case, all communication between customer and payment processor is direct.&lt;/p&gt;
&lt;p&gt;Customers with a digital wallet then approve a transfer for the converted amount of bitcoin. Merchants need not display or maintain conversion rates; they can continue to use their native currency and BitPay will manage the exchange rate directly.&lt;/p&gt;
&lt;p&gt;The payment negotiation is managed by the creation of an &lt;em&gt;invoice&lt;/em&gt; which tracks the state of the payment through a sequence of status updates. Each status update can be thought of as analogous to a credit card transaction, but instead of a number of independent transactions related through references, the same invoice and identifier are used throughout.&lt;/p&gt;
&lt;h3 id=&#34;the-bitpay-process&#34;&gt;The BitPay Process&lt;/h3&gt;
&lt;p&gt;At the point to finalize an order, a number of steps must be taken to complete the transaction using bitcoin. Again, the general process is most similar to PayPal, although no component of address management is included.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;At the point where an authorization is normally issued for a credit card payment, instead the merchant generates a new invoice from BitPay, which starts in the status “new”.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upon successful invoice generation, the merchant can either redirect the customer to BitPay’s server to present the payment screen, or embed the same through an iframe to keep the customer on site, if preferred.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Customer authorizes payment to transfer bitcoin from their digital wallet at the exchange rate provided from BitPay.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once transferred, the invoice status moves to “paid” and the customer can then be presented with the order receipt, or in the case of redirect can click back on the merchant-provided backlink to the site to produce the receipt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BitPay then negotiates a validation process over the bitcoin network, where the payment is “complete” after 6 block confirmations. Merchants can choose how quickly they want to accept the payment as valid, where a higher speed increases the odds of failing validation later in the process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High speed: immediately consider the payment “confirmed” once customer authorizes payment.&lt;/li&gt;
&lt;li&gt;Medium speed: consider payment “confirmed” once BitPay receives 1 block confirmation. Typically 5-10 minutes.&lt;/li&gt;
&lt;li&gt;Low speed: wait for all 6 block confirmations before considering the payment “confirmed”. Typically 45-60 minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All payments must ultimately receive 6 block confirmations within 1 hour to complete. If this threshold cannot be met, the invoice status becomes “invalid” and the merchant must decide how to proceed.- Once the invoice status is “complete”, the transaction cannot be reversed. No chargebacks. Funds can be received by the merchant in the local currency or in bitcoin.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Invoices can be created with flags to either send notifications to an API the merchant creates, a designated email address, or both. This allows the merchant to keep on top of the transaction lifecycle in real time and minimize delays or disruptions on fulfillment.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;bumps-in-the-road&#34;&gt;Bumps in the Road&lt;/h3&gt;
&lt;p&gt;This was our first venture into an emerging field with a young technology. We hit a few difficulties on our path to implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not every status change on the invoice generates a notice, even when the fullNotifications flag is set to true. Customers have 15 minutes to pay an invoice, and if that time elapses the invoice status moves to “expired”. That left us with a dangling order because the IPN does not fire on that status update. We had to compensate by developing a monitoring script to look for those dangling orders older than 15 minutes, make an API call to BitPay to confirm the invoice status of “expired”, and cancel the order.&lt;/li&gt;
&lt;li&gt;We initially received no update messages on status changes to the invoice that sourced back to problems in BitPay’s proxy software. However, BitPay was diligent in their efforts to troubleshoot the issue with us and they were able to upgrade the packages causing the problem. Their responsiveness in this matter certainly separates BitPay from other payment processors who are either entirely unresponsive to system bugs or are content to allow problems to persist through their regular release schedule.&lt;/li&gt;
&lt;li&gt;Development and testing was complicated by the lack of a development environment at BitPay.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are looking forward to the anticipated success of accepting bitcoin for payment on FrozenCPU and the opportunity to explore this offering with our many other ecommerce clients.&lt;/p&gt;

      </content>
    </entry>
  
</feed>
