<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Underjord.io</title>
    <link>https://underjord.io/blog/</link>
    <description>A blog on independent software development, Elixir and more.</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 11 Mar 2026 04:00:00 +0000</lastBuildDate><atom:link href="https://underjord.io/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Hacking on the Nerves Starter Kit in Malaga</title>
      <link>https://underjord.io/malaga-nerves-event.html</link>
      <pubDate>Wed, 11 Mar 2026 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/malaga-nerves-event.html</guid>
      <description>I&amp;rsquo;m going to be speaking at ElixirConf EU in Malaga. When I got to Elixir conferences I relatively often also make sure to run a little something-something for the real heads. A Nerves thing. Touch keyboard, touch hardware, get to know people.
The biggest reason for this is to make an ice-breaker for people that are attending the conf. People who would enjoy a way to engage with others up front in a practical workshop kind of way.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;m going to be speaking at ElixirConf EU in Malaga. When I got to Elixir conferences I relatively often also make sure to run a little something-something for the real heads. A Nerves thing. Touch keyboard, touch hardware, get to know people.</p>
<p>The biggest reason for this is to make an ice-breaker for people that are attending the conf. People who would enjoy a way to engage with others up front in a practical workshop kind of way. We&rsquo;ve done shared hacking on PRs and issues. We&rsquo;ve done more or less a prepared workshop. We&rsquo;ve done a few varieties. I think this also spices up the conference period a bit.</p>
<p>This one we&rsquo;ll have some hardware by Gus. Prototypes for the Nerves Starter Kit that we can work on. We hope to also have a scheme where you can pay the cost of the hardware if you want to keep it. Or maybe you dazzle us with your contributions so hard we throw it at you. One never knows.</p>
<p>You can learn more about the Nerves Starter Kit <a href="https://nervesmeetup.eu/meetup/gus-workman-introducing-nerves-starter-kit">in this presentation</a> that Gus did at the EU online meetup (which is a lovely time and happening <a href="https://nervesmeetup.eu/meetup/marc-lainez-will-it-run-nerves">today actually</a>, Marc Lainez is sharing his porting efforts). The point of the starter kit is to be a common target platform for playing with Nerves that we can offer for a long time, fairly priced, interesting variants built on open hardware designs. Rather than buying a bunch of disparate Pi parts and hoping for the best in terms of support you get something cohesive and supported right away.</p>
<p>So we plan to have variants of the hardware for this event and we&rsquo;ll work on the firmware/software and application side. You can actually <a href="https://github.com/protolux-electronics/name_badge">grab the project</a> and hack on it in the simulator entirely. Whether you feel like contributing on-hardware functionality or making the docs really shine. We&rsquo;ll sit down, play, hack and socialize.</p>
<p>The event is co-organized with The Erlang Ecosystem Foundation, massive thanks to Dan J for finding a venue. There will also be an Elixir Unconference happening at the same time. We hope you&rsquo;ll throw in a 10 Euro donation to the EEF for our trouble. More <a href="https://marketing.erlef.org/events/malaga-unconf.html">details here</a>.</p>
<p>You can <a href="https://luma.com/q4ykwcsl">register for the event here</a>, same registration for the Nerves hack session and the Elixir Unconference.</p>
<p>Hope to see you there :)</p>
<p>If you have questions you can reach me on the Fediverse <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> or via email <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Shipping grayscale photos at small scale</title>
      <link>https://underjord.io/shipping-grayscale-photos-at-small-scale.html</link>
      <pubDate>Sun, 08 Mar 2026 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/shipping-grayscale-photos-at-small-scale.html</guid>
      <description>In September of 2025 Tigris sponsored an odd effort at an unusual conference. They contributed the money for manufacturing hardware devices to be used and hacked on during Goatmire Elixir in Varberg, Sweden. These devices also used Tigris for some features. Everything about it is open source. Let&amp;rsquo;s see what it turned into.
Goatmire Elixir is my (Lars&#39;) brainchild. I like the idea of a quirky event, smaller scope and people who are deeply enthusiastic about related things.</description>
      <content:encoded><![CDATA[ <p>In September of 2025 Tigris sponsored an odd effort at an unusual conference.
They contributed the money for manufacturing hardware devices to be used and
hacked on during Goatmire Elixir in Varberg, Sweden. These devices also used
Tigris for some features. Everything about it is open source. Let&rsquo;s see what it
turned into.</p>
<img src="/assets/images/blog/goatmire-2025-name-badge.jpg" alt="eInk name badge device opened showing battery and circuit board." />
<p>Goatmire Elixir is my (Lars') brainchild. I like the idea of a quirky event,
smaller scope and people who are deeply enthusiastic about related things. So I
made one, for the Elixir programming language community. In the little swedish
coastal &ldquo;city&rdquo; of Varberg. And it was a blast.</p>
<p>The <a href="https://2025.goatmire.com/gallery">photo gallery is
up</a> to get a sense of the amazing
presentations and <a href="https://video.goatmire.com">all talks are available online</a>, also <a href="https://www.youtube.com/@goatmireinternational">on
YouTube</a>. This kind of
conference can only happen thanks to companies willing to take a gamble on an
idea, like Tigris did. I&rsquo;m very thankful.</p>
<p>We made a name badge. People do that
for other conferences to. And if they make a special hardware name badge they
are usually based on some reasonable microcontroller. Maybe LEDs or an OLED
screen. This one is a bit wild. It is a small Linux-capable device built around
the Allwinner T113-S4. A 1.2 GHz dual-core ARM SoC. The hardware design is
called <a href="https://github.com/protolux-electronics/wisteria_hardware">Wisteria</a> and is based on a core board called
<a href="https://github.com/protolux-electronics/trellis_core">Trellis</a> both are open
source. Designed, implemented, sourced and made real by Gus Workman. Importantly
this thing has an eInk display, a battery, a few buttons and Wi-Fi. Because who
doesn&rsquo;t love eInk.</p>
<img src="/assets/images/blog/goatmire-2025-name-badge-2.jpg" alt="eInk name badge device showing the words NAME BADGE" />
<p>A big reason to make it a Linux-capable board is that one day of the conference
was NervesConf EU. <a href="https://hexdocs.pm/nerves/getting-started.html">Nerves</a> is a
framework for IoT devices of the Linux-level class. So not microcontrollers,
rather things with an MMU and a tractable amount of RAM. Essentially we run a
barebones Buildroot-based Linux OS and the first process that boots in turn
starts the legendary BEAM virtual machine. The thing Erlang runs on. Also, the
thing Elixir runs on.</p>
<p>The reason a bunch of us like to work with Nerves is that
it gives you these very strong error handling behaviors and lets you build
devices at a higher level of abstraction. There are other cool things like the
open source firmware update service
<a href="https://github.com/nerves-hub/nerves_hub_web">NervesHub</a> . That is also
available as a commercial offering called <a href="https://nervescloud.com">NervesCloud</a>
which delivers firmware using .. Tigris!</p>
<p>Mostly Nerves is about Elixir and what
a lovely language that is to build devices that deal with a lot of
communication. Elixir inherits and builds on Erlang&rsquo;s fantastic ability to deal
with binary formats and networking protocols.</p>
<p>For this device we wanted to make
it interactive and continuously helpful during the conference. So it would load
the schedule for the conference. That&rsquo;s helpful. It would allow you to show your
name and title as a good name badge. And being eInk you could switch it off and
it would just keep showing that.</p>
<p>It also had a photo gallery and the way this
worked was that we let attendees upload photos from their phone to a <a href="https://hexdocs.pm/phoenix_live_view/welcome.html">Phoenix
LiveView</a> web app, the
backend service for the whole thing. We&rsquo;d moderate the inbound photos and
whatever we approved would be converted to 1-bit black-and-white, with
dithering, for display on these monochrome devices. Then they were downloaded
and displayed. Rendered in the UI using Typst, a Rust-based rendering library.</p>
<p>Of course all media handling of uploads went through Tigris with pre-signed
uploads and downloads. We didn&rsquo;t really get a lot of mileage out of the
distributed regions and all that considering we were the very opposite of
geo-distributed at the time. Everything except our local Wi-Fi worked great.</p>
<img src="/assets/images/blog/goatmire-2025-name-badge-gallery.jpg" />
<p>A nice feature is that all devices that were switched on would actually show the same
image as the server will push new pre-signed URLs out at an interval to all connected devices.
Very neat and typical Phoenix shenanigans.</p>
<img src="/assets/images/blog/goatmire-2025-name-badge-hackers.jpg" />
<p>There was a lot of people poking at and hacking on the badges. The most critical
thing, aside from some rather urgent
bug fixes, was an implementation of Snake. The classic cell-phone game was
implemented by Peter Ullrich during the conference.</p>
<img loading="lazy" src="/assets/images/blog/goatmire-2025-name-badge-snake.gif" />
<p>The community will keep
iterating on this software and this hardware. Another batch of these were made
and sent to 39C3, the Chaos Computer Congress, for some people to hack on. Gus
recently presented on <a href="https://www.youtube.com/watch?v=8k4V2iithT0&amp;feature=youtu.be">the Nerves Starter
Kit</a> which expands
on this device with more costly and capable peripherals which will be a great
boon to people getting into Nerves. And with the open nature of the hardware and
software may well get supported and adopted in other ecosystems.</p>
<p>On the 22nd we
are planning a small community event to hack on prototypes for the starter kit
leading up to <a href="https://www.elixirconf.eu/">ElixirConf EU in Malaga</a> as Gus is
presenting in depth on the development of this kit. We have people considering
putting wheels on it to make it a balance robot. The idea is to make something
extensible and hackable that people can push further.</p>
<p>The Tigris crew is nice
and friendly. We&rsquo;ve worked together in the past and I don&rsquo;t think they had a big
plan funding those boards. I think it was a fun way to sponsor a cool thing and
show their support of fun community events. There just happens to be rings on
the water from an act like that. The version of the device that was made for
Goatmire Elixir thanks to Tigris will ensure more iterations of open hardware
and more people getting their first crack at running on hardware.</p>
<p>While I know
Tigris can ship large files, globally at massive scale. Sometimes people want to
work in the small and it also works for shipping 1-bit images, locally at a
human scale.</p>
<p>Thanks. From me, the Goatmire team and the Nerves community.</p>
<hr>
<p>If you have questions about any of this I&rsquo;m around on the fediverse <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> and on email <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Goatmire 2, announced</title>
      <link>https://underjord.io/goatmire-2-announced.html</link>
      <pubDate>Wed, 14 Jan 2026 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/goatmire-2-announced.html</guid>
      <description>Goatmire Elixir 2025 was a very dense success. The second one just got announced.
I say dense. Because massive success would make it sound large. And it wasn&amp;rsquo;t a large event. 150 attendees, with speakers and volunteers we were around 200 in total. And it went incredibly well. The feedback, the surveys, the outpouring of love and appreciation were all way above what I dared hope for. People have been graciously giving me space to consider a second one but it has also been the single most requested thing ever.</description>
      <content:encoded><![CDATA[ <p>Goatmire Elixir 2025 was a very dense success. The second one just got announced.</p>
<p>I say dense. Because massive success would make it sound large. And it wasn&rsquo;t a large event. 150 attendees, with speakers and volunteers we were around 200 in total. And it went incredibly well. The feedback, the surveys, the outpouring of love and appreciation were all way above what I dared hope for. People have been graciously giving me space to consider a second one but it has also been the single most requested thing ever.</p>
<p>September 30th to October 2nd, 2026. You can sign up to be notified <a href="https://goatmire.com">on the website</a>. In our little town of Varberg in Sweden.</p>
<h2 id="unchanged">Unchanged</h2>
<p>Lots of things will <em>be the same</em> as last time. Venue is the theatre. Lots of the practical bits that worked will be kept the same or be iterated on a little bit.</p>
<p>You may notice that the visuals have changed though. You can still see the <a href="https://2025.goatmire.com">Goatmire 2025 website</a> and compare it to <a href="https://2026.goatmire.com">the new one</a>. Part of what made Goatmire special is that people knew I intended to make it a bit different but they didn&rsquo;t know what to expect. And I believe that one of my more important tasks is to be a creative influence and encourage speakers to take things a little bit further than they might typically do. And to scrub every ounce of sense that this is a corporate or commercial event. There are commercial aspects in terms of making the budget work out. That is not the focus. This is to be an experience for the community. I want to reinforce the connective tissue of the community. Have people connect and make memories. Feed the enthusiasm and appreciation that I see in so many spots around the Elixir space. Most of it is thematic, creative and abstract. Very hard to know if you hit the mark.</p>
<p>I feel fairly confident about the theme for this one. I&rsquo;m not going to be putting it into words because it hasn&rsquo;t been stated more than what you see on the website. Everyone can start interpreting what it will be and I&rsquo;ll try to shape and mold that as we go forward.</p>
<p>Some of the &ldquo;tricks&rdquo; I pulled for the first one can&rsquo;t be the same for the second one. Or at least that&rsquo;s how I&rsquo;m wired. And considering how many people have said they want to do a repeat visit I&rsquo;m probably right to not make it too similar. And lots of things can only be done in collaboration with the speakers and their creative intent. I don&rsquo;t know what we have to work with yet. It&rsquo;s all very exciting.</p>
<p>From 2025 we had some talks that stood out in concept and character. And <a href="https://video.goatmire.com/v/ywpqe">the Nerves Car</a> is now well-known. Saša Jurić has already <a href="https://video.goatmire.com/v/ex36w">told us a story</a>. Zach Daniel has already <a href="https://video.goatmire.com/v/e37me">broken time and space</a> using Ash Framework. We&rsquo;ve done <a href="https://video.goatmire.com/v/eadjw">the distributed system murder mystery</a> and we&rsquo;ve ended on <a href="https://video.goatmire.com/v/e85qw">a phenomenal live-coded musical number</a>. So all of those are out. Not necessarily the people. Just those presentations. I&rsquo;m sure we&rsquo;ll have a different mix of speakers as well though I certainly hope to see many of these in the CTF when we launch that.</p>
<p>I also still plan to co-locate NervesConf EU with Goatmire Elixir because it worked really well. The double-branding is a pain in my ass and I don&rsquo;t have a clear answer to how I want that to work. Maybe NervesConf EU doesn&rsquo;t get as much visual space this year. I don&rsquo;t know.</p>
<h2 id="changes">Changes</h2>
<p>We know catering will be different. Things have changed at the venue. Because the lady that had the contract on the food for the venue cancelled it and left to do other stuff. We&rsquo;ll probably make similar choices. Maybe communicate more aggressively up front that the food is vegetarian so if that&rsquo;s not your jam you might need to look around. But we don&rsquo;t know much about the catering situation at the moment.</p>
<p>We plan to have more tickets, more people. The venue can theoretically absorb 350. But I think like 10 of them wouldn&rsquo;t see the stage. We want more people to be able to attend. From the first one it seems like interest is very high. We have more people on the interested mailing list than we had tickets last year. People have suggested it will sell out like Woodstock and we just have no way of knowing what to expect. Compared to last year, I&rsquo;m not worried about reaching my minimum number of attendees to run the event. But the space has limitations for how many people can comfortably hang out and socialize outside the seating area. I&rsquo;m excited about upping the headcount because it ensures that we&rsquo;ll have people that weren&rsquo;t at the first one and it should boost the social aspects. It still won&rsquo;t hit a size where it is an intractable mass of bodies and people become anonymous to each other I hope. I don&rsquo;t think that&rsquo;ll be an issue.</p>
<p>Also at 350 I imagine we&rsquo;d choke to death. Just upping the headcount by 50 will mean that door discipline becomes more important to ensure air flow. It is an older theatre and while Taun Chapman actually put a (Nerves powered) air quality sensor in there during the event and it was never particularly bad it does get a bit toasty. It isn&rsquo;t typical for theatre to be a full day where people are in the seats for most of it. Gotta not put everyone to sleep.</p>
<p>Another plan is to tweak the contents in various ways. We got some feedback that requested more technical contents and deeper technical contents and I think that&rsquo;s appropriate. Some of our balance shifted due to speakers not being able to show up and others stepping in. Some talks end up not as technical as one had expected. It&rsquo;s tricky. I think we can tweak that balance. The feedback felt applicable. Anyone who noted that they wanted more technical depth and who skipped the NervesConf EU day made a gross miscalculation though. NervesConf was crunchy.</p>
<p>We also had a very packed schedule. More than 30 speakers across 3 days, single track. We will probably leave more air in the schedule this time. This means the selection process for the CFT will probably be brutal for us in the committee and competition pretty intense for the spots. We said no to more than 40 talks last time. If interest is higher this year and slots are fewer.. oof. Good content requires good editing though and this is editing. Good talks will be rejected.</p>
<p>Weird and creative shit. Gonna wanna do things. Not the same things. Not gonna tell you what.</p>
<h2 id="hopes-and-aspirations">Hopes and aspirations</h2>
<p>I want to run things on the dates leading up to the proper conference. Many conferences have training days. And I&rsquo;ve personally organized a few satellite events for Elixir and BEAM conferences where we gather and hack on Nerves stuff. They are always a ton of fun. Hands on keyboard is a very good social environment for some devs. It also helps some people get their first PRs in or maybe try a project they wouldn&rsquo;t have tried otherwise. So what I want to try and do is pull together about 2 days of workshops that are some mix of trainings, project sprints, getting-started and weirdly specific things. Free or at cost and to whatever level of ambitions the community maintainers and collaborators want to put into it. And I hope that local students will show up for these as well as conference attendees getting a few more days off work to get free training from the most knowledgeable people in the ecosystem. I think it&rsquo;d be rad. Plans are still early.</p>
<p>My hope is that we have more satellite events. Like we had Ash Summit 2025 after Goatmire Elixir last time. It was very neat. Happy to repeat that and happy to have more variants.</p>
<p>I want to support speakers in making their best presentations in a few more ways. I&rsquo;m happy with the mix of newer, less known speakers and big names we had. There are certainly ways to improve the mix. And I want to surface more folks that I&rsquo;ve heard of that others have not that do cool stuff. But I also want to help people that might play it safe to take bigger swings. Ideally we can help people with polish, practice and such. I have some plans. It is a tricky topic but I think we can do more to ensure great presentations. And I&rsquo;m very happy with how talks were in 2025. I just think we could potentially do more.</p>
<h2 id="closing">Closing</h2>
<p>That&rsquo;s it for now. Goatmire Elixir 2026 has a date. Sponsorship prospectus is currently being drafted. If you know you are interested in showing up as supporting the event, feel free to reach out. If you have questions, concerns, ideas or plans and want to discuss, I&rsquo;m also very available. I look forward to making another one of these things. Big thanks to the community for the beautiful support of last year and of the announcement of the new one. Deeply touching :)</p>
<p>Thanks for reading. I&rsquo;m available as email <a href="mailto:lars@underjord.io">lars@underjord.io</a> and socially <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Conference Report: Goatmire Elixir 2025</title>
      <link>https://underjord.io/conference-report-goatmire-elixir-2025.html</link>
      <pubDate>Mon, 22 Sep 2025 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/conference-report-goatmire-elixir-2025.html</guid>
      <description>The quiet was unsettling. The lack of concrete things to do was stressful. Me and my wife had been fretting and flitting around the house for most of the day making sure everything was staged, that we had the various prints, checklists were ready, lots of bags and boxes were packed. The event wasn&amp;rsquo;t even starting today, not even fully tomorrow. The quiet after intense preparation.
Snap to today. The event has now happened.</description>
      <content:encoded><![CDATA[ <p>The quiet was unsettling. The lack of concrete things to do was stressful. Me and my wife had been fretting and flitting around the house for most of the day making sure everything was staged, that we had the various prints, checklists were ready, lots of bags and boxes were packed. The event wasn&rsquo;t even starting today, not even fully tomorrow. The quiet after intense preparation.</p>
<p>Snap to today. <a href="https://goatmire.com">The event</a> has now happened. Results are in. I am very satisfied. This is my conference report, as the organizer. Because of my deep level of involvement it may end up being quite a long post.</p>
<h2 id="conception">Conception</h2>
<p>I can&rsquo;t put a date to how long we&rsquo;ve prepared for the conference. But about 6 months at increasing intensity. I picked up the bug of wanting to run an event when I visited Gig City Elixir in Chattanooga in May 2024. All credit to Maggie and Bruce Tate for that, they have a special thing going. A few things really made it a brain bug back then. Todd Resudek offered that I could freely use the NervesConf name to run an EU event. Very kind and I definitely want to gather the EU Nerves community. And when I chatted with folks about running something in Sweden they seemed optimistic. Andrea Leopardi of Elixir core team fame confirmed himself as my first speaker then and there. In the middle of <a href="https://www.seerockcity.com/">Rock City</a>.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/tates.jpg"><img src="assets/images/goatmire/tates.jpg" alt="Bruce and Maggie Tate being lovely hosts during my trip to Chattanooga. They hosted me in their home!" /></a>
        <p>
            Bruce and Maggie Tate being lovely hosts during my trip to Chattanooga. They hosted me in their home!
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/rockcity.jpg"><img src="assets/images/goatmire/rockcity.jpg" alt="Rock City in Chattanooga" /></a>
        <p>
            Rock City in Chattanooga
        </p>
    </div>
</div>
<p>It probably took most shape during the first quarter of this year (2025). The name, sourced from a historic name for Varberg, Getakärr, can be fairly translated to Goatmire. It was distinctive. In fact I think I got about 20 minutes of extra air time in the <a href="https://thinkingelixir.com/index.html">Thinking Elixir</a> podcast just on the name. I bet it lived rent-free in many people&rsquo;s heads. My particular sense of humor would be a guiding blight throughout the process.</p>
<p>There were a few things that confirmed it for us. Talking to some of the people around the Varberg tech hub <a href="https://varbergtech.se/techarena/">Techarenan</a> was encouraging. They (thanks Sara!) put me in touch with the public sector commerce folks and they were very enthusiastic and gave me a warm introduction to Sparbanksstiftelsen. This is a foundation that owns Varbergs Sparbank, the local bank. They reinvest in the local area and after some conversation we sent a suggestion, a rough budget and we applied for a stipend.</p>
<p>It took a minute to confirm but the year was very young when we got our base funding from them confirmed. Then we started spinning up the non-profit registration. Gathered recommendations for venues and talked to them. In the end landing on Varbergs Teater, the theater where me and my wife married. We chose it because it was the most interesting and the most flexible to custom ideas and is still smack in the center of town. There are many good venues in this little city since it is a swedish tourist-destination but I wanted something with a lot of character and I knew the theater could deliver.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/theater.jpg" target="_blank"><img src="assets/images/goatmire/theater.jpg" alt="The lovely Varbergs Teater. Picture provided by Varbergs Teater." /></a>
        <p>
            The lovely Varbergs Teater. Picture provided by Varbergs Teater.
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/beach.jpg" target="_blank"><img src="assets/images/goatmire/beach.jpg" alt="Beach in Varberg. Photos from Varberg Kommun, credit Natalie Greppi." /></a>
        <p>
            Beach in Varberg. Credit Natalie Greppi for Varbergs Kommun.
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/town.jpg" target="_blank"><img src="assets/images/goatmire/town.jpg" alt="Town of Varberg. Photos from Varberg Kommun, credit Natalie Greppi." /></a>
        <p>
            Town of Varberg. credit Natalie Greppi for Varbergs Kommun.
        </p>
    </div>
</div>
<p>Some basic funding and a venue meant we had a starting point.</p>
<h2 id="event-architecture">Event architecture</h2>
<p>One day NervesConf EU for IoT with Elixir and two days Goatmire Elixir for regular Elixir. That&rsquo;s the model of Gig City, so I yoinked it. Not reinventing the wheel in terms of structure. I grabbed the scheduling from Code BEAM Europe as well. And you&rsquo;ll see on speaker&rsquo;s day, I nabbed a concept from Öredev. I suppose this makes me a great artist. I hear they steal.</p>
<p>I wanted to make the event distinct. I don&rsquo;t think we actually gave a ton of guidance to speakers towards that. The most relevant bit from the CFP was:</p>
<p>&ldquo;We will not be taking on talks that are &ldquo;Update on project X&rdquo; but are fine with talks that involve new features and either how they were built or what they allowed you to do. Avoid presenting a changelog. Teach something, tell a story, blow some minds or show your work.&rdquo;</p>
<p>I wanted to do wild things but I&rsquo;m also new at making events and didn&rsquo;t want to make my first time completely impossible. I didn&rsquo;t push for people to take exceptionally bold swings in the CFP but in every conversation I ended up encouraging the more interesting choice at every turn. And there were a lot of conversations.</p>
<p>Importantly I&rsquo;d heard the great things people had to say about EMPEX and The Big Elixir. A single track conference in a great venue will be a very focused affair. I had some faith that we could deliver something similar though we are decidedly not New Orleans or New York levels of cool. But the town punches above its weight.</p>
<p>3 days, the first probably smaller, single track. That&rsquo;s the rough of it.</p>
<p>The crew was me, my wife and our then-employee Tomie. Tomie has about 10-15 years of event planning experience and helped get us started well but they would then leave Underjord to do great work over at Entryfy an awesome client of ours (with our whole-hearted, mildly heart-broken support). Entryfy would also end up sponsoring the event. We brought in a new acquaintance. Helene Mattisson, wife of swedish Elixir old-timer Johan Mattisson. She helped us stay secure in the knowledge that we weren&rsquo;t missing crucial know-how and details. Like having insurance. She is a gem, and <a href="https://www.linkedin.com/in/helenemattisson/">available to contract</a> for event work.</p>
<h2 id="building-it-up">Building it up</h2>
<p>I had a single speaker (thanks Andrea!) so I started reaching out to my network to pin people down. The Nerves core team for NervesConf, good collaborators for more Nerves talks. Some solid names for the Elixir days of the event. Added myself for padding, since I&rsquo;m probably the most visible Elixir dev in Sweden.</p>
<p>The procedure for getting sponsors was similar. Tap my network, both the local one in Varberg and the one in I have in Elixir. My sponsor journey went pretty well overall. I had a late plot twist of a sponsor pulling out shortly before payment was due but someone picked up the mantle quickly after (thanks Alembic!).</p>
<p>At some point I launched the waiting list. We estimated we could deal with 150 people. The waiting list climbed to 90 people very quickly. Over time it drifted up to 200 before we launched tickets. If there is something I&rsquo;ve worked up a muscle for in the last 7-8 years it is &ldquo;marketing&rdquo;. That is, I am already actively posting on socials. I have a newsletter. I have a podcast. I know other people with podcasts. I get around.</p>
<p>Excitement was building way better and faster than I had dared hope. I was fully ready to run the conference at 90 people.</p>
<p>The appetite for a community-run less-commercial Elixir event was definitely there and another one popped up essentially in parallel with AlchemyConf in Braga, Portugal. But they were in March, we were targeting September. I had already decided on the theater when they announced their own (larger, fancier) theater. They were first on using Discord as a conference app though. I jacked that from them.</p>
<p>Over time design, ideas and concepts started to crystallize. The pre-CFP line-up looked great. The CFP made it fantastic. I had great help from the program committee I recruited (thanks Sophie, Rebecca and Cornelia!). The event gained steam. And speakers kept getting back to me with weird (awesome) ideas and requests which pleased me to no end. <a href="https://bsky.app/profile/gworkman.bsky.social">Gus Workman</a> and I conspired to do something with hardware. He did all the work, I found the money. Supported by old compatriots <a href="https://www.tigrisdata.com/">Tigris</a>.</p>
<h2 id="the-day-before-the-day-before">The day before the day before</h2>
<p>The emptiness was heavy. I felt like I had missed something. Probably everything.</p>
<p>The Discord was popping with people travelling, making board game plans and announcing their arrivals to the swedish west coast. Lots of cute observations about the strangeness of various parts of Sweden as people traversed it. I was on high revs and completely idle. Thankfully I had an errand I could run. It was both practical and an excuse for me to say hi to some people. We literally didn&rsquo;t have more we could do at this point. Very close to &ldquo;go time&rdquo; but many hours until we could pull any triggers.</p>
<p>I shoved a bunch of Nerves hardware into the car and drove into the city (I live in the countryside). I would stash it in Gus' room until he arrived and could deal with it (oh, how he would have to deal with it &hellip;). Three boxes of devices with less-than-good 3D-printed cases and one full box of actually good 3D printed cases. For 200 devices in total.</p>
<p>Parking outside the speaker hotel, Gästis, I wrangled boxes in there and went to the reception desk. As I waited a bit for the receptionist I spotted a group of 6 or 7 familiar faces. From memory it was Frank Hunleth, Rebecca Le, Jonatan Männchen, Pascal C, Srinivas Ganti and Dan Janowski. Pascal was the only new acquaintance and he was subsequently everywhere. They helped me stash the boxes in Gus' room and I had already cleared permission with Stina (my wife) to grab dinner in town with people. So we acquired a Josh Price and a Sam Aaron at minimum before we sauntered out to find some swedish-style pizza for the weary travelers.</p>
<p>I went home shortly after the pizza, more people accrued as we ate. I got to say hi to Flora and Tom Petterson, just in from New Orleans. We tee-hee&rsquo;d in secret about the massive part they still had to play in making the event memorable.</p>
<h2 id="setup--speakers-day">Setup &amp; Speaker&rsquo;s Day</h2>
<p>The day before the conference started was planned for setup. We had access to the venue from 12-16 to rig stuff. Then we had plans with the speakers of the event from 16 into the evening. Me and my wife absolutely stuffed the car with stuff, including the plywood goat, drove into town. Parked at the venue and went for food.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/goat.jpg"><img src="assets/images/goatmire/goat.jpg" alt="The goat. A statement art piece by David Qvarnström." /></a>
        <p>
          The goat. A statement art piece by David Qvarnström. All event photo by Petter Boström.
        </p>
    </div>
</div>
<p>Srinivas was a champ who showed up immediately for the prep though he wasn&rsquo;t even originally a volunteer. He just wanted to help and that he did. After a while Flora and Tom rolled in and started the wildest testing for their talk. We&rsquo;ll cover what they did later.</p>
<p>Zach Daniel showed up to test his talk which required some special effects. A few others also came to test, help and so on. Badges, lanyards and shirts were prepped, thoroughly optimized by Dan J, executed by Helene and Srinivas. Plans were made. Roll-ups placed. We were pretty much ready.</p>
<p>An extra wifi-network was added for Nerves-devices. Would it be sufficient? Of course not. It never is.</p>
<p>Essentially, setup went super. The tech, Tobias is an absolute mensch. What a solid calming presence, absolute pro. I also can&rsquo;t recommend dealing with a theater enough. Where a conference hotel venue will go &ldquo;You want to do WHAT?&rdquo; a venue like this will go &ldquo;Typical thursday, sounds good. Need lights or smoke with that?&rdquo;. They are used to doing the difficult thing.</p>
<p>Right before 16 o' clock me and Stina left the venue to get drinks and snacks to <a href="https://kallbadhuset.se%5D(https://kallbadhuset.se/)">Kallbadhuset</a> (The Cold Bath House) where the first speaker social event was. Sauna and a swim in the ocean, rince and repeat. Good way to break the ice. Completely stolen from the Öredev conference who do the same thing in Malmö. People are various level of comfortable, makes for fun conversation and it was all optional. Good view. Nice old place. Not everyone attended this but it was a good time and a good start. Then we sauntered over to the restaurant, NAMI. Japanese fusion tapas. Great place.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/kallbadhuset-1.jpg"><img src="assets/images/goatmire/kallbadhuset-1.jpg" alt="Kallbadhuset, photo credit Natalie Greppi." /></a>
        <p>
          Kallbadhuset, photo credit Natalie Greppi.
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/kallbadhuset-2.jpg"><img src="assets/images/goatmire/kallbadhuset-2.jpg" alt="Kallbadhuset, photo credit Natalie Greppi." /></a>
        <p>
          Also photo credit Natalie Greppi.
        </p>
    </div>
</div>
<p>I was already very happy. Seeing some of my best community friends drift around my town. Getting to be their curator and having them come to the experiences I chose, definitely brought a smile to my lips. Watching the speakers gather, most of who I already know from the community. What a feeling. With some extra friends included we were around 36 people for dinner. Food was great. People had a great time connecting, reconnecting and so on. Then some of us drifted over to the bar for a bit.</p>
<p>That was a slight mistake on my part.</p>
<h2 id="day-1-nervesconf-eu">Day 1: NervesConf EU</h2>
<p>I did end up staying out a bit longer and having a few more drinks than I had planned so I was definitely not at optimal energy when I dragged myself out of my hotel room bed around 6.15 in the morning. We slept in the hotel to not have the travel time and the kids were with my in-laws which was a life-saver. Sleeping through the night without bonus dialogue was a big win.</p>
<p>Get into outfit #1, trashy, my most worn metal shirt, the authentically worn-through jeans, studded belt, keychain, Converse All-Star, red plaid shirt. Throwback to my teens. Underscoring the informality of the event. Breakfast at hotel. Get to venue, setup from 7-8, doors at 8, seating area open from 8.30. Start at 9.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/trash.jpg"><img src="assets/images/goatmire/trash.jpg" alt="Trash outfit. Hair was accidentally messy but I suppose it fits." /></a>
        <p>
          Trash outfit. Hair was accidentally messy but I suppose it fits.
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/trash-again.jpg"><img src="assets/images/goatmire/trash-again.jpg" alt="Observe the holy jeans." /></a>
        <p>
         Observe the holy jeans.
        </p>
    </div>
</div>
<p>Great start. It starts on <a href="https://youtu.be/QOdd3Ya6qlY?si=7aRU04ULX-JMkwBj">this video</a>. Then Marc Lainez is off and delivering a talk on The Nerves Car project. An audacious electric mod to a WV Polo using a variety of Nissa Leaf, Tesla and other parts. Brains with Nerves, UI with Flutter and tons more interesting detail. I was probably still mildly hung over but at some point I just forgot about that. If you are curious they have <a href="https://elixirforum.com/t/driving-a-car-powered-with-nerves-and-elixir/71557">shared a lot of detail</a> on it.</p>
<p>All talks were recorded and the absolute majority will be released.</p>
<p>Then we are off. I won&rsquo;t go blow by blow. I loved so many of the talks and I think everyone put their best foot forward. I&rsquo;m very pleased that my starting strategy worked. Cold open on a talk. The initial keynote speaker is the welcome. The event is not about the event, it is about the contents of the event, talks and people. As the MC I spoke after. Kicking off the event when it was already in full swing. Having earned some of the space to talk about sponsors and what the logistics of the day look like.</p>
<p>Later in the day Gus Workman gave his talk. And every attendee in the audience had one of <a href="https://bsky.app/profile/gworkman.bsky.social/post/3lzdmdufdjs2k">his Wisteria devices</a>, aka. The Name Badge. A special eInk board based on <a href="https://www.youtube.com/watch?v=XgMDTAKd7-4&amp;list=PLvL2NEhYV4Zu421KzHuLICUqieJXI2o_Z&amp;index=27&amp;pp=iAQB">his Trellis design</a>. We hadn&rsquo;t gotten the batteries yet but if you plugged it in to USB-C power, it totally worked. He told the story of the device, pretty awesome. The project of getting these devices was real tight to deadlines and Gus pulled an enormous workload (electronic, sourcing, software, manufacturer relations, all him) to do it (on his own volition). May it bring him great business.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/name-badge-1.jpg"><img src="assets/images/goatmire/name-badge-1.jpg" alt="Wisteria, the name badge." /></a>
        <p>
          Wisteria, the name badge.
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/name-badge-2.jpg"><img src="assets/images/goatmire/name-badge-2.jpg" alt="By Gus Workman, Protolux Electronics." /></a>
        <p>
            By Gus Workman, Protolux Electronics.
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/men-at-workman.jpg"><img src="assets/images/goatmire/men-at-workman.jpg" alt="Gus fixing and hacking on boards with a crew." /></a>
        <p>
            Gus fixing and hacking on boards with a crew.
        </p>
    </div>
</div>
<p>If there was one thing that didn&rsquo;t go at all as planned it was the badge hack session during this day. It was fine. We covered the state of things, Gus shared more about how it worked. But it was planned to be more workshop and hacking. It didn&rsquo;t turn out that way because we hadn&rsquo;t had enough stuff prepared. It was the most shit-show thing of the entire day and it was .. fine. Not very shit at all.</p>
<p>I also tried not to close on logistics. &ldquo;Don&rsquo;t open or close on logistics.&rdquo; is one of my big takeaways from The Art of Gathering by Priya Parker. Love that book. During this day I did say a few words to wrap up the day. This was just after the last speaker, Damir Batanovic, had impressed us thoroughly with his drone and video shenanigans, but I had handled the end logistics before he spoke.</p>
<p>MC:ing felt smooth enough. I even got a chant going for Frank Hunleth and got to introduce him as if he were a pro fighter. I had a really fun time facilitating, informing, introducing throughout the days. I got really comfortable and we had few issues, just dealing with the occasional HDMI issue.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/damir-1.jpg"><img src="assets/images/goatmire/damir-1.jpg" alt="Damir Batinovic flying one of his drones." /></a>
        <p>
            Damir Batinovic flying one of his drones.
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/damir-2.jpg"><img src="assets/images/goatmire/damir-2.jpg" alt="Damir showing the camera feed." /></a>
        <p>
            Damir showing the camera feed.
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/frank-restarts.jpg"><img src="assets/images/goatmire/frank-restarts.jpg" alt="Frank and the golden rules of restarts." /></a>
        <p>
            Frank and the golden rules of restarts.
        </p>
    </div>
</div>
<p>Feedback from the first day was extremely positive. I went to my hotel room, I showered, I tried to rest. I failed. I revised MC notes to update them with some missing details for the next days. I had food alone, in quiet. Then I went to the bar for a little bit, got to hang with the people and catch the good mood. I had a single beer and was in bed by eleven. I slept well and was in good shape the next day.</p>
<h2 id="day-2-goatmire-elixir-starts">Day 2: Goatmire Elixir starts</h2>
<p>Outfit. Very fancy. Cravat, waistcoat, blazer styled like old furniture. This time to show people that we are taking it up a notch. Pack extra outfit and lots of gear because this is the weird start. I have a part to play. Breakfast. At the venue from 7.10 probably. Attendees get in at 8. We kick off at 9.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/fancy-1.jpg"><img src="assets/images/goatmire/fancy-1.jpg" alt="Fancy outfit 1." /></a>
        <p>
            Fancy outfit 1.
        </p>
    </div>
</div>
<p>In the green room (another perk of operating in a theater) activity is frantic and fun. Zach Daniel has tapped me, Sasa Juric, Shannon and Parker Selbert into speaking roles in his keynote. And then on top of that a whole Ash ensemble of Josh Price, Rebecca Le, James Harton and Barnabas Jovanovics. We are all outfitting ourselves. This talk is weird.</p>
<p>Sam Aaron was jamming introductory music as attendees filtered in and got settled. When he walked off stage the intro stinger played. This was my attempt at capturing the sense of Goatmire Elixir in my head. I had asked speakers for short clips to include. They obliged and I cut this together. Speakers had seen it before the event. Attendees came in cold.</p>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/om4m7ua4Nuk?si=wMip1WmY-5FU2MPn" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<p>Cue Zach. I&rsquo;ll spoil the talk a little bit. He does not actually announce a new, real, Ash library. But he does break the timeline. One after another we bring him the stories of broken timelines related to the Elixir ecosystem.</p>
<p>It was a whole thing.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/zach.jpg"><img src="assets/images/goatmire/zach.jpg" alt="Zach, guilty of the whole thing." /></a>
        <p>
            Zach, guilty of the whole thing.
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/sasa-rammstein.jpg"><img src="assets/images/goatmire/sasa-rammstein.jpg" alt="mix new solar_system" /></a>
        <p>
            mix new solar_system
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/ash-team.jpg"><img src="assets/images/goatmire/ash-team.jpg" alt="Ash team assembled!" /></a>
        <p>
            Ash team assembled!
        </p>
    </div>
</div>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/future-lars.jpg"><img src="assets/images/goatmire/future-lars.jpg" alt="Future me." /></a>
        <p>
            Future me.
        </p>
    </div>
</div>
<p>It was also another cold open. I end up needing to MC in my future post-apocalyptic time traveler kit for a bit as I kick off the day and lead us into the next thing. Then I hurry off to get fancy again.</p>
<p>From the day before I have the feeling down for how talks go for me as the organizer. I start off on stage introducing the speaker, I make sure they get rolling and possibly help with HDMI or stalling for time. Once rolling, I leave and go check on things and let some of the nervous energy that I&rsquo;m made out of simmer out by walking around a lot. Rinse and repeat.</p>
<p>Many great talks through the day. The sense of excitement and fun among the attendees was fantastic. We eventually received the Wisteria batteries and handed those out. The devices were not just for NervesConf. <em>Everyone got one.</em> The mood was stellar. Catering did well, coffee was available, people were generally not hungry.</p>
<p>Towards the end I gave a few notes, talked a bit of logistics. And then handed things off to Sasa Juric for the last iteration of the year of the talk he has been touring that has not yet been released on video. Some say we got the best version. Tell Me A Story.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/sasa-1.jpg"><img src="assets/images/goatmire/sasa-1.jpg" alt="Sasa Juric - Tell Me A Story" /></a>
        <p>
            Sasa Juric - Tell Me A Story
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/sasa-2.jpg"><img src="assets/images/goatmire/sasa-2.jpg" alt="Feat. the Skull" /></a>
        <p>
            Feat. the Skull
        </p>
    </div>
</div>
<p>It was sublime. I was riveted. When the Erlangelist asks you for 55 minute slot to present a three act monodrama you give it to him. When he asks for a skull you order the skull. And then he will enrapture your crowd and grab a standing ovation on his way out. And he really thoroughly lectured me on making better PRs&hellip;</p>
<p>The energy of this evening was wild. I&rsquo;ve never had so many people thank me in my life. I&rsquo;ve never had so many people tell me about my own event in bewildered excitement. I could not be smiling more.</p>
<p>Partially I was smiling because I was one of five people among the nearly 200 involved that knew how the next day starts. Everyone was curious what to expect from another day at Goatmire but no-one was ready. To make sure they didn&rsquo;t discount the start, I pinged @everyone on Discord and teased that they should not miss it.</p>
<p>One beer. Maybe two. In bed at a reasonable hour.</p>
<h2 id="day-3-goatmire-elixir-ends">Day 3: Goatmire Elixir ends</h2>
<p>Other fancy outfit, darker, more somber. Breakfast. Head to venue. Piano music hits me the moment I enter. It is on.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/fancy-2.jpg"><img src="assets/images/goatmire/fancy-2.jpg" alt="Somber fancy" /></a>
        <p>
            Somber fancy
        </p>
    </div>
</div>
<p>I knew Flora would bring a good talk. She&rsquo;s an engaging person, fastidious about delivering and smart as a whip. But when she had seen Sasa give Tell Me A Story at Gig City Elixir 2025 she realized what you could actually do at a conference and she asked if she could completely change her talk.</p>
<p>Her mother did puppetry professionally. Flora has done a bunch of puppetry with her mother, internationally. Our conversation spiraled into shadow puppets, old-school overhead projectors and repeatedly asking &ldquo;is it okay if it&rsquo;s weird?&rdquo; at which I would respond &ldquo;fuck yes, please, kindly make it weirder&rdquo;. My favorite was &ldquo;can I bring my husband so he can play piano?&rdquo;. I was in deep already. I was game for anything.</p>
<p>The grand piano was out. The light of the old projector was suitably flawed compared to the previous day. Another cold open. It could not have been more warm. As she spoke she found ways to put tears in my eyes. Odd that.</p>
<p>The puppetry was riveting. The scissors were .. abrupt. It was a beautiful, awe-inspiring performance. The standing ovation was a given. While overhead projectors have certainly seen use in computer science before I think this was truly unique. I could not be more pleased.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/flora-1.jpg"><img src="assets/images/goatmire/flora-1.jpg" alt="Flora's welcome" /></a>
        <p>
            Flora's welcome
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/flora-2.jpg"><img src="assets/images/goatmire/flora-2.jpg" alt="Flora's story" /></a>
        <p>
            Flora's story
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/flora-3.jpg"><img src="assets/images/goatmire/flora-3.jpg" alt="Flora speaking" /></a>
        <p>
            Flora speaking
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/flora-4.jpg"><img src="assets/images/goatmire/flora-4.jpg" alt="Puppeteering" /></a>
        <p>
            Puppeteering
        </p>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/flora-5.jpg"><img src="assets/images/goatmire/flora-5.jpg" alt="Piano music and birds" /></a>
        <p>
            Piano music and birds
        </p>
    </div>
</div>
<p>This talk was recorded (technology be willing) but it will not be published in the near term if ever. That is not under mine or Flora&rsquo;s control for now.</p>
<p>The day continued with people somewhat stunned from that initial presentation. Later The Oban Murders also stuck out as a talk with fabulous showmanship and a very funny performance by the Selberts.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/oban.jpg"><img src="assets/images/goatmire/oban.jpg" alt="The Oban Murders" /></a>
        <p>
            The Oban Murders
        </p>
    </div>
</div>
<p>I also gave my talk during this day. Prior to it I stole a nap, or at least zonked out, in the green room for a bit. I suppose the days had caught up with me. When it was time for my talk. I had slides. I had notes. And maybe I should have used my slides, for clarity, but I felt so comfortable on this stage at this point and I knew the contents of <a href="/booting-5000-erlangs-on-ampere-one.html">my 5000 Erlangs</a> topic so well that I just went for it with a demo in hand and a little bit of web browser. I think I stole those style notes from Sam Aaron&rsquo;s earlier talk on Tau5 actually. I think my talk went fine or even well. I did not know that I had a good line-up at the time when I added my own name to it. If we do another one I probably won&rsquo;t bother speaking though I do enjoy it.</p>
<p>As we approached the end I had the luxury of addressing the crowd for the last time. I was just launching into a few more personal emotional notes about what this all meant to me when the frantic technical rigging happening behind me <em>really</em> needed a UK-to-EU power adapter. We improvise. Thanks to <a href="https://oferlund.se/">Oferlund</a> for hooking us up. He also provided about half the music used in the breaks. The other half was local metal band <a href="https://www.youtube.com/@tvivelband2616/videos">Tvivel</a>. The ending notes were a pleasure to give. A bit of logistics, lots of thank yous. I was so pleased with the entire event and I was excited for the final presentation. Things were good and some adapter tech issue improv did not shake me in the slightest.</p>
<p>Then Sam Aaron played us home by teaching us the fundamentals of music, the fundamentals of programming music and then showed us what it can truly do. I spoke to at least 3 people in the staff who are not programmers who expressed interest in trying their hand at Sam&rsquo;s Sonic Pi tool. I have already tried it. I <a href="https://www.youtube.com/watch?v=suH_goWVBeA">love it</a>. You can <a href="https://sonic-pi-studio.teachable.com/p/sonic-pi-introduction">learn it with Sam&rsquo;s course</a> that supports his open source work.</p>
<p>People who&rsquo;ve never seen Sam Aaron do his thing before were in for a treat. People who know him were thrilled. It was awesome.</p>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/sam-1.jpg"><img src="assets/images/goatmire/sam-1.jpg" alt="Sam Aaron performing" /></a>
    </div>
    <div class="group">
        <a href="assets/images/goatmire/sam-3.jpg"><img src="assets/images/goatmire/sam-3.jpg" alt="Sam Aaron performing" /></a>
    </div>
</div>
<div class="images">
    <div class="group">
        <a href="assets/images/goatmire/sam-2.jpg"><img src="assets/images/goatmire/sam-2.jpg" alt="Sam Aaron performing" /></a>
    </div>
</div>
<p>Each day the venue staff were a bit close to the limit for how many hours they were allowed to work. Not over it to my knowledge but close. So we tried to clear out promptly. I&rsquo;d shouted people out of the venue both evenings prior. When I tried it this time. Someone shouted about thanking me and suddenly, in the fully lit theatre, not full but well seated and with two balcony levels of people. I got a standing ovation. This was hard to absorb. It was moving. It was very gratifying. Thank you all.</p>
<p>Then I ran them out into the streets for real. We tore our stuff down and let the staff do their thing. Me and Stina, my wife who was on logistics and reception throughout the event went to eat with a couple of speakers and then joined the impromptu party at Bank 28. The bar had offered a discount for anyone with a badge so it became quite popular.</p>
<p>I was not as careful about bedtime this evening and I didn&rsquo;t have to pay for a single drink throughout the night. People were very kind.</p>
<p>My heart was full. I was just happy. Satisfied. It was a very weird state of being that was thoroughly enjoyable.</p>
<h2 id="bonus-day-ash-summit-2025">Bonus Day: Ash Summit 2025</h2>
<p>Because we accidentally accumulated most of the Ash framework&rsquo;s core team in person for the first time with this event I helped Alembic put together a little shindig at another venue on the Saturday. Being responsible for the keys I showed up and checked people in, watched the door and goofed off on my computer.</p>
<p>I am actually quite interested in Ash but I was checked out. Not sleepy but bone tired. People seemed to enjoy the summit. I think we checked 56 people into this sideshow. Then I had thai food with some friends. Then I went home. I slept a lot that weekend.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>There were a few major things people brought up when they grabbed me quickly during the conference:</p>
<ul>
<li>&ldquo;Thank you for making this happen&rdquo;</li>
<li>&ldquo;You must be so tired!&rdquo;</li>
<li>&ldquo;Will there be another one?&rdquo;</li>
</ul>
<p>On the first. I mean &ldquo;you&rsquo;re welcome&rdquo; would seem a bit flippant or arrogant. But essentially that. I was also happy. I was glad they were glad.</p>
<p>On the second. Surprisingly not that tired throughout the event. I feed off of this type of energy. I certainly burned a lot of resources and needed to catch up on it after but it was never brutal during the event for me. Which is surprising. I thought it would feel worse but I do often thrive under pressure, it has a focusing effect on me.</p>
<p>I want to make another one. It just needs to not screw with my day-to-day, work-life balance and vacation time too much. This one was brutal during July especially when I&rsquo;d normally be on vacation I had tons of print designs to finish, lots of random things to check off and meanwhile I was letting my team go due to the market being terribly poor, we were building a green house which took a lot of time and effort, I was trying to not lose too much income so I was trying to do billable work. It was honestly a shit-show. It wasn&rsquo;t mainly <em>because</em> of the conference but the conference was part of the bad workload. It was a really crap moment in time. We did okay through it but neither me or my wife appreciated that part of the experience.</p>
<p>There are many reasons to believe we can do it with a bit less grind on a repeat because we know more about what we are doing now. We would keep the venue, the accomodations. There are things to make simpler, there are things to control better. We can hand more of it to Helene this time because we know what we want. It will never be an easy lift if it is going to be an impressive event. The effort shows in the result. We will make a decision but not quite yet.</p>
<p>You can find a list to be notified about any next events, if you go to <a href="https://www.goatmire.com/">the website</a>.</p>
<p>I am incredibly pleased with how it came out. Thanks to all who contributed to that. My family, relatives, the staff, the sponsors, the volunteers, the speakers and the attendees. The town.</p>
<p>The response has been overwhelmingly positive. Thank you.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Booting 5000 Erlangs on Ampere One 192-core</title>
      <link>https://underjord.io/booting-5000-erlangs-on-ampere-one.html</link>
      <pubDate>Tue, 05 Aug 2025 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/booting-5000-erlangs-on-ampere-one.html</guid>
      <description>In the previous post on 500 virtual linux devices on ARM64 I hinted that I expected serious improvements if we got KVM working. Well. We&amp;rsquo;re there. Let&amp;rsquo;s see what we got going on.
Disclosure: I am running a conference called Goatmire Elixir which Ampere is a sponsor of. This post is not part of the sponsorship exchange as such. It is prep for my talk for the conference which uses the hardware they lent me.</description>
      <content:encoded><![CDATA[ <p>In the previous post on <a href="/500-virtual-linux-devices-on-arm64.html">500 virtual linux devices on ARM64</a> I hinted that I expected serious improvements if we got KVM working. Well. We&rsquo;re there. Let&rsquo;s see what we got going on.</p>
<p><em>Disclosure: I am running a conference called <a href="https://goatmire.com">Goatmire Elixir</a> which Ampere is a sponsor of. This post is not part of the sponsorship exchange as such. It is prep for my talk for the conference which uses the hardware they lent me. So this is your transparency notice, but fundamentally I am not making comparisons on whether they are better or not. I&rsquo;m learning and sharing about qemu and virtual Linux machines. Now I&rsquo;d love if they paid me to shill them a bit later and I&rsquo;d be transparent about that too. But this is not that :)</em></p>
<p>To recap. We have an Ampere One 192-core machine with 1 TB of RAM. The goal is to run as many virtual Linux IoT devices <em>using the Nerves framework</em>. We got 500 of them last time before I tried pushing any further. I also got a bit further on the same setup when I tried. Maybe 1000, I don&rsquo;t recall exactly. But there have been developments, so read on!</p>
<p>Briefly on Nerves: the framework treats the BEAM virtual machine like the OS and essentially only uses Linux for a kernel, drivers and the like. This means we can write much if not all of the embedded device in a productive high-level language running on a provenly robust and reliable environment with memory safety and solid recovery strategies. And it means your cloud integration developer doesn&rsquo;t risk seg-faulting the entire device while mangling JSON back and forth. Nerves also brings some best-practice tooling and conventions. Your init process is <a href="https://github.com/nerves-project/erlinit">erlinit</a>, your updates use <a href="https://github.com/fwup-home/fwup">fwup</a> to provide A/B partitions and factory reset, auto-failback, validation of firmware viability, disk encryption, delta updates, streaming updates and a bunch more.</p>
<p>The most interesting development is the thing you can probably learn the most from. Frank Hunleth who has been my co-conspirator and a massive help saved me from fighting u-boot by .. writing another bootloader. Introducing <a href="https://github.com/fhunleth/little_loader">little_loader</a>. This adorable tractor will load up your ARM64 qemu device, consult the uboot environment that Nerves uses, find a Linux kernel from information in that and then boot. Consequently it enables the A/B upgrade features and everything else that makes Nerves great.</p>
<p>Writing a boot loader is a little bit ridiculous. Frank knows his way around C and apparently ChatGPT knows a fair bit about ARM and qemu. Enough to be dangerous. And where it was wrong he could rummage around until he found the way. How he does what he does is beyond me but the result is a very small boot loader that you can probably read through and understand. So if you are curious about booting ARM64 or about how qemu starts things the code should be a worthwhile read.</p>
<p>We got a bit tangled up in EL1 vs EL2 when we only ever needed EL1 to work. EL2 on ARM is what you&rsquo;d run under if you want to be able to run VMs in your VMs so you can VM while you VM. And the version of qemu + KVM I got from Ubuntu doesn&rsquo;t seem to support that. We weren&rsquo;t interested in it either. At some point we might explore EL3 for secure boot and whatnot. Only time will tell.</p>
<p>One of the weirder challenges and something we haven&rsquo;t disentangled yet is that we have some compilation issue where using the toolchains I was using the non-debug build would hang while the debug one runs fine. For now I run the debug build of the bootloader. I think it was fine from GCC 15? Anyway, hopefully we pin that down at some point. But it tripped us up a few times when the bootloader would hang due that issue rather than any actual problems with the implementation.</p>
<p>KVM didn&rsquo;t really require anything extra aside from making sure we didn&rsquo;t go to EL2. And when we tried it on MacOS it worked great with HVF as well. Host-based ARM64 VMs are ridiculously fast and practical. As in booting to the full IEx prompt in single-digit seconds instead of double-digit. And they use about 500Mb less memory. And see, that&rsquo;s important. Because we want to shove as many as we can into this server I got access to.</p>
<h2 id="accelerated-on-host">Accelerated on host</h2>
<p>My very hacky project for running this stuff is <a href="https://github.com/lawik/amproj">available here</a>. This code is cribbed from <code>simple.sh</code>:</p>

  <div class="code  shell "  data-file="simple.sh" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">simple.sh</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">  qemu-system-aarch64 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-machine virt,accel<span style="color:#f92672">=</span>kvm <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-cpu host <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-smp <span style="color:#ae81ff">1</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-m 150M <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-kernel ../little_loader/little_loader.elf <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-netdev user,id<span style="color:#f92672">=</span>eth0 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-device virtio-net-device,netdev<span style="color:#f92672">=</span>eth0,mac<span style="color:#f92672">=</span>de:ad:be:ef:00:01 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-global virtio-mmio.force-legacy<span style="color:#f92672">=</span>false <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-drive <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>none,file<span style="color:#f92672">=</span>/space/disks/special.img,format<span style="color:#f92672">=</span>raw,id<span style="color:#f92672">=</span>vdisk <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-device virtio-blk-device,drive<span style="color:#f92672">=</span>vdisk,bus<span style="color:#f92672">=</span>virtio-mmio-bus.0 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>	-nographic</code></pre></div>
  </div>

<p>To go through it. We use <code>qemu-system-aarch64</code> to emulate an ARM64 machine. <code>aarch64</code> is the common shortname for ARM64, except sometimes on MacOS where I hear it can be <code>arm64</code>. We specify the <code>machine</code> to be <a href="https://www.qemu.org/docs/master/system/arm/virt.html">virt</a>. Previously we&rsquo;d leave it there but now we use an <a href="https://www.qemu.org/docs/master/system/introduction.html">accelerator</a> named KVM (<a href="https://linux-kvm.org/page/Main_Page">Kernel-based Virtual Machine</a>). It is the virtualization mechanism included with Linux and qemu can integrate with that to accelerate the execution. This also requires <code>-cpu host</code>, meaning, we are no longer trying to emulate a <code>cortex-a53</code> processor. We are trying to run on the host processor, whatever that is. <code>host</code> means that we emulate the host CPU, or at least as much as qemu and the KVM accelerator can support of what the host can do. This is where we drop about 500Mb of memory overhead. We no longer have to have a pretend ARM chip in memory because we have an actual ARM chip to run on. That&rsquo;s my understanding at least. Would love notes on that.</p>
<p>We only give it 1 core via <code>-smp 1</code> and we give it 150Mb memory with <code>-m 150</code>. Skipping ahead we give it virtual Ethernet and a <a href="https://docs.kernel.org/driver-api/virtio/virtio.html">virtio</a> block storage drive. You&rsquo;ll see a lot of virt and virtio when doing this stuff. And with <code>-nographic</code> we tell it to not bother trying to pop up a GUI window, so we get our console in the terminal. I&rsquo;ve done all the work over SSH so that&rsquo;s definitely my preference.</p>
<p>The disk we provide runs from the raw disk image file <code>special.img</code> which was generated using <code>fwup</code> based on the Nerves project <code>amproj</code> I mentioned earlier. If you build that project with <code>mix firmware</code> you can then run:</p>

  <div class="code  shell " >
    <div class="meta">
      <span class="language">shell</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">fwup -a -i amproj.fw -d special.img -t complete</code></pre></div>
  </div>

<p>That&rsquo;ll give you an image file that contains a full Nerves system. The stuff written to disk is:</p>
<ul>
<li>A uboot env formatted chunk of data. We don&rsquo;t use uboot this time but we used that format.</li>
<li>A linux kernel, not on a filesystem. Just written to the disk. RAW!</li>
<li>An MBR and some partitions:
<ul>
<li>Root filesystem A (squashfs, read-only)</li>
<li>Root filesystem B (squashfs, read-only)</li>
<li>Application data partition (f2fs, read/write)</li>
</ul>
</li>
</ul>
<p>The uboot env is used to tell the bootloader important things about the A/B upgrade process as well as where to find the kernel to load as well as what <a href="https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html">kernel cmdline</a> to use which is how we tell it what root filesystem to use.</p>
<p>The only config I put into the loader is to set the offset where it can expect the uboot-env and I set that at build-time.</p>
<h2 id="promising-results">Promising results</h2>
<p>NervesCloud received 3389 simultaneous connected devices before the server hit me with the OOM killer. It was probably running a few more but around there. So each VM is:</p>
<ul>
<li>Bootloader</li>
<li>Linux</li>
<li>erlinit</li>
<li>BEAM/ERTS</li>
<li>Nerves base functionality</li>
<li><a href="https://github.com/nerves-hub/nerves_hub_link">NervesHubLink</a> for connecting to NervesHub</li>
</ul>
<p>I have had 3000 devices running stable and then I started to see &ldquo;fun&rdquo; challenges. For one thing, our NervesCloud hosts were looking a bit tight on memory because all these devices connect from the US west coast and we were only running a single node in that region. I scaled that up a smidge to make sure I didn&rsquo;t bother any paying customers.</p>
<p>The VMs are super well-behaved, the Ampere CPU just works. The memory usage is roughly where I&rsquo;d expect it. 150-250 total I think. There are probably things I can do to make it behave a little more tighter. Will explore that if time allows.</p>
<p>Then I ran my first demo workloads. As the purveyor of the finest Over-the-Air updates for embedded devices we here at NervesCloud.. I kid. But I wanted to shove lots of updates at them and see what that did. The updates process is a lot of compression, decompression and IO. Probably mostly IO-bound but if the devices would be struggling for CPU that&rsquo;d be noticeable. If the memory usage exploded, that&rsquo;d be noticed very quickly.</p>
<p>It worked. Not really any problems. I limited the concurrency of the update to 1000 and it couldn&rsquo;t hand out the updates faster than they completed so it tended to hover around 200-300 concurrent updates happening. Or at least that&rsquo;s my understanding of what happened. Did I mention the KVM setup is pretty fast?</p>
<p>I logged some issues about UI behavior as I was watching things live and trying to adjust things. It seems like good guy Nate Shoemaker already has a fix in flight for this. There may be more details. When you get a lot of progress reports the LiveView UI perhaps shouldn&rsquo;t try to refresh all the things all the time.</p>
<h2 id="memory-tuning">Memory tuning</h2>
<p>Frank gave me some tips about tuning Linux memory usage and tuning BEAM memory usage. When I looked into his advice I ended up doing a few things:</p>
<p>For BEAM VM, we <a href="https://github.com/lawik/amproj/commit/cb5919eee5b5c58321cf67aaff388c5db05a1ccc#diff-b9fbf8080c62b37068a3fefe69eeed4d0b7e801049c87b9aaaf721c2d5ed47afR38">change the allocators</a>. This should use less memory and probably trades off in raw performance. Which is fine for this purpose.</p>
<p>Erlang release, <a href="https://github.com/lawik/amproj/commit/cb5919eee5b5c58321cf67aaff388c5db05a1ccc#diff-b9fbf8080c62b37068a3fefe69eeed4d0b7e801049c87b9aaaf721c2d5ed47afR26">use default mode</a> instead of embedded. Which probably makes it boot a bit faster, makes it use less memory but it could lead to surprising delays and growing memory usage later if it loads code ad-hoc. The use of embedded mode is helpful in making the release behave much more consistently, is my understanding.</p>
<p>Made <a href="https://github.com/nerves-project/nerves_system_qemu_aarch64/commit/149a0545312df0d422e44975babe9c9b15247ce7">a bunch of adjustments to Linux memory usage</a>. Using <code>zram</code> was suggested by Frank. Then I checked with (famous ML model) Claude to get hints about what knobs were available to tune on Linux because Frank hinted that it might be caching a bit much and I never know where to start when it comes to what I can do to the Linux kernel. It had some suggestions, I looked those up, found articles that matched the claims that this might reduce memory usage. Changing swappiness, dirty ratios and <code>vfs_cache_pressure</code> like I knew what I was doing and it sure seems to have improved things.</p>
<p>I know I could play with different allocators, my co-founder Josh has been doing that for NervesCloud recently. I think I could also do something with virtual balloons to reclaim memory and essentially over-provision VMs but I haven&rsquo;t got there yet.</p>
<p>This memory tuning led to some interesting further runs where we ran a solid 5100 devices and I could have pushed it a bit further. I just didn&rsquo;t have time and could be bothered to do more math at the time. The VMs are now started with 110 MB of RAM on the inside and they seem to run steady around 160 MB RES according to htop. The people I&rsquo;ve talked to at Ampere indicate that I&rsquo;m probably running the most VMs anyone has ever ran on their hardware. Which is fun. I&rsquo;m not even running tiny VMs. I could make a Buildroot system that does nothing and run another gajillion probably. But this is much closer to a real device and workload.</p>
<h2 id="the-utility-of-it-all">The utility of it all</h2>
<p>Honestly, getting a chance to run significant, not massive, but significant workloads against a SaaS is pretty useful. But the work we&rsquo;ve put in now means we can tidy up this Nerves system and make it part of supported Nerves tooling. This would make it easy to run stuff &ldquo;on device&rdquo; without physical hardware. It would make running more detailed tests of Nerves functionality much more feasible as well. Essentially you&rsquo;d need an ARM64 Linux box with KVM or an Apple Silicon Mac and you&rsquo;d get the blazing fast ones. Or you can absolutely get by with the emulated, more demanding things from the x86 side of things. There is a lot we can do with a full-featured qemu-system for ARM devices.</p>
<p>While my experimentation is a bit of a stunt and mostly for the joy of experimentation and Frank&rsquo;s bootloader is mostly about learning the end result is still that we have produced something we should get good mileage out of.</p>
<p>Heck that MacOS thing. I just tried the <code>DELAY=1 COUNT=200 CHUNK=10 ./run.exs</code> after modifying the script to use <code>hvf</code>instead of <code>kvm</code> on my M2 MacBook Air. I think I had 50 VMs when I ran out of disk. Solvable problem, but not throwing out my photo library right this minute.</p>
<h2 id="further-work">Further work</h2>
<p>I need to look at how KVM and <a href="https://en.wikipedia.org/wiki/Non-uniform_memory_access">NUMA</a> interact and if/how I can pin. I don&rsquo;t think I&rsquo;ll hit problems where caches and pinning matter all that much but it would feel better. When the VMs are at rest after booting the overall system CPU usage is generally less than 20% running thousands of VMs. Mostly idle, yes, but there are things happening in all of them.</p>
<p>Should run the workload with some graphs to see what is actually happening big picture. Right now I&rsquo;m mostly going &ldquo;hey, it is STILL running, eh!?&rdquo;. Which is fine enough when figuring out if it fits in memory.</p>
<h2 id="tidying-up">Tidying up</h2>
<p>We are in the process of tidying up <a href="https://github.com/nerves-project/nerves_system_qemu_aarch64">nerves_system_qemu_aarch64</a> and then it should get a proper release and some docs. It has a mix task for generating an appropriate qemu command for you. So this all becomes a part of Nerves. Over time we should be able to build some really nice tooling based off of this. And if you have ideas you should be able to pick it up and run with it already.</p>
<p>Really enjoying this deeper dive into things I&rsquo;ve only been at the periphery of. Learning a lot of Linux, getting to really get into it with qemu, performance tuning for both the BEAM, Linux and virtualization. It is a ton of fun to see how far you can push the hardware.</p>
<p>Alright, that&rsquo;s enough words. Let me know what you think and if there is anything in particular you&rsquo;d like me try in and around this. Thanks for reading, hit me up on <a href="mailto:lars@underjord.io">lars@underjord.io</a> or <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> or wherever you find me.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>500 virtual Linux devices on ARM 64</title>
      <link>https://underjord.io/500-virtual-linux-devices-on-arm64.html</link>
      <pubDate>Wed, 30 Jul 2025 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/500-virtual-linux-devices-on-arm64.html</guid>
      <description>This is the first part of an experimental journey as I explore how many instances of my favorite IoT framework I can run on the 192 core Ampere One.
Background I work on the Nerves project which is an IoT framework providing best-practice underpinnings and support so that you can build your IoT hubs, smart thermostats and the like with a safe and productive high-level language on a runtime known for reliability, resilience and consistent performance.</description>
      <content:encoded><![CDATA[ <p>This is the first part of an experimental journey as I explore how many instances of my favorite IoT framework I can run on the 192 core Ampere One.</p>
<h2 id="background">Background</h2>
<p>I work on <a href="https://nerves-project.org/">the Nerves project</a> which is an IoT framework providing best-practice underpinnings and support so that you can build your IoT hubs, smart thermostats and the like with a safe and productive high-level language on a runtime known for reliability, resilience and consistent performance. The language being <a href="https://hexdocs.pm/elixir/introduction.html">Elixir</a> and the runtime being <a href="https://www.erlang.org/doc/readme.html">Erlang&rsquo;s BEAM virtual machine</a>.</p>
<p><em>If you want more about Erlang, we&rsquo;ve had <a href="https://www.beamrad.io/82">Björn on BEAM Radio</a> talking a lot about his work on the compiler and runtime.</em></p>
<p>As part of my frequent collaborator <a href="https://glesys.com/">GleSYS</a> sponsoring Sweden&rsquo;s first Elixir conference <a href="https://goatmire.com">Goatmire Elixir</a> (I would be shilling but we essentially don&rsquo;t have any tickets left *shrug*) they suggested we might connect with <a href="https://amperecomputing.com/">Ampere</a> as they have this particularly interesting hardware with the Ampere One server CPUs (you may have seen the 192 core, 3.2 GHz one discussed) and we turned it into a joint sponsorship. Since I didn&rsquo;t have a talk topic lined up we discussed me doing something with their hardware which seemed fun. I love experimenting with impressive hardware.</p>
<p><em>Disclosure: This post is not part of the sponsorship exchange. They get some posts on socials and space in my newsletter along with branded presence at the event. This is me reporting on what I&rsquo;m up to and providing the background for that. But I want to be transparent, they have supported the not-for-profit that runs the conference which I am organizing.</em></p>
<h2 id="the-runtime">The runtime</h2>
<p>If you know the BEAM you know it is highly concurrent and parallel. By default it starts one scheduler thread per core available and then does work stealing across those to ensure efficient use of the cores. Based on anecdata from a friend who tests these sorts of things (he has data, he shared it anecdotally) the BEAM does not scale arbitrarily with this amount of cores. I&rsquo;ve speculated whether that&rsquo;s due to NUMA but I don&rsquo;t know the architecture of the chips well enough to say really. There would be overhead when running many schedulers of course and whatever coordination is needed. I know Meta has contributed recent updates to Erlang/OTP that should improve the many core performance. I need to see if I can find a really good benchmark for testing the limits of a single BEAM on this thing. Anyway, this was not what I primarily wanted to do.</p>
<h2 id="going-embedded">Going embedded</h2>
<p>This is an ARM64 part. And I mostly deal with ARM64 on my laptop or in the shape of various embedded Linux boards for Nerves usage. Now I could put Nerves on the server and call that good but that is just one massive Linux board. And this has been done with <a href="https://github.com/nerves-project/nerves_system_x86_64">nerves_system_x86_64</a> and specifically for <a href="https://github.com/nerves-project/nerves_system_vultr">Vultr VPSes</a>. While it would have some interesting challenges I&rsquo;m not sure it would be all that interesting in terms of a demo. And I also <a href="https://nervescloud.com">have to shill NervesCloud</a> which is the hosted version of <a href="https://github.com/nerves-hub/nerves_hub_web">NervesHub</a> that me and Josh Kalderimis offer. Over-the-Air updates of firmware is a nice and lively type of demo. Also, while we know NervesHub scales to hundreds of thousands of devices we don&rsquo;t very often get to run tests with a lot of full-fledged concurrent devices. We can simulate a lot of connections but having them backed by &ldquo;real&rdquo; devices is a different matter.</p>
<p>You will note that this post claims 500 virtual devices and you might think &ldquo;that is not a lot&rdquo; and you&rsquo;d be right. Each device being single-core it shakes out to every device getting about 1.2GHz to play with. They should not need that much. I very much hope that a later post about this project will have a much higher number. But we are getting ahead of ourselves, we are not there yet.</p>
<p>The goal I have in mind. Running as many virtual Linux devices packed with Nerves as feasible on the hardware I&rsquo;ve been handed access to.</p>
<p>Knowing I had a deadline of 10th of September and a lot of stuff to do until then both for putting together the conference, helping other presenters, I felt I needed insurance. So I reached out to Frank Hunleth (creator of Nerves) and kindly checked that if I got stuck on this, could he help unstick me. He graciously obliged and when I got the server credentials I went into it with his DMs as my backstop.</p>
<h2 id="making-a-custom-nerves-system">Making a custom Nerves system</h2>
<p>My approach to Nerves, embedded and IoT is top down. I come from the outside, from higher up in the stack, web and cloud stuff. The parts I know the best are the parts that face the user and the parts I&rsquo;m learning are the foundations and underpinnings. Nerves made embedded more approachable to me and over time I&rsquo;ve unravelled the helpful structure and come to understand more and more of it. I&rsquo;ve made initramfs-booting work with dm-verity for verified boot. I&rsquo;ve compiled more kernels than I&rsquo;d ever expected. I&rsquo;ve made minor contributions upstream to buildroot. I&rsquo;m getting there.</p>
<p>I still tend to start a custom Nerves project from some foundation. I knew I shouldn&rsquo;t start from <a href="https://github.com/nerves-project/nerves_system_rpi4">a Raspberry Pi system</a> because those are weird, unique and do things no other ARM systems do. <code>config.txt</code>, <code>cmdline.txt</code>, the FAT <code>/boot</code> partition dealio. Compared to most boards I&rsquo;ve seen since, it is weird. Approachable in many ways but fundamentally weird. So I looked at <a href="https://github.com/nerves-project/nerves_system_bbb">the BeagleBone Black system</a>, it is lauded as a good workhorse board. Then I realized it is ARM 32-bit and also Frank mentioned that the software around that board is kind of hairy. So it would probably need a lot of adaptation.</p>
<p>So I grabbed a fairly modern ARM 64 system I knew worked which was <a href="https://github.com/redwirelabs/nerves_system_iot_gate_imx8plus">the IOT Gateway iMX8 Plus</a> by Redwire Labs. And I went to town. It had a lot of Compulab, NXP and iMX-specific stuff in there but at least it was ARM 64 and I am comfortable tearing up a Linux defconfig and a Buildroot defconfig.</p>
<h2 id="running-virtually">Running virtually</h2>
<p>My goal is running under <a href="https://www.qemu.org/">qemu</a> with KVM acceleration so I&rsquo;m running ARM64 guests natively on ARM64 hardware. You have the command <code>qemu-system-aarch64</code> which can run ARM 64 systems. And if you grab a <a href="https://github.com/buildroot/buildroot">buildroot</a> you can use the <a href="https://github.com/buildroot/buildroot/tree/master/board/qemu/aarch64-virt">qemu_aarch64_virt</a> board config to get something that runs essentially.</p>
<p>So I could prove fairly easily that this was possible, confirming much of what my web searches had indicated about feasibility. I even managed to confirm that I could run a buildroot build under <code>accel=kvm</code> and <code>-cpu host</code>.</p>
<p>Now qemu can start Linux directly which is kind of cheating compared to most boards. Usually you need a bootloader for various reasons. But with qemu you can just throw a kernel at it, provide a root FS disk image and off it goes. Buuuuut. That removes some of Nerves' greatest features. A/B partitions, factory reset, safe updates. Those rely on boot loader features. So most Nerves systems use <a href="https://github.com/u-boot/u-boot">u-boot</a> (hereafter known as uboot because that&rsquo;s what buildroot calls it).</p>
<h2 id="failing-at-uboot">Failing at uboot</h2>
<p>I said I know my way around a Linux defconfig and a Buildroot defconfig. Well. I don&rsquo;t know my way around a uboot defconfig. Not really. I have done a few run-ins with uboot previously preparing for this experiment but essentially I don&rsquo;t know it very well and there is a lot to it. It is featureful. I wonder how long it will take until I need to learn swupdate, I think that&rsquo;s another defconfig. <em>Anyway.</em> I found the documentation for <a href="https://docs.u-boot.org/en/stable/board/emulation/qemu-arm.html">the uboot qemu ARM support</a> and verified that it worked.</p>
<p>And then I spent untold hours trying to translate all these parts into the nerves system. I got pretty far with Linux and buildroot, the parts I already know decently. I got somewhere, multiple times, only to realize I was way off. Very often the only response on a build was that qemu would hang and print nothing. That is honestly still a common response.</p>
<p>This was an area where Frank chimed in, in the strangest way possible. I don&rsquo;t want to spoil entirely what he got up to until it is public and I can make his part proper justice. But suffice to say he provided a solution that completely bypassed needing uboot. At least for now, maybe for the entire project. I still want to get better at uboot but I have to be pragmatic here and the solution Frank provided is  quite interesting and will be public eventually I&rsquo;m sure.</p>
<p>Meanwhile you can look at <a href="https://github.com/lawik/nerves_system_qemu_aarch64">my work-in-progress system</a> and <a href="https://github.com/lawik/amproj">the barebones project I use for it</a>. The docs are all wrong, there is no guidance. Just sharing if you are curious or want to help fix my uboot I suppose.</p>
<h2 id="aside-approaches">Aside: approaches</h2>
<p>Frank and I had a call where we resolved some weird errors I was seeing and discussed approaches. He is incredibly experienced in the embedded Linux realm and would have taken a very different approach to what I did. And I figure I should share it here because it is the wise way if you can do it.</p>
<p>He would not have used Nerves to get started. He would have started with just buildroot. I did verify things with buildroot and essentially backed my way into grabbing a working buildroot config for my target as I described above. But he would essentially make sure he got all of the desired Linux and system bits working through buildroot first. And then the A/B partition stuff with uboot or whatever mechanism the board might have. Before bringing those configs into a Nerves system to enjoy <a href="https://github.com/fwup-home/fwup">fwup</a> and Nerves for testing all those firmware updates. He applies a bottom-up approach because he already knows the foundations. I am getting to know the foundations so I tend to approach from the parts I know best. My approach is definitely more trial and error.</p>
<p>I can totally see his approach working now, even for me. I couldn&rsquo;t see starting there when I started this project. Now I feel like that possibility is closer. This is why it is hard to document &ldquo;how to make a custom system with Nerves&rdquo; because it ties together a bunch of embedded Linux know-how. How Nerves does things is a relatively small set of conventions on top of that which makes a compelling whole. Anyway, learn your buildroot if you want to make custom systems.</p>
<h2 id="running-virtual-nerves">Running virtual Nerves</h2>
<p>Yesterday we got the Nerves system running under qemu. Not with KVM, instead with <code>-cpu cortex-a53</code>. This should leave a lot of performance on the table. I hope we can get through the challenging bits of getting KVM and running directly on the host CPU. Let&rsquo;s just say currently qemu just hangs there but Frank certainly has ideas about how to fix it and arguably it should be fixable through uboot as well. Whichever way gets us there first.</p>
<p>But it is interesting to see how far we can push with less-than-optimal virtualization. So I finished up the Nerves firmware project to make it connect to NervesCloud and so on.</p>
<p>1 device. Up and running. Gotta implement generating serial numbers.</p>
<p>2 devices. Good, it works.</p>
<p>10 devices. No problem.</p>
<p>50 devices. No problem. Takes a bit to start.</p>
<p>500 devices. Well the host machine hit more than 450 load avg for a while there. Took a bunch of time to calm down. Then no issue. So I don&rsquo;t think we are taxing this machine beyond the startup.</p>
<h2 id="limits">Limits</h2>
<p>Why stop at 500? I ran out of evening. I did that yesterday. My last bit of time went to starting to make the devices report back to their orchestrating script when they are up and running so that I could potentially bring them up more gracefully. Then I can try to push more interesting numbers.</p>
<p>I&rsquo;ll have some figuring out to do. I&rsquo;m guessing I can bring them up at about 190 or so at a time without making it unnecessarily slow. And once each device is up and reporting in they don&rsquo;t spend a lot of CPU grunt. I hope that would make bringing up a couple of thousand somewhat smooth.</p>
<p>KVM support would almost certainly help make them boot much faster and improve things in general. My hope is to reach the point where memory is the limit. The BEAM is not particularly lean on memory, it ain&rsquo;t bad, but I&rsquo;m guessing I&rsquo;ll need 100-150 Mb per virtual device. I&rsquo;ll tune that if I see the need and have the time. I have 1 TB of RAM. In the best of worlds that ends up being the limiting factor and in rough math that means almost 7000 devices. I don&rsquo;t know if we can get all the way there. I&rsquo;m assuming there are bottlenecks I don&rsquo;t know about. I hit a small one that I believe we&rsquo;ve addressed. The base disk image was 2.4 Gb or so (mostly empty). I trimmed it down to a couple of 100 Mb. That was required to fit the 500 on the system disk. Now I think I have a new disk to play with so that fun little limitation should be thoroughly handled.</p>
<h2 id="useful">Useful?</h2>
<p>So this is actually taking us somewhere useful. Having efficient qemu builds of Nerves means we can build test harnesses and things for some fairly intricate functionality, like A/B updates and a bunch of the resiliency-mechanism around Nerves.</p>
<p>It would allow people to do host development against a virtual board for cases where that is desireable. This can be particularly useful in workshops and training, though I personally tend to prefer having the hardware.</p>
<p>It is essentially a starting point for running on any ARM virtual servers offered by cloud providers.</p>
<p>This should also be doable to translate into virtualized ARM 64 on top of Apple Silicon. This may mean we can drop the creaking Docker implementation from Nerves (use UTM with a Linux VM instead if you are doing Nerves on MacOS) and maybe get something better. And it would mean that running a ton of virtual Nerves devices for testoing things is actually feasible on my laptop.</p>
<p>Some of the features we develop for NervesHub are quite hard to test without proper devices and being able to spin up a small IoT startup at a moment&rsquo;s notice is really helpful. So yeah, I see a few different use-cases.</p>
<h2 id="next">Next?</h2>
<p>Let&rsquo;s push it, shall we?</p>
<p>So a few different things. I need to make that reporting-in on boot behavior work as intended and see how efficiently I can start and stop these things. And then I need to see how many we can actually do under the current setup. Because 500 was entirely arbitrary.</p>
<p>I may have nerd-sniped Frank into doing work towards the KVM support working. We&rsquo;ll see :)</p>
<p>Big thanks to Dave Cottlehuber for early help on getting to grips with this stuff. Massive thanks to Frank Hunleth as always helping me grow my understanding and enabling me to succeed when I flail. And thanks to GleSYS and Ampere for the hardware access.</p>
<p>This ain&rsquo;t over.</p>
<p><strong>Update:</strong> In truth, we have <a href="/booting-5000-erlangs-on-ampere-one.html">an update post</a>.</p>
<p>If you have thoughts or questions you can reach me on email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or via the fediverse <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Delta support - A tale of two firmware versions</title>
      <link>https://underjord.io/delta-support-a-tale-of-two-firmwares.html</link>
      <pubDate>Mon, 07 Jul 2025 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/delta-support-a-tale-of-two-firmwares.html</guid>
      <description>Binary deltas of firmware via fwup has been a supported feature for a while. There has also been a kind of rudimentary support for it in NervesHub. We can now remove the word rudimentary. There is now proper delta support in NervesHub. It is not in a tagged release quite yet but NervesCloud runs off of main and so can you if you would benefit from this.
(I initially wrote this up for the Nerves Newsletter but wanted it shareable in some other way)</description>
      <content:encoded><![CDATA[ <p>Binary deltas of firmware via fwup has been a supported feature for a while. There has also been a kind of rudimentary support for it in NervesHub. We can now remove the word rudimentary. There is now proper delta support in NervesHub. It is not in a tagged release quite yet but NervesCloud runs off of main and so can you if you would benefit from this.</p>
<p>(I initially wrote this up for the Nerves Newsletter but wanted it shareable in some other way)</p>
<p>One thing that prompted finalizing this work was that Frank Hunleth shipped the support for disk encryption combined with deltas for fwup. And since they use encryption this enabled us to make deltas work for SmartRent. I am very excited to see what the results are there as we might get some numbers on the savings at significant scale. Also, I learned a neat trick where I can write things for Frank in english and he&rsquo;ll write things for me in C. I wonder if I can scale that.</p>
<p>The PRs that enabled this:</p>
<p><a href="https://github.com/nerves-hub/nerves_hub_web/pull/2090">Enable firmware delta delivery</a>
This included making a new library called <a href="https://hexdocs.pm/confuse/0.1.3/readme.html">confuse</a> to parse the libconfuse style of config file that fwup uses. We needed to extract some information from the <code>meta.conf</code> files provided in .fw archives. This let us determine which resources have deltas enabled and which do not and only calculate deltas for the right ones.</p>
<p>This also includes <a href="https://github.com/nerves-hub/nerves_hub_web/blob/bc6f8df8e415c62d4c4bf17737607dd7e70cbe1d/test/nerves_hub/firmwares/delta_updater_test.exs">a bag of tests</a> that took a minute to get right to make sure that we produced the expected results with the deltas we generate.</p>
<p>This made deltas work.</p>
<p>If you created a deployment group on a product with deltas enabled it would immediately start making deltas for the firmwares that are in use within that deployment group. And with a bit of luck it all works out. But the control is very coarse-grained.</p>
<p>Coded up and worked through by me with some good review by Josh Kalderimis and Elin Olsson. Lots of help on fwup-related know-how from Frank.</p>
<p><a href="https://github.com/nerves-hub/nerves_hub_web/pull/2145">Move delta setting to deployment group</a>
This lets you try deltas with a subset of devices. Thanks Elin for that one and Josh for suggesting it. And subsequently we <a href="https://github.com/nerves-hub/nerves_hub_web/pull/2163">fixed a bug</a> in that update, thanks Nate Shoemaker.</p>
<p><a href="https://github.com/nerves-hub/nerves_hub_web/pull/2139">Add information about available deltas to deployment summary</a>
A first step towards making deltas a first-class citizen is to make it visible that they exist. This makes them show up on the deployment group page.</p>
<p>There is more work to be done.</p>
<p>In progress: <a href="https://github.com/nerves-hub/nerves_hub_web/pull/2179">Safer delta updates</a>
This is a large-ish PR because it also generalizes a few fwup-specific things and tries to make for a maintainable future.</p>
<p>The important parts are that the Confuse library is now updated to pull out more and better information and to understand which fwup versions allow which features. This means we can disqualify a device from delta updates because it has a fwup version on device that is too old to allow the update to succeed. Then we deliver the full update.</p>
<p>fwup first got support for deltas for &ldquo;raw&rdquo; image writes. Then for FAT filesystems. And very recently for encrypted filesystems. When this lands it should mean deltas become an option for pretty much any user of Nerves.</p>
<p>It also add the choice between delta and full update when pushing an update manually. Which is nice.</p>
<p>In progress: <a href="https://github.com/nerves-hub/nerves_hub_web/pull/2140">Log transfer savings from deltas</a>
Knowing if deltas matter for you and how much they matter is kind of important. And this PR will introduce us tracking that information in and surfacing it in the UI. Showing the savings per transfer and in total.</p>
<p>This can be exciting for a couple of reasons. Maybe you run NervesHub and need smaller updates. Or maybe you don&rsquo;t run NervesHub but want to shrink your updates using deltas. You can use this as a reference implementation for generating valid delta firmware images which is a bit finicky. In NervesHub it is just a checkbox telling the system whether it should generate deltas for you.</p>
<p>And when the PR for additional safety lands it will do a pretty solid job of figuring out cases where a delta could not work and just ship the full version instead.</p>
<p>There is also a quirk we need to look at in fwup. A delta update causes more reads than writes and this makes the progress bar kind of weird. We know :)</p>
<p>This has been awesome to work on. I expect it to make decent savings for companies that use it and I now understand the delta updates way better.</p>
<p>If you have questions or want to reach me you can use email <a href="mailto:lars@underjord.io">lars@underjord.io</a> or the fediverse <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Making of an Elixir conference</title>
      <link>https://underjord.io/making-of-an-elixir-conference.html</link>
      <pubDate>Fri, 04 Jul 2025 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/making-of-an-elixir-conference.html</guid>
      <description>It all started when I visited Gig City Elixir. Or maybe it started when I did a workshop in preparation for a Code BEAM in Stockholm. But that actually started from reading Priya Parker&amp;rsquo;s book The Art of Gathering. Maybe this has been coming for a while.
I&amp;rsquo;m making an Elixir conference and it happens 10-12th of September in Varberg, Sweden. Tickets are still available at the time of writing. I figured it might be interesting to hear one data point of pulling something like this together.</description>
      <content:encoded><![CDATA[ <p>It all started when I visited Gig City Elixir. Or maybe it started when I did a workshop in preparation for a Code BEAM in Stockholm. But that actually started from reading Priya Parker&rsquo;s book The Art of Gathering. Maybe this has been coming for a while.</p>
<p>I&rsquo;m making <a href="https://goatmire.com">an Elixir conference</a> and it happens 10-12th of September in Varberg, Sweden. Tickets are still available at the time of writing. I figured it might be interesting to hear one data point of pulling something like this together.</p>
<p>I may mention sponsors, those are companies I have an economic relationship with. This post is not an ad for them and is not particularly part of any deal. This post is in a small way an ad for the conference. But really I find the process of learning what goes into making things fascinating so this is me offering a behind the scenes of my first attempt.</p>
<h2 id="seeding-the-idea">Seeding the idea</h2>
<p>The idea for the conference formed in Chattanooga at Gig City Elixr and NervesConf US. The NervesConf pulled about 50 people and Gig City pulled around 150 I believe. Lovely conference, Maggie and <a href="https://pragprog.com/search/?q=bruce+tate">Bruce Tate</a> are great hosts and clearly care about making people happy, bringing in varied voices and putting on a cool thing without getting too pretentious. Love em. Also Todd Resudek offered me the opportunity, if I wanted it to use the NervesConf name for an EU event. That got wheels turning.</p>
<p>Still in Chattanooga, visiting <a href="https://www.seerockcity.com/">Rock City</a> in the evening, when I spoke to <a href="https://goatmire.com/speaker/andrea-leopardi">Andrea Leopardi</a> of the Elixir core team and he immediately said he&rsquo;d show up if I made it happen.</p>
<p>Poking around with some friends and acquaintances showed me I had the bones of a line-up. I bothered <a href="https://goatmire.com/speaker/sasa-juric">Saša Jurić</a> about being a speaker and he accepted around the end of the year.</p>
<p>I also poked around both locally here where I live and remotely in my community for sponsorship and funding and found some enthusiasm and angles. There is a foundation that owns the local bank that invests a lot in local events. They were interested in contributing, assuming it was not-for-profit.</p>
<p>Essentially the core of making me confident that I could pull together speakers and sponsor money is that I&rsquo;ve been operating in the Elixir community for about 7 years and I know enough people. If you want to put on an event, show up at events. Want good speakers? Know good people that give talks. It may sound simple and it kind of is. I haven&rsquo;t done this with the goal of making a conference. It is more like I&rsquo;ve accidentally accrued the raw materials required for a conference and I don&rsquo;t have enough self-doubt to stop myself.</p>
<p>We pinned down the venue to be this nice classic theater in Varberg. Scheduled it after the main tourist season. A tourist-friendly town like this is nice because there is good eating, decent drinking and lots of accomodations but you can&rsquo;t schedule during the busy season because everything is slammed.</p>
<h2 id="the-point-of-no-return">The Point of No Return</h2>
<p>Once we had enough confidence that we had a base line-up and some very likely money I bodged together a logo, website and announced the conference. All details TBD essentially. I first shared it privately for feedback and the feedback was excited. Then I put it out into my social feeds, Elixir Forum and the various appropriate channels for announcing things in Elixir Slack and Elixir Discord.</p>
<p>And the logo animates which was the important thing. Except on Safari on iOS, sometimes.</p>
<div style="background: black">
<svg id="logo" class="w-full max-w-[800px] mx-auto" width="210mm" height="64mm" version="1.1" viewBox="0 0 210 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
        <defs>
          <linearGradient id="c">
            <stop stop-color="#404040" offset="0"></stop>
            <stop stop-color="#202020" offset="1"></stop>
          </linearGradient>
          <linearGradient id="d">
            <stop stop-color="#f0f" offset="0"></stop>
            <stop stop-color="#7f7fff" stop-opacity=".33557" offset=".5"></stop>
            <stop stop-color="#0ff" offset="1"></stop>
          </linearGradient>
          <radialGradient id="b" cx="18" cy="200" r="200" gradientTransform="matrix(-.42835 1.9376 -1.9909 -.44014 766.66 355.55)" gradientUnits="userSpaceOnUse" xlink:href="#d">
            <animate attributeName="r" dur="20000ms" repeatCount="indefinite" values="200; 100; 300; 200;"></animate>
            <animate attributeName="cy" dur="30000ms" repeatCount="indefinite" values="200; 100; 300; 200;"></animate>
            <animate attributeName="cx" dur="40000ms" repeatCount="indefinite" values="18; 32; -32; 18;"></animate>
          </radialGradient>
          <radialGradient id="a" cx="369" cy="332" r="338" gradientTransform="matrix(.93814 .32929 -.20817 .59307 92.966 10.053)" gradientUnits="userSpaceOnUse" xlink:href="#c">
            <animate attributeName="r" dur="20000ms" repeatCount="indefinite" values="338; 200; 400; 338;"></animate>
            <animate attributeName="cy" dur="30000ms" repeatCount="indefinite" values="332; 200; 400; 332;"></animate>
            <animate attributeName="cx" dur="40000ms" repeatCount="indefinite" values="369; 300; 420; 369;"></animate>
          </radialGradient>
        </defs>
        <g transform="translate(-5.2665 -27.865)">
          <g transform="matrix(.26458 0 0 .26458 5.9648 -32.806)" stroke-width=".75591" style="shape-inside:url(#rect19885);white-space:pre" aria-label="GOATMIRE">
            <path d="m110.76 292.03v-52.32h-86.72v160h86.72v-103.2l-49.76 27.52v36.8l17.6-9.6v16.16h-22.24v-95.36h22.24v20z" fill="url(#a)" stroke="url(#b)"></path>
            <path d="m120.4 239.71v160h86.72v-160zm54.56 127.68h-22.24v-95.36h22.24z" fill="url(#a)" stroke="url(#b)"></path>
            <path d="m281.08 239.71h-33.92l-30.24 90.4v69.6h32.32v-32l29.76 17.28v14.72h32.32v-69.6zm-30.56 91.52 13.6-40.48 14.88 44.64v12.32z" fill="url(#a)" stroke="url(#b)"></path>
            <path d="m399.12 239.71h-85.76v32.48h26.72v127.52h32.32v-127.52h26.72z" fill="url(#a)" stroke="url(#b)"></path>
            <path d="m465.8 283.55-15.2-43.84h-43.52v160h32.32v-92.16l8.96 26.24zm17.92-43.84-40.16 119.84h34.08l17.44-51.52v91.68h32.32v-160z" fill="url(#a)" stroke="url(#b)"></path>
            <path d="m569.36 239.71h-32.32v160h32.32z" fill="url(#a)" stroke="url(#b)"></path>
            <path d="m611.48 272.03h17.12l-8.64 26.24 29.12 15.36 24-73.92h-93.92v160h32.32v-66.08l29.44 16.32v49.76h32.16v-68.8l-61.6-34.08z" fill="url(#a)" stroke="url(#b)"></path>
            <path d="m736.16 367.39h-22.24v-36.48l34.08-18.72v-36.8l-66.4 36.48v87.84h86.72v-52.32h-32.16zm-54.56-127.68v68.64l32.32-18.4v-17.92h54.4v-32.32z" fill="url(#a)" stroke="url(#b)"></path>
          </g>
        </g>
        <g transform="translate(-5.2 -27.8)">
          <path transform="rotate(7.7833)" d="m109.32 46.65q0 0.15614-0.0173 0.31227 0 0.15614-0.0174 0.29492-0.052 0.08674-0.20818 0.0694-0.13879-0.01735-0.22553-0.01735-0.0867 0-0.15614 0.0347-0.052 0.01735-0.052 0.10409 0 0.10409 0.0174 0.13879-0.13879-0.01735-0.29493-0.0347t-0.27758-0.10409q-0.24288 0.05205-0.46841 0.13879-0.20818 0.08674-0.41636 0.19083-0.20818 0.10409-0.43372 0.19083-0.22553 0.08674-0.46841 0.10409-0.15613 0.17348-0.39901 0.26023-0.24288 0.08674-0.50311 0.15614-0.26023 0.0694-0.48576 0.17349-0.22553 0.08674-0.36432 0.27758-0.38167 0.0347-0.72864 0.19083-0.34697 0.15614-0.69394 0.32962-0.32962 0.17349-0.67659 0.32962-0.34697 0.15614-0.74599 0.20818-0.10409 0.10409-0.26023 0.17348-0.13878 0.06939-0.29492 0.13879-0.15614 0.05204-0.29493 0.12144-0.13878 0.0694-0.22553 0.19083h-0.0347q-0.0694 0-0.10409-0.0347-0.0347-0.0347-0.1041-0.0347-0.052 0-0.0694 0.01735-0.41636 0.19083-0.83273 0.39902t-0.86743 0.39902q-0.88478 0.36432-1.7869 0.71129-0.90212 0.34697-1.7349 0.85008-0.19083 0.69394-0.31227 1.4226-0.12144 0.71129-0.43371 1.3705v0.81538q-0.17349 0.32962-0.22553 0.72864-0.05205 0.39902-0.05205 0.76334 0 0.13879 0 0.26023 0.01735 0.10409 0.01735 0.22553 0.50311-0.13879 0.97152-0.31227 0.48576-0.17348 0.97152-0.31227 0.24288-0.08674 0.48576-0.12144 0.26023-0.0347 0.50311-0.12144 0.19083-0.05204 0.39902-0.13879 0.20818-0.10409 0.39902-0.19083 0.52046-0.22553 1.0583-0.39902 0.55515-0.17349 1.1277-0.26023 0.17348-0.13879 0.38166-0.26023 0.20819-0.12144 0.41637-0.12144 0.15614 0 0.26023 0.05204 0.29492-0.17348 0.69394-0.31227 0.39901-0.13879 0.74599-0.10409 0.0867-0.12144 0.20818-0.15614 0.12144-0.05205 0.26023-0.05205 0.0694 0 0.12144 0.01735 0.0694 0 0.15613 0 0.17349-0.12144 0.38167-0.17349 0.20818-0.05204 0.41636-0.08674 0.20819-0.0347 0.39902-0.08674 0.20818-0.06939 0.36432-0.20818 0.24288 0.01735 0.38167 0.13879 0.15613 0.12144 0.26023 0.29492 0.10409 0.17348 0.17348 0.36432 0.0694 0.19083 0.17349 0.36432-0.0347 0.13879-0.10409 0.22553-0.0694 0.0694-0.15614 0.13879-0.0867 0.06939-0.15614 0.13879-0.0694 0.06939-0.0867 0.19083-0.6072 0-1.1797 0.17348-0.55515 0.17349-1.1103 0.39902-0.5378 0.22553-1.1103 0.43371-0.55516 0.19083-1.145 0.24288-0.0867 0.08674-0.22553 0.13879l-0.26023 0.08674q-0.12144 0.0347-0.22553 0.10409-0.10409 0.05204-0.15613 0.17348-0.10409-0.05205-0.22553-0.05205-0.29492 0-0.5725 0.15614-0.27758 0.15614-0.62455 0.15614-0.13879 0-0.19083-0.01735-0.08674 0.15614-0.26023 0.22553-0.15614 0.0694-0.34697 0.12144-0.17349 0.0347-0.34697 0.10409-0.15614 0.06939-0.24288 0.24288-0.05205-0.01735-0.12144-0.01735h-0.10409q-0.27758 0-0.6419 0.10409-0.34697 0.08674-0.50311 0.34697-0.13879-0.0347-0.24288-0.0347-0.24288 0-0.43371 0.10409-0.19083 0.08674-0.38167 0.20818l-0.38167 0.24288q-0.19083 0.12144-0.41636 0.17349-0.45106 0.6419-0.62455 1.4746-0.17349 0.83273-0.26023 1.7349-0.06939 0.90212-0.13879 1.7869-0.06939 0.90212-0.27758 1.6481 0.10409 0.27758 0.15614 0.58985 0.05204 0.31227 0.08674 0.62455 0.0347 0.32962 0.06939 0.65924 0.0347 0.32962 0.12144 0.62455 0.08674 0.36432 0.32962 0.6419 0.26023 0.29492 0.36432 0.65924 0.08674 0.01735 0.17348 0.01735 0.10409 0.01735 0.20818 0.01735 0.62455 0 1.093-0.24288 0.48576-0.24288 0.90212-0.6072 0.41636-0.34697 0.79803-0.76334 0.39902-0.39902 0.85008-0.71129h0.32962q0.12144-0.13879 0.20818-0.29492 0.08674-0.15614 0.19083-0.29492 0.10409-0.13879 0.24288-0.24288 0.13879-0.08674 0.34697-0.12144 0.05205-0.26023 0.24288-0.39902 0.19083-0.13879 0.26023-0.41636 0.0347 0.01735 0.08674 0.01735 0.13879 0 0.20818-0.06939l0.26023-0.24288q0.13879-0.13879 0.29493-0.27758 0.17348-0.12144 0.32962-0.24288 0.5031-0.36432 1.0062-0.72864 0.50311-0.34697 0.91947-0.81538 0.0174 0.01735 0.052 0.01735 0.13879 0 0.19083-0.08674 0.052-0.08674 0.24288-0.05205 0.1041-0.15614 0.19084-0.31227 0.0867-0.17348 0.13879-0.36432l0.55515 0.06939q0.27758 0.0347 0.55515 0.0347 0.0867 0 0.15614 0 0.0867-0.01735 0.15614-0.05204 0.20818 0.32962 0.22553 0.71129 0.0174 0.36432 0.0867 0.72864-0.0347 0-0.0521 0 0-0.01735-0.0347-0.01735-0.0867 0-0.17349 0.06939-0.0694 0.05204-0.15614 0.13879l-0.17348 0.17348q-0.0867 0.08674-0.15614 0.13879-0.19083 0.12144-0.39901 0.20818-0.20819 0.10409-0.39902 0.20818-0.19083 0.10409-0.36432 0.24288-0.17348 0.15614-0.27758 0.38167-0.34697 0.13879-0.67659 0.29492-0.31227 0.15614-0.6072 0.32962-0.0867 0.08674-0.15613 0.17349-0.0694 0.08674-0.1041 0.19083-0.46841 0.15614-0.78068 0.46841-0.29492 0.31227-0.6072 0.67659l-0.0174-0.01735q-0.12144 0-0.15613 0.08674-0.01735 0.08674-0.19083 0.05204-0.32962 0.36432-0.69394 0.67659-0.36432 0.31227-0.74599 0.62455-0.38167 0.31227-0.76334 0.62455-0.38167 0.32962-0.71129 0.69394-0.22553 0.06939-0.43371 0.17349-0.20818 0.10409-0.31227 0.31227h-0.05205q-0.19083 0-0.27758 0.08674-0.06939 0.08674-0.17348 0.22553-0.36432 0.08674-0.74599 0.19083-0.38167 0.10409-0.78068 0.10409-0.27758 0-0.5378-0.10409-0.24288-0.10409-0.48576-0.27758-0.22553-0.15614-0.43371-0.34697-0.19083-0.19083-0.36432-0.36432-0.10409-0.34697-0.27758-0.72864-0.15614-0.36432-0.15614-0.71129 0-0.08674 0.01735-0.17348 0.0347-0.0694 0.0347-0.13879 0-0.12144-0.08674-0.10409-0.06939 0.01735-0.10409-0.08674-0.0347-0.17348-0.05205-0.39902 0-0.20818-0.0347-0.39902-0.05204-0.39902-0.13879-0.81538-0.06939-0.41636-0.15614-0.81538 0.06939-0.0347 0.10409-0.12144 0.05205-0.06939 0.10409-0.13879-0.05205-0.13879-0.10409-0.31227-0.0347-0.15614-0.0347-0.32962 0-0.34697 0.26023-0.52046 0-0.12144-0.06939-0.19083-0.05205-0.05205-0.05205-0.19083 0-0.0347 0.01735-0.19083 0.0347-0.17348 0.06939-0.38167 0.05205-0.20818 0.08674-0.39902 0.0347-0.19083 0.05205-0.26023 0.12144-0.79803 0.20818-1.5614 0.08674-0.78068 0.24288-1.5614-0.17348-0.13879-0.45106-0.20818-0.26023-0.08674-0.36432-0.22553-0.08674-0.12144-0.13879-0.31227-0.05205-0.19083-0.05205-0.32962 0-0.50311 0.39902-0.79803 0.39902-0.31227 0.85008-0.36432 0.22553-0.6072 0.32962-1.2317 0.12144-0.6419 0.19083-1.2838l0.13879-1.3011q0.06939-0.65924 0.20818-1.3011h-0.10409q-0.34697 0-0.67659 0.10409-0.32962 0.10409-0.67659 0.10409h-0.0347q-0.50311-0.39902-0.50311-0.95417 0-0.32962 0.13879-0.65924 0.27758-0.10409 0.50311-0.27758 0.22553-0.17348 0.45106-0.34697 0.24288-0.19083 0.48576-0.32962 0.24288-0.13879 0.5378-0.17349 0.0347-0.50311 0.17349-1.0062 0.13879-0.50311 0.13879-1.0062 0-0.10409-0.01735-0.20818 0-0.10409-0.0347-0.20818 0.12144-0.15614 0.15614-0.32962 0.05205-0.17348 0.05205-0.36432 0-0.20818-0.0347-0.39902-0.01735-0.20818-0.05205-0.39902 0.15614-0.06939 0.24288-0.13879 0.08674-0.08674 0.15614-0.15614 0.08674-0.06939 0.20818-0.12144 0.12144-0.05204 0.34697-0.05204 0.32962 0 0.55515 0.13879 0.24288 0.12144 0.41636 0.32962 0.17348 0.20818 0.29492 0.46841 0.13879 0.26023 0.26023 0.52046-0.0347 0.19083-0.15614 0.27758-0.12144 0.08674-0.20818 0.22553 0.0347 0.27758 0.06939 0.5378 0.0347 0.24288 0 0.52046 0.17348 0.08674 0.32962 0.08674 0.26023 0 0.46841-0.13879 0.22553-0.13879 0.31227-0.38167h0.0347q0.05205 0 0.08674 0.0347 0.05204 0.0347 0.12144 0.0347h0.0694q0.19083-0.17349 0.41636-0.26023 0.22553-0.10409 0.46841-0.19083 0.24288-0.08674 0.46841-0.17349 0.24288-0.10409 0.45106-0.26023 0.10409 0.05205 0.22553 0.05205 0.17349 0 0.32962-0.06939 0.17349-0.06939 0.32962-0.13879 0.15614-0.08674 0.32963-0.15614 0.19083-0.06939 0.39901-0.06939 0.0347 0 0.0867 0.01735 0.052 0 0.0867 0 0.12144 0 0.13879-0.08674 0.0173-0.08674 0-0.15614 0.55515-0.13879 0.95417-0.31227 0.41636-0.19083 0.91947-0.43371 0-0.01735 0.0347-0.01735 0.052 0 0.0694 0.05205 0.0173 0.05204 0.0173 0.08674 0.10409-0.05204 0.10409-0.17348 0.0173-0.12144 0.0867-0.20818 0.0694 0.01735 0.19084 0.01735 0.15613 0 0.31227-0.06939 0.17349-0.06939 0.32962-0.15614 0.15614-0.08674 0.31228-0.15614 0.15613-0.06939 0.31227-0.06939 0.13879 0 0.22553 0.06939 0.10409-0.05204 0.20818-0.13879 0.10409-0.10409 0.22553-0.10409 0.0347 0 0.0521 0.01735 0.0347 0.01735 0.0694 0.0347 0.0867-0.08674 0.15614-0.13879 0.0867-0.05204 0.0867-0.17348 0.6419-0.06939 1.1797-0.27758 0.55516-0.22553 1.0583-0.58985 0.19084 0.0347 0.36432 0.08674 0.19084 0.0347 0.38167 0.0347 0.10409 0.19083 0.19083 0.38167 0.0868 0.19083 0.38167 0.17348 0.0174 0.08674 0.0174 0.19083 0.0173 0.10409 0.0173 0.19083zm15.163 14.989q0 0.34697-0.12144 0.5725-0.10409 0.20818-0.24288 0.48576-0.0347-0.01735-0.0867-0.01735-0.10409 0-0.19084 0.05205-0.0867 0.05205-0.17348 0.08674-0.50311 0.55515-1.0409 1.0756-0.52046 0.52046-1.1624 0.91947-0.17348 0.10409-0.34697 0.20818-0.17348 0.08674-0.36432 0.17348-0.0694 0.0347-0.13878 0.06939-0.0521 0.01735-0.1041 0.05205-0.12144 0.06939-0.26022 0.22553-0.12144 0.15614-0.29493 0.31227-0.15614 0.17349-0.34697 0.27758-0.19083 0.12144-0.41636 0.10409-0.13879 0.08674-0.24288 0.22553-0.10409 0.13879-0.24288 0.27758-0.74599 0.26023-1.3705 0.67659-0.6072 0.43371-1.2664 0.83273-0.39902 0.26023-0.79803 0.41636-0.0868 0.0347-0.20819 0.05205-0.10409 0.01735-0.22553 0.05204-0.5725 0.13879-1.1624 0.22553-0.5725 0.08674-1.1624 0.08674-0.72864 0-1.4052-0.17348 0-0.05205-0.0347-0.0694t-0.0867-0.0347q-0.0347-0.01735-0.0694-0.05204-0.0347-0.01735 0-0.08674-0.38166-0.0347-0.67659-0.19083-0.27757-0.15614-0.36432-0.55515-0.052 0.0347-0.0867 0.08674t-0.12144 0.0347q-0.24288-0.34697-0.5378-0.6419-0.29493-0.29492-0.43372-0.71129-0.0347-0.08674-0.052-0.17348 0-0.06939-0.0173-0.15614-0.0867-0.24288-0.31228-0.24288 0.0347-0.06939 0.0347-0.17348 0-0.05205-0.0173-0.10409 0-0.05205 0-0.10409 0-0.08674 0.052-0.17349-0.17349-0.39902-0.24288-0.78068-0.0694-0.39902-0.0694-0.81538 0-0.5378 0.052-1.0583 0.0521-0.5378 0.0521-1.0756 0.10409-0.20818 0.15613-0.48576 0.0694-0.27758 0.22553-0.45106-0.0173-0.10409-0.0694-0.10409-0.052-0.01735-0.052-0.10409 0-0.31227 0.12144-0.67659t0.12144-0.74599q0.15614-0.08674 0.19084-0.26023 0.052-0.17348 0.19083-0.27758v-0.08674q0-0.26023 0.052-0.52046 0.0694-0.26023 0.12144-0.50311 0.0694-0.26023 0.10409-0.50311 0.052-0.26023 0.0347-0.50311 0.0347-0.05205 0.0867-0.19083 0.0694-0.15614 0.12144-0.31227 0.0694-0.17348 0.10409-0.32962 0.052-0.15614 0.052-0.22553 0-0.10409-0.0347-0.19083-0.0347-0.08674-0.0347-0.19083 0-0.08674 0.0521-0.24288 0.052-0.17348 0.0867-0.27758 0.0694-0.29493 0.12144-0.6072 0.0694-0.32962 0.12144-0.62455 0.15613-0.95417 0.22553-1.9083 0.0694-0.97152 0.0867-1.943 0.24288-0.13879 0.50311-0.26023 0.27758-0.12144 0.5725-0.12144 0.0867 0 0.17349 0.01735 0.0867 0.01735 0.17348 0.05205 0.0867 0.34697 0.27758 0.55515 0.19083 0.20818 0.34697 0.50311-0.10409 0.27758-0.13879 0.5725l-0.0521 0.58985q-0.0173 0.29492-0.0694 0.58985-0.052 0.27758-0.17348 0.5378 0.0347 0.08674 0.0347 0.17349v0.17348q0 0.24288-0.052 0.46841-0.0347 0.22553-0.0868 0.46841-0.052 0.22553-0.0867 0.45106t0 0.45106q-0.052 0.08674-0.10409 0.13879-0.0521 0.0347-0.0694 0.12144l-0.0347 0.32962q-0.0347 0.19083-0.0694 0.38167-0.0173 0.17348-0.052 0.36432-0.12144 0.52046-0.27758 1.0409-0.13879 0.52046-0.13879 1.0756v0.10409q-0.15613 0.24288-0.24288 0.6072-0.0867 0.34697-0.052 0.6419-0.19083 0.50311-0.36432 0.98887t-0.17349 1.0062q0 0.08674 0 0.19083 0.0173 0.08674 0.0347 0.17348-0.13879 0.34697-0.17348 0.71129-0.0347 0.34697-0.0347 0.69394 0 0.24288 0.0173 0.5725 0.0347 0.31227 0.0867 0.65924 0.0694 0.32962 0.15614 0.6419 0.0867 0.31227 0.19083 0.50311 0.39902 0.6419 1.0409 1.0062 0.65925 0.38167 1.3879 0.48576 0.29492-0.10409 0.46841-0.12144 0.19083-0.01735 0.52045 0 0.27758-0.13879 0.55516-0.20818 0.29492-0.06939 0.58985-0.15614 0.0867-0.0347 0.17348-0.06939 0.10409-0.0347 0.17349-0.0694 0.0867-0.0347 0.19083-0.15614 0.10409-0.12144 0.19083-0.20818 0.36432-0.10409 0.69395-0.29492 0.32962-0.17348 0.64189-0.36432l0.6419-0.38167q0.32962-0.19083 0.69394-0.32962 0.13879-0.05205 0.15614-0.13879 0.0347-0.10409 0.10409-0.24288 0.0347 0.0347 0.10409 0.0347 0.20818 0 0.29492-0.12144 0.0867-0.13879 0.20819-0.27758 0.22553-0.0347 0.43371-0.12144 0.20818-0.10409 0.31227-0.31227 0.43372-0.08674 0.76334-0.31227 0.32962-0.22553 0.64189-0.48576 0.31228-0.27758 0.62455-0.52046 0.31228-0.26023 0.71129-0.41636 0.052-0.08674 0.0694-0.15614 0.0173-0.06939 0.052-0.15614 0.29493-0.0347 0.46841-0.15614 0.17349-0.13879 0.39902-0.27758 0.48576 0.05204 0.69394 0.43371zm3.2789 4.8402q0 0.27758-0.19083 0.50311-0.19084 0.22553-0.32962 0.43371-0.24288 0.12144-0.46842 0.13879-0.20818 0.0347-0.45106 0.12144-0.0867 0.01735-0.19083 0.12144-0.13879-0.01735-0.27758-0.12144-0.13879-0.08674-0.27757-0.12144-0.19084-0.06939-0.50311-0.08674-0.29493-0.01735-0.43371-0.17348-0.24288-0.24288-0.41637-0.6419-0.15614-0.39902-0.26023-0.85008-0.0867-0.45106-0.13879-0.88478-0.0347-0.45106-0.0347-0.78068 0-0.55515 0.0694-1.1103t0.20818-1.0756v-0.08674q0-0.08674 0-0.13879 0.0173-0.06939 0.0173-0.15614 0-0.05204-0.0173-0.10409 0-0.05205-0.052-0.08674 0.22553-0.5725 0.32962-1.1797t0.15614-1.2144q0.0694-0.62455 0.13879-1.2317 0.0867-0.62455 0.24287-1.2144-0.0173-0.10409-0.0347-0.22553 0-0.12144 0-0.22553 0-0.31227 0.0694-0.6072 0.0694-0.31227 0.0867-0.6419 0.0174-0.10409 0-0.20818-0.0173-0.12144-0.0173-0.24288 0-0.76334 0.0694-1.492 0.0867-0.72864 0.0867-1.4746 0-0.39902-0.0694-0.76334-0.052-0.38167-0.052-0.76334 0-0.17349 0.0173-0.32962 0.0173-0.17349 0.10409-0.31227 0.38167-0.26023 0.90213-0.26023 0.38166 0 0.65924 0.20818-0.0173 0.0347-0.0173 0.10409 0 0.08674 0.0173 0.15614 0.0174 0.0694 0.0174 0.15614v0.06939q0 0.0347-0.0174 0.0694 0.12144 0.20818 0.15614 0.45106 0.052 0.22553 0.052 0.46841 0 0.22553-0.0173 0.45106-0.0173 0.20818-0.0173 0.43371 0 0.31227 0 0.62455 0.0173 0.31227 0.0173 0.62455 0 0.83273-0.0867 1.6481-0.0694 0.79803-0.10409 1.6308-0.0347 0.71129-0.19083 1.3879-0.13879 0.67659-0.19084 1.3705-0.0173 0.13879-0.0173 0.27758 0.0173 0.12144 0.0173 0.27758 0 0.39902-0.15613 0.79803-0.15614 0.38167-0.15614 0.81538 0 0.20818 0.0694 0.36432-0.10409 0.27758-0.19083 0.65924-0.0694 0.38167-0.13879 0.79803-0.052 0.39902-0.0867 0.79803-0.0347 0.38167-0.0347 0.67659 0 0.41636 0.10409 0.85008 0.10409 0.41636 0.34697 0.76334 0.17349 0.05205 0.41637 0.05205t0.48576 0.01735 0.45106 0.08674 0.31227 0.27758q0.0173 0.0347 0.0173 0.06939zm15.718-19.986q0 0.13879-0.0347 0.26023-0.0173 0.12144 0.0173 0.26023-0.53781 0.32962-0.74599 0.86743-0.27758 0.13879-0.45106 0.38167-0.17349 0.22553-0.32962 0.48576-0.13879 0.24288-0.29493 0.48576-0.15614 0.22553-0.41636 0.38167v0.38167q-0.13879-0.0347-0.19084 0.0347-0.0347 0.05205-0.12144 0.08674-0.34697 0.65924-0.71129 1.3185-0.36432 0.6419-0.90212 1.1624-0.13879 0.36432-0.31228 0.71129-0.17348 0.34697-0.36432 0.69394-0.19083 0.34697-0.36431 0.69394-0.17349 0.34697-0.27758 0.71129-0.34697 0.36432-0.58985 0.81538-0.24288 0.43371-0.46841 0.88478-0.20818 0.43371-0.46841 0.86743-0.24288 0.43371-0.58985 0.79803 0.0173 0.15614-0.0521 0.26023t-0.0694 0.22553q0 0.39902 0.38167 0.90212 0.39901 0.48576 0.93682 0.98887 0.5378 0.48576 1.0756 0.90212 0.5378 0.41636 0.83273 0.6419 0.45106-0.19083 0.79803-0.38167 0.34697-0.20818 0.83273-0.24288 0.24288 0.12144 0.38167 0.32962 0.15613 0.19083 0.15613 0.45106 0 0.39902-0.24288 0.76334-0.24288 0.34697-0.5378 0.5725-0.27758 0.05205-0.55515 0.08674-0.27758 0.0347-0.55516 0.0347-0.55515 0-1.1103-0.10409-0.53781-0.12144-1.0756-0.26023-0.29493-0.38167-0.65925-0.65924-0.34697-0.29492-0.78068-0.46841-0.0173-0.20818-0.10409-0.34697-0.0867-0.13879-0.20819-0.24288-0.12144-0.12144-0.27757-0.20818-0.13879-0.08674-0.27758-0.19083-0.17348 0.05205-0.31227 0.15614-0.13879 0.08674-0.31228 0.13879-0.32962 0.79803-0.88477 1.492 0.0347 0.31227-0.12144 0.48576-0.13879 0.15614-0.36432 0.32962-0.0867 0.50311-0.32962 0.90212-0.22553 0.39902-0.53781 0.78068l0.0174 0.13879q0 0.06939-0.052 0.12144-0.0347 0.05205-0.0173 0.17349-0.26023 0.31227-0.43372 0.71129-0.17348 0.41636-0.32962 0.85008-0.13879 0.43371-0.27757 0.86743-0.12144 0.43371-0.27758 0.81538 0.0694 0.15614 0.0694 0.36432 0 0.19083-0.0347 0.38167t-0.0347 0.38167q0 0.15614 0.052 0.29492 0.0521 0.15614 0.19084 0.26023 0.20818-0.19083 0.5378-0.19083 0.19084 0 0.36432 0.05205 0.19084 0.05205 0.34697 0.13879 0.0694 0.13879 0.0694 0.24288 0 0.12144-0.0694 0.20818-0.052 0.10409-0.13879 0.19083-0.0694 0.10409-0.15613 0.19083-0.0694 0.08674-0.0867 0.20818-0.39902 0.0347-0.79804 0.12144-0.38167 0.08674-0.74598 0.26023-0.20819-0.0347-0.38167-0.15614-0.15614-0.10409-0.36432-0.15614-0.22553-0.29492-0.34697-0.6419-0.13879-0.34697-0.34697-0.65924 0-0.39902 0.10409-0.79803 0.10409-0.38167 0.22553-0.76334 0.10409-0.38167 0.19083-0.78068 0.0867-0.38167 0.0521-0.78068 0.17348-0.08674 0.27758-0.27758 0.10409-0.17348 0.15613-0.39902 0.0694-0.20818 0.12144-0.43371 0.052-0.20818 0.12144-0.38167 0.19084-0.12144 0.27758-0.31227 0.10409-0.17348 0.29492-0.29492 0-0.0347-0.0173-0.06939 0-0.01735 0-0.05205-0.0173-0.0347-0.0347-0.05205-0.0173 0-0.0173-0.0347t-0.0174-0.06939q0-0.01735 0.0174-0.05205l0.17348-0.22553q0.24288-0.31227 0.36432-0.5725 0.12144-0.27758 0.27758-0.62455 0.13879-0.32962 0.34697-0.65924 0.20818-0.32962 0.39902-0.65924 0.45106-0.81538 0.86742-1.6134 0.43372-0.81538 0.91948-1.6134-0.13879-0.10409-0.22553-0.24288-0.0867-0.15614-0.17349-0.31227-0.0694-0.15614-0.15614-0.29492-0.0867-0.15614-0.24288-0.26023-0.36432-0.95417-0.83273-1.8563-0.45106-0.91947-0.88477-1.8216-0.15614-0.32962-0.29493-0.65924-0.12144-0.34697-0.29492-0.6419-0.052-0.08674-0.12144-0.0694-0.0694 0.01735-0.12144-0.05204-0.13879-0.39902-0.24288-0.71129l-0.26023-0.78068q0.0694-0.34697 0.31228-0.50311 0.24287-0.15614 0.5725-0.15614 0.26022 0 0.55515 0.08674 0.15614 0.17348 0.29493 0.38167 0.13878 0.19083 0.32962 0.36432v0.45106q0.5031 1.0236 0.93682 2.0471 0.45106 1.0236 0.97152 2.0471 0.19083 0.0347 0.31227 0.15614 0.12144 0.12144 0.22553 0.26023t0.20818 0.26023q0.12144 0.12144 0.32963 0.13879 0.0694-0.0347 0.20818-0.22553 0.13879-0.20818 0.27757-0.43371 0.15614-0.24288 0.26023-0.46841 0.12144-0.22553 0.12144-0.31227v-0.12144q1.145-1.5787 1.9257-3.3483 0.17348-0.08674 0.27758-0.22553 0.10409-0.15614 0.19083-0.31227 0.10409-0.17348 0.20818-0.32962t0.26023-0.26023q0.0867-0.36432 0.27758-0.67659 0.19083-0.31227 0.39901-0.62455 0.20819-0.31227 0.39902-0.62455 0.19083-0.32962 0.29492-0.69394 0.19084-0.06939 0.29493-0.22553 0.12144-0.15614 0.20818-0.32962 0.0867-0.17348 0.19084-0.32962 0.10409-0.15614 0.29492-0.22553 0.12144-0.5725 0.46841-0.93682t0.90213-0.55515l0.0694-0.19083q0.0694 0.0347 0.24288 0.06939 0.17348 0.01735 0.32962 0.05205 0.17349 0.01735 0.29493 0.06939 0.13878 0.05205 0.13878 0.15614zm3.9902 19.986q0 0.27758-0.19084 0.50311-0.19083 0.22553-0.32962 0.43371-0.24288 0.12144-0.46841 0.13879-0.20818 0.0347-0.45106 0.12144-0.0867 0.01735-0.19084 0.12144-0.13878-0.01735-0.27757-0.12144-0.13879-0.08674-0.27758-0.12144-0.19083-0.06939-0.50311-0.08674-0.29492-0.01735-0.43371-0.17348-0.24288-0.24288-0.41636-0.6419-0.15614-0.39902-0.26023-0.85008-0.0867-0.45106-0.13879-0.88478-0.0347-0.45106-0.0347-0.78068 0-0.55515 0.0694-1.1103t0.20818-1.0756v-0.08674q0-0.08674 0-0.13879 0.0173-0.06939 0.0173-0.15614 0-0.05204-0.0173-0.10409 0-0.05205-0.0521-0.08674 0.22553-0.5725 0.32963-1.1797 0.10409-0.6072 0.15613-1.2144 0.0694-0.62455 0.13879-1.2317 0.0867-0.62455 0.24288-1.2144-0.0174-0.10409-0.0347-0.22553 0-0.12144 0-0.22553 0-0.31227 0.0694-0.6072 0.0694-0.31227 0.0867-0.6419 0.0173-0.10409 0-0.20818-0.0173-0.12144-0.0173-0.24288 0-0.76334 0.0694-1.492 0.0867-0.72864 0.0867-1.4746 0-0.39902-0.0694-0.76334-0.052-0.38167-0.052-0.76334 0-0.17349 0.0174-0.32962 0.0173-0.17349 0.10409-0.31227 0.38167-0.26023 0.90212-0.26023 0.38167 0 0.65925 0.20818-0.0173 0.0347-0.0173 0.10409 0 0.08674 0.0173 0.15614 0.0173 0.0694 0.0173 0.15614v0.06939q0 0.0347-0.0173 0.0694 0.12144 0.20818 0.15613 0.45106 0.0521 0.22553 0.0521 0.46841 0 0.22553-0.0173 0.45106-0.0174 0.20818-0.0174 0.43371 0 0.31227 0 0.62455 0.0174 0.31227 0.0174 0.62455 0 0.83273-0.0867 1.6481-0.0694 0.79803-0.10409 1.6308-0.0347 0.71129-0.19084 1.3879-0.13879 0.67659-0.19083 1.3705-0.0173 0.13879-0.0173 0.27758 0.0173 0.12144 0.0173 0.27758 0 0.39902-0.15614 0.79803-0.15614 0.38167-0.15614 0.81538 0 0.20818 0.0694 0.36432-0.10409 0.27758-0.19084 0.65924-0.0694 0.38167-0.13878 0.79803-0.0521 0.39902-0.0867 0.79803-0.0347 0.38167-0.0347 0.67659 0 0.41636 0.10409 0.85008 0.10409 0.41636 0.34697 0.76334 0.17348 0.05205 0.41636 0.05205t0.48576 0.01735 0.45106 0.08674q0.20819 0.06939 0.31228 0.27758 0.0173 0.0347 0.0173 0.06939zm22.362-0.15614q-0.0347 0.34697-0.24288 0.5378-0.20818 0.20818-0.53781 0.20818-0.17348 0-0.32962-0.05205-0.32962 0.26023-0.78068 0.39902-0.45106 0.13879-0.85008 0.27758-0.48576-0.01735-0.95417-0.08674t-0.97152-0.0347q-0.17348-0.12144-0.36432-0.15614-0.19083-0.0347-0.39901-0.05205-0.19084 0-0.38167-0.05205-0.17349-0.05204-0.34697-0.24288h-0.86743q-0.12144 0-0.36432-0.12144-0.22553-0.10409-0.52045-0.22553-0.27758-0.12144-0.6072-0.22553-0.31228-0.10409-0.58985-0.10409h-0.10409q-0.19084-0.15614-0.45107-0.24288-0.24287-0.06939-0.5031-0.13879-0.26023-0.05205-0.50311-0.12144t-0.41637-0.19083q-0.17348-0.12144-0.39901-0.24288-0.22553-0.13879-0.46841-0.12144-0.32962-0.19083-0.69394-0.32962-0.34697-0.13879-0.71129-0.26023l-0.71129-0.26023q-0.34697-0.13879-0.69394-0.32962-0.052 0.01735-0.12144 0-0.0694-0.01735-0.12144-0.01735-0.1041 0-0.12144 0.06939-0.12144-0.12144-0.27758-0.20818-0.13879-0.08674-0.27758-0.20818-0.0694 0.01735-0.13879 0.0347-0.052 0-0.13878 0-0.15614 0-0.31228-0.0347-0.13879-0.0347-0.27757-0.08674-0.15614 0.34697-0.20819 0.78068-0.052 0.43371-0.0867 0.90212-0.0173 0.45106-0.052 0.90212-0.0347 0.46841-0.15614 0.85008 0.0347 0.13879 0.0347 0.27758v0.29492q0 0.29492 0.0867 0.46841-0.0347 0.15614-0.0694 0.29492-0.0173 0.13879-0.052 0.27758 0.0173 0.19083 0.0173 0.34697 0.0174 0.17349 0.0174 0.36432v0.29492q0 0.15614-0.0347 0.31227-0.17349 0.10409-0.34697 0.19083-0.17349 0.10409-0.34697 0.24288-0.12144-0.22553-0.41637-0.26023-0.0694 0.08674-0.10409 0.22553-0.0174 0.15614-0.15614 0.15614-0.24288-0.19083-0.39901-0.31227-0.15614-0.12144-0.34697-0.36432-0.0174-0.0347-0.0174-0.08674 0-0.08674 0.0347-0.15614 0.0347-0.06939 0.0347-0.13879 0-0.20818-0.052-0.36432-0.0347-0.22553-0.0521-0.45106-0.0173-0.20818-0.0173-0.41636 0-0.29492 0.0347-0.5725 0.0521-0.27758 0.17349-0.5725 0.19083-0.50311 0.26022-1.0583 0.0868-0.55515 0.12144-1.1103 0.0347-0.5725 0.0347-1.145 0.0173-0.5725 0.0694-1.093-0.12144-0.27758-0.29492-0.43371-0.15614-0.17348-0.15614-0.5378 0-0.32962 0.20819-0.50311 0.20818-0.19083 0.36431-0.45106-0.0347-0.10409 0.0521-0.13879l0.20818-1.8216q0.0173-0.19083 0.0521-0.41636 0.052-0.22553 0.0694-0.41636 0.0347-0.39902 0.0347-0.78068t0.0347-0.76334q0.0867-0.85008 0.15613-1.6828 0.0867-0.83273 0.0867-1.6655 0-0.39902-0.0347-0.79803-0.0173-0.39902-0.10409-0.79803-0.29493 0.08674-0.65925 0.19083-0.34697 0.08674-0.58985 0.31227h-0.13879q-0.45106 0-0.85007 0.17349-0.38167 0.17348-0.78069 0.32962-0.17348 0.08674-0.34697 0.12144-0.17348 0.01735-0.36432 0.06939-0.0867 0.01735-0.19083 0.10409-0.10409 0.08674-0.19084 0.12144l-0.29492 0.06939q-0.12144 0.0347-0.10409 0.15614 0.0173 0.12144-0.0867 0.15614-0.0867 0.08674-0.24288 0.08674-0.15614-0.01735-0.24288-0.01735h-0.0694q-0.43371 0.31227-0.83273 0.5378-0.41636 0.20818-0.97152 0.27758l-0.38166 0.24288q-0.19084 0.12144-0.43372 0.12144-0.19083 0-0.31227-0.06939-0.13879-0.20818-0.20818-0.46841-0.0867-0.26023-0.27758-0.45106 0.0867-0.08674 0.12144-0.22553 0.0173-0.15614 0.0521-0.27758 0.52045-0.22553 1.0583-0.43371 0.52045-0.20818 1.0062-0.50311 0.052 0.01735 0.15614 0.01735 0.17348 0 0.31227-0.05205t0.27758-0.12144 0.27758-0.12144q0.13878-0.05204 0.32962-0.0347 0.58985-0.32962 1.2491-0.5378 0.65925-0.20818 1.2491-0.5725 0.0867 0 0.13879 0.0347 0.0694 0.0347 0.13878 0.0347 0.0694 0 0.0867-0.01735 0.10409-0.13879 0.27757-0.19083 0.17349-0.06939 0.34697-0.10409 0.19084-0.05205 0.34697-0.12144 0.17349-0.08674 0.22554-0.26023 0.0867 0.06939 0.26022 0.06939 0.29493 0 0.53781-0.12144 0.26023-0.12144 0.55515-0.12144 0.0694 0 0.12144 0.0347 0.0694 0.01735 0.13879 0.01735 0.13879 0 0.13879-0.08674 0.0174-0.08674 0.10409-0.15614 1.0583-0.19083 2.0992-0.34697 1.0583-0.15614 2.1339-0.15614 0.15614-0.10409 0.34697-0.10409 0.19084 0 0.34697 0.10409 0.29493-0.0347 0.58985-0.05205 0.29493-0.01735 0.58985-0.01735 0.6766 0 1.3879 0.08674t1.4052 0.27758q0.71129 0.17349 1.3532 0.48576 0.6419 0.29492 1.197 0.71129 0.0521 0.15614 0.13879 0.31227 0.0867 0.15614 0.22553 0.24288 0 0.0347-0.0173 0.05205 0 0.01735 0 0.05204 0 0.19083 0.10409 0.36432 0.12144 0.17348 0.12144 0.41636 0 0.24288-0.0867 0.48576-0.0694 0.24288-0.052 0.48576-0.19084 0.27758-0.32963 0.50311-0.12144 0.22553-0.15613 0.5725-0.31228 0.31227-0.55516 0.69394-0.24288 0.36432-0.5031 0.72864-0.24288 0.34697-0.55516 0.6419-0.31227 0.29493-0.74598 0.46841-0.0173 0.17348-0.13879 0.38167h-0.36432q-0.0521 0.13879-0.17349 0.20818l-0.24288 0.13879q-0.12144 0.0694-0.20818 0.17349-0.0694 0.10409-0.0694 0.29492-0.0347-0.01735-0.0868-0.01735-0.17348 0-0.41636 0.17349-0.22553 0.17348-0.36432 0.27758-0.29493 0.20818-0.6072 0.38167-0.29492 0.17348-0.52046 0.48576-0.69394 0.29492-1.3185 0.69394-0.62455 0.38167-1.3532 0.6072-0.27758 0.29492-0.65924 0.46841-0.36432 0.15614-0.71129 0.34697v0.05205q0 0.13879 0.10409 0.27758 0.10409 0.12144 0.24288 0.22553 0.13879 0.08674 0.29492 0.12144 0.17349 0.0347 0.29493 0 0.34697 0.31227 0.79803 0.50311t0.93682 0.34697q0.48576 0.15614 0.95417 0.32962 0.46841 0.17348 0.86743 0.43371h0.41636q0.39902 0.34697 0.93682 0.52046 0.55516 0.17348 1.0583 0.31227 0.20819 0.19083 0.52046 0.29492 0.32962 0.08674 0.62455 0.08674h0.10409q0.0694 0 0.12144-0.01735v0.0347q0 0.08674 0.052 0.12144 0.0521 0.01735 0.10409 0.0347 0.0694 0 0.12144 0.0347 0.0694 0.01735 0.1041 0.06939h0.17348q0.36432 0 0.69394 0.15614 0.32962 0.13879 0.69394 0.13879 0.15614 0 0.24288-0.0347 0.10409 0.0347 0.15614 0.13879 0.0521 0.10409 0.13879 0.17349 0.12144-0.01735 0.22553-0.0347 0.10409 0 0.20818 0 0.38167 0 0.71129 0.08674t0.67659 0.26023q0.55516-0.24288 1.093-0.39902 0.53781-0.15614 1.145-0.15614v0.0347q0 0.20818 0.10409 0.38167 0.12144 0.19083 0.20818 0.38167zm-6.6445-13.844q0-0.20818-0.0867-0.32962-0.0694-0.12144-0.19084-0.20818-0.12144-0.10409-0.26022-0.19083-0.12144-0.08674-0.22554-0.22553-0.45106-0.13879-0.88477-0.31227-0.43371-0.17349-0.88478-0.29493-0.22553-0.05204-0.45106-0.08674-0.22553-0.05205-0.45106-0.05205h-0.38167q-0.10409-0.01735-0.22553-0.0347-0.10409-0.0347-0.20818-0.05205-0.24288-0.0347-0.48576-0.05204-0.22553-0.01735-0.46841-0.01735-0.45106 0-0.90212 0.0347l-0.90213 0.0694q-0.19083 0.01735-0.39901 0.01735-0.20819 0-0.38167 0.0347-0.0867 0.01735-0.24288 0.08674-0.15614 0.05204-0.32962 0.12144-0.17349 0.05204-0.34697 0.12144-0.15614 0.06939-0.27758 0.10409 0.0173 0.12144 0.0173 0.24288v0.22553q0 0.26023-0.0173 0.52046t-0.0694 0.52046q-0.0173 0.10409-0.052 0.20818-0.0347 0.10409-0.0521 0.20818-0.0347 0.15614-0.0347 0.34697v0.32962q-0.0347 0.39902-0.0521 0.78068-0.0173 0.38167-0.052 0.78068-0.1041 1.145-0.22554 2.2727-0.10409 1.1277-0.13878 2.2727 0.5725-0.12144 1.093-0.36432 0.52046-0.24288 1.0236-0.52046 0.5031-0.29492 1.0062-0.5725 0.50311-0.27758 1.0583-0.46841 0.13879-0.19083 0.32962-0.31227 0.20819-0.12144 0.34698-0.31227 0.17348 0 0.26022-0.0694 0.0867-0.08674 0.22553-0.12144 0.83273-0.72864 1.7002-1.3705 0.86743-0.6419 1.6655-1.3705 0.0694-0.24288 0.22553-0.46841 0.17349-0.24288 0.32962-0.48576 0.15614-0.24288 0.27758-0.48576 0.12144-0.24288 0.12144-0.52046z" fill="#f0f" stroke-width=".44412" aria-label="ELIXIR"></path>
        </g>
      </svg>
</div>
<p>Keep watching, it does animate. All SVG.</p>
<p>Then it was a matter of starting to knock down the immediate needs.</p>
<p>Find sponsors, create a CFP/CFT (Call for Papers/Proposals/Talks) or whatever you call it.</p>
<p>I did a wait-list signup using my newsletter platform Campaign Monitor which I do not recommend. We fairly quickly reached 190-something people. Checking in with the lovely people at CodeSync who run conferences all day every day this actually sounded pretty good.</p>
<h2 id="the-tools">The Tools</h2>
<p>I picked up <a href="https://sessionize.com/">Sessionize</a> for managing speakers because I&rsquo;d used it a bit as part of a Program Committee for Code BEAM in Stockholm in the past. It seemed competent. And it is. It has awkward parts but it is so incredibly useful. Superb software, does the job, has all the features I&rsquo;ve needed and very few I don&rsquo;t. Pricing was manageable.</p>
<p>Actually, Sessionize has guided me towards good thinking in how to handle sessions and speakers. It is very explicit about internal status of a session and what it shows the speaker. It is <strong>very explicit</strong> when you notify speakers about accepting and rejecting talks.</p>
<p>Similarly I picked up <a href="https://ti.to/home">Tito</a> for ticketing, I checked it on recommendation from a friend. Just like Sessionize, it is a sidebar of features and once you dig in it models the domain very clearly. I have a quirk for my event. I have two conferences and you can buy a ticket to both or either. Don&rsquo;t do this. It messes with messaging, design and everything. Just make one conference. Anyway, Tito supports multiple activities with separate limits on tickets sold so that if one runs out the combo ticket would no longer be available.</p>
<p>It integrates with Stripe and does all the stuff you&rsquo;d expect. I haven&rsquo;t messed with the API yet but should be doing that soon enough. I&rsquo;ve definitely messed with the Sessionize API&hellip;</p>
<p>The website is a messy static site generator I built based on <a href="https://fly.io/phoenix-files/crafting-your-own-static-site-generator-using-phoenix/">this post by Fly.io</a> and some violence. My dev server is cruuuude. But building in a language I&rsquo;m very comfortable in was efficient and when it came time to list the speakers I could just pull them from Sessionize, store that JSON data and build the speaker lists from that data. Including pulling images and all that. I will eventually do the same with the agenda when we publish it. The site will be open sourced eventually I think. I want others to be able to yoink the code for this type of thing if they want it.</p>
<p>I went pretty deep. If there is one thing I think I&rsquo;ve done pretty well on this conference effort it is marketing. I&rsquo;m an active poster on <a href="https://bsky.app/profile/lawik.bsky.social">Bluesky</a>, <a href="https://hachyderm.io/@lawik">Mastodon</a> and <a href="https://www.linkedin.com/in/lawik/">LinkedIn</a>. And so my conference stuff has gone out there. And the API data came in handy here. Because I work across a Linux desktop and a Mac laptop day-to-day I&rsquo;ve settled on Inkscape for most of my scalable graphics work. And Inkscape can be automated. In this case I made a template SVG in Inkscape, changed it into an <a href="https://hexdocs.pm/eex/EEx.html">EEx template</a> and then just generated SVGs for every speaker which I then generated PNGs from.</p>
<p>Examples:</p>
<img src="/assets/images/social-goatmire-andrea.png" alt="Picture of Andrea leopardi, lots of neon in the logos, title of the talk is The Umbrella and the Range" />
<img src="/assets/images/social-nervesconf-frank.png" alt="Picutre of Frank Hunleth, a fair bit of neon and a circuit board photo in the background. Talk title is Sound the alarm: Recovering devices at scale" />
<p>Not all talks were ready to be publicized even if accepted. Some speakers had been invited and hadn&rsquo;t sent in their material yet as well. No problem. Sesssionize has custom fields, I added a boolean called Announced which determined if they went on the website and later another called Presentable for whether they were okay for socials. Non-presentable speakers on the website had no page about their talk and title was &ldquo;To be announced&hellip;&rdquo; and that worked great.</p>
<p>Then I oscillated between the alpha of <a href="https://justcrosspost.app/">JustCrossPost</a> and my <a href="https://buffer.com/">Buffer</a> account for posting. But importantly I wrote a specific blurb for each speaker. I try to automate the repetitive work that is not about the content of the conference. I don&rsquo;t automate the communications.</p>
<p>All visual design work is, for good and bad, mine. Most conceptual design work is mine as well. I&rsquo;m at least getting to stretch skills that are not in my day-to-day set. I enjoy it a lot.</p>
<p>We are looking to use Discord for the event app. This is not a massive conference and all conference apps seem quite bad. Whova used to be kind of practical but now is very noisy. Recent Elixir conferences have used Swapcard which seems okay. I am going for low friction with Discord that most in the community already have, it enables continued connections after the event and it is really smooth. Also it is built with Elixir. I&rsquo;ve considered open alternatives but that has at best the same friction as the bad conference apps. Jury is still out on how I&rsquo;ll like this but <a href="https://alchemyconf.com/">AlchemyConf</a> did that and it seemed good. A short informal poll of the opinions also gave a lot of thumbs up for Discord.</p>
<h2 id="the-content">The Content</h2>
<p>We are a single track conference which I&rsquo;ve heard many people note that they are excited about. I think multi-track is just a bit exhausting with all the options and a single track means it is way more likely that people saw the same talk as you which helps conversation. With scale more tracks are essentially necessary I imagine, but we don&rsquo;t have to and our venue wouldn&rsquo;t support it.</p>
<p>Theatre stage invites showmanship and theatrics and while I can&rsquo;t spoil the plans some speakers have, let&rsquo;s just say the combination of venue and my encouragement to swing for the fences has made for some really interesting plans. And because the venue is a theater it is all normal for them and their technicians.</p>
<p>This was the interesting bit of the CFP instructions:</p>
<p>&ldquo;We will not be taking on talks that are &ldquo;Update on project X&rdquo; but are fine with talks that involve new features and either how they were built or what they allowed you to do. Avoid presenting a changelog. Teach something, tell a story, blow some minds or show your work.</p>
<p>The space for ideas is open-ended, the venue provides undivided attention. What do you want to share?&rdquo;</p>
<p>Telling people what a thing is not is often more useful than trying to say what it is. Editing, cutting, constraints do more to actually make the gears turn than lofty possibilities in my experience.</p>
<p>I reached out to José Valim to see if he wanted to show up. If he wanted to speak I would certainly have let him. But I encouraged him to come and only participate. Because I think people would have enjoyed that. Unfortunately the start of the school year in Poland threw a wrench in that plan. I hope to see some more awesome people from the community and ecosystem even if they aren&rsquo;t speaking.</p>
<p>And I suppose that gets to the program committee. On recommendation from my then-colleague Tomie I did what I had considered doing and formed a program committee to help me make decisions. Sophie Debenedetto, Rebecca Le and Cornelia Kelinske helped me a lot in sorting through submissions and making decisions. Unfortunately most can&rsquo;t make it over here for the conference unfortunately. But they were awesome. Things got reviewed, emails flowed on one-off decisions. They pushed back on things I considered doing that conflicted with the bigger ideas. Exactly what I want.</p>
<p>And not entirely unimportant, it gave me some social cover when we had to reject probably 45 talks, many of which I knew the speaker personally. It would have been fine. But now I didn&rsquo;t have to only reference my opinions in feedback but actually could give some wider notes.</p>
<p>As speakers have had questions, suggestions, ideas. I tend to just trust them. I give them the possibilities, I let them know about constraints. But I&rsquo;m willing to take risks with the content. I want them to try stuff you wouldn&rsquo;t see at an event that is more careful, more conventional. Most people that choose to present are creative and I want to see that creativity.</p>
<h2 id="the-talks">The talks</h2>
<p>We&rsquo;ve gotten so many good ones. We have <a href="https://elixirforum.com/t/driving-a-car-powered-with-nerves-and-elixir/71557?u=lawik">the Nerves Car</a>, we have <a href="https://bsky.app/profile/gworkman.bsky.social/post/3lrxydv5wm22w">Nerves hardware development</a>, we have a new perspective on <a href="https://www.goatmire.com/talk/distributed-elixir-in-production-crucial-changes-to-save-the-day">HCA Waterpark</a> one of the <strong>wildest</strong> Elixir systems ever built, we have <a href="https://www.goatmire.com/talk/branching-out-with-ecto-crafting-a-git-adapter-that-nobody-asked-for">ORMs for Git</a> which is delightful nonsense and we have someone giving <a href="https://www.goatmire.com/talk/from-object-oriented-to-functional-thinking-my-elixir-journey">a completely fresh-out-of-bootcamp view of Elixir</a>.</p>
<p>And several I&rsquo;m not talking about because I really shouldn&rsquo;t.</p>
<p>Oh, and this is probably the last stop on Sasa&rsquo;s &ldquo;Tell me a story&rdquo; tour. When a talk is described as a three-act monodrama that is as the kids say <em>a vibe</em>.</p>
<h2 id="the-sponsors">The Sponsors</h2>
<p>I don&rsquo;t think I have a ton of interesting stuff to share here. Sponsorship sales is just sales. You are selling a mix of brand exposure, developer interactions and for the right type of company potential customers. It all hinges on network, trust and your skills at prospecting and reaching out. My sales have all been in the realm of network and trust. Many of the sponsors are doing it because they want the event to exist, I really hope it is also beneficial in the ways it can be for them.</p>
<h2 id="the-locals">The Locals</h2>
<p>The commerce people of the &ldquo;city&rdquo; of Varberg were quite excited when I said I could probably pull 150 developers from outside of Varberg in for a 2-3 day conference. Aside from endorsements for my stipend request with Sparbanksstiftelsen I haven&rsquo;t really been able to get any public sector money, in spite of being a not-for-profit. Which is a bit disappointing. The people I&rsquo;ve been dealing with have been great. They&rsquo;d love to give me a bag of money or cut my venue bill in half but they don&rsquo;t call those shots.</p>
<p>Some students at the local campus are potentially interested in attending. .Net devs the lot of them but I hope some do. And some of the campus alumni that are now professional devs in their early years are certainly coming. I&rsquo;ve reserved some tickets to ensure we get some visitors that are fully local. I want the mixing. I need more Elixir in the groundwater here.</p>
<p>That&rsquo;s a big reason I&rsquo;m doing this. I engage with the local tech scene but most of it is incredibly different from what I do. They operate locally or operate some niche I&rsquo;m not in. Lots of .Net around. But there are plenty of people interested in learning new stuff, in widening horizons and there are people who want more startup spirit in town. And I can bring a certain part of open source, a builder community and plop them down here for a couple of days. I want to connect my local community to my global community. Since I&rsquo;m the loudest Elixir developer in Sweden I&rsquo;d love for Varberg to weirdly end up being the Elixir capital of Sweden.</p>
<p>It should also give me some practice for running other types of events that we want to do with the not-for-profit, Goatmire International.</p>
<h2 id="the-remotes">The Remotes</h2>
<p>It is awesome to see people coming from all over. We have people from Nerves JP, the japanese Nerves community, coming in to speak. We have New Zeeland and Australia. We have the US of course. We have lots of Europe represented.</p>
<p>There is no rhyme or reason to this in terms of practicalities. I suppose people trust me from my long time and various contributions in the community. And the vibe of the conference must have hit a good spot. The ticket price is low but travel and accomodations are a whole thing. So people are definitely putting their money where their mind is and just giving this a shot.</p>
<p>We aren&rsquo;t doing a virtual event, we plan to film everything and release videos for the global community. I don&rsquo;t want an event with some split of attention towards a livestream. I want</p>
<h2 id="the-dread">The Dread</h2>
<p>I hope I don&rsquo;t fuck it up.</p>
<p>That&rsquo;s it. I am not a big worrier thankfully. But this is <em>a lot</em>, the thing feels big to wrangle.</p>
<h2 id="the-advisor">The Advisor</h2>
<p>We started out having our friend and colleague Tomie Lee as our event advisor. They&rsquo;ve done this type of thing for over a decade before becoming a programmer so they were a superb help. Then they got a chance to try a great job and went on leave to do that. That&rsquo;s when we brought in <a href="https://www.linkedin.com/in/helenemattisson/">Helene Mattisson</a>. I know her husband from the community and saw her post about doing events and things. Reached out and she has been sanity-checking us ever since.</p>
<p>Every two weeks we have a meeting and figure out if we are missing anything. She has been immensely valuable, easy to work with and a very good investment for our sanity.</p>
<p>We don&rsquo;t know events, we don&rsquo;t have the learned experience. Getting some of that, borrowing those instincts and mental check-lists. Make sure you have someone that can advise you.</p>
<h2 id="the-partner">The Partner</h2>
<p>My wife, my love, my diligent, awesome, capable partner in life, crime and business. She does the admin, the money-wrangling, the figuring out of taxes and all the nasty bits that must work. She makes phone calls she doesn&rsquo;t want to make just to take them off my plate. She joins me in meetings with the theatre, she helps with all the practical stuff, she gives feedback on visual stuff.</p>
<p>I bring the headache of making and event happen to our doorstep and she just digs in. Alright, she&rsquo;ll let me know I&rsquo;m making our lives harder again. But she is incredibly game to dare things and that hasn&rsquo;t come easy.</p>
<p>Couldn&rsquo;t do it without her.</p>
<h2 id="the-tangibles">The Tangibles</h2>
<p>Physical event. Can&rsquo;t just do virtual stuff.</p>
<p>We <a href="https://oswag.org/">already make shirts</a>. Our Open Swag Platform is the most modest of modest productions. We sell high-quality Elixir and Nerves shirts currently. With more projects coming <em>soon</em>. So we work with a local print shop for those and will use them for the shirts for the event.</p>
<p>The range of cost for a good vs bad shirt is ridiculously wide. You can do DTG (Direct-To-Garment) prints on cheap blanks and get shirts that cost essentially nothing. The print will wash out as tiny rubbery particles at 40C in the machine. I find this unacceptable. I don&rsquo;t want shirts like that. They make me angry.</p>
<p>We also want Fair Trade and ecologically sane stuff.</p>
<p>The shirts we&rsquo;ve made for Underjord have held up for 3-4 years without really deteriorating. The blanks (base shirts) we use cost us around $20. Full color prints that last need to be some kind of transfer so that brings us near $30 for making the shirt if we make ~200 of them. We could compromise on the blanks and still get a &ldquo;really good one&rdquo; that I don&rsquo;t like as much for about $9 per shirt. Nah. The thing I am willing to adapt is the print. You can make a single-color high quality screen print much cheaper than the transfers and since conference shirts are a bulk order we can do that efficiently. This is different from what we need for Oswag. So shirts land around $23 in cost per shirt. And they should last.</p>
<p>If you are unaware of this stuff as I have been you&rsquo;ll pick up a DTG on some &ldquo;recommended&rdquo; blanks at Printful or Spreadshirt and get cheap shirts that die in the wash. Cheapest is to combine a cheap shirt and screen printing in bulk, that&rsquo;s how you get really cheap marketing shirts where the shirt is trash but the print might hold up quite well.</p>
<p>Same print shop will also do badges, some banners and roll-ups for branding around the venue. I have a lot of design to pin down and I need every sponsor logo ready to do so. This is the real hard work of this type of thing. Remembering and making the effort until the things can be shipped.</p>
<p>We also have plans that I hope can happen that I won&rsquo;t get into too much yet. But let&rsquo;s just say NervesConf is an embedded conference and that&rsquo;s a very tangible business.</p>
<h2 id="the-execution">The Execution</h2>
<p>We have more prep to do but we also have a lot of execution and that&rsquo;s the most exciting bit. When the half-crafted, half-cobbled vessel we&rsquo;ve built goes into the hands of attendees and speakers and we just get to watch as the results take shape. While we put out any practical fires in the background. Lots of eyes, lots of beholding, at that point we can just hope for beauty.</p>
<p>I already know the speakers and many of the attendees and I know they&rsquo;ll make an event that is a blast to hang out at. I have no idea how it will feel to be on the organizer side for 3 days straight. I hope all our fun plans come together. We have plans for the social bits that are still in flux, we have plans for bonus events that are not pinned down yet. It is incredibly fun how everyone is so game to collaborate, help and make things happen.</p>
<p>Hopefully this makes some of you consider organizing things. Be mindful of your energy. These things are a lot. We might regret it. We might repeat it. We don&rsquo;t know. I&rsquo;m excited to find out.</p>
<p>You can reach me via electronic mail <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on the fediverse <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. Thanks for reading.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Elixir is not owned by Big Tech</title>
      <link>https://underjord.io/elixir-is-not-owned-by-big-tech.html</link>
      <pubDate>Tue, 29 Apr 2025 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/elixir-is-not-owned-by-big-tech.html</guid>
      <description>We all have varying degrees of exposure to Big Tech. Some of it seems fine, stable and can be relied on. Some of it feels like shifting sand under your feet. React seems to move a lot on whims, I don&amp;rsquo;t envy tracking that. Go seems like it might be fairly stable? With the current geo-political climate I don&amp;rsquo;t find massive corporations to be a guarantee for stability. People may be getting fired even though they chose IBM.</description>
      <content:encoded><![CDATA[ <p>We all have varying degrees of exposure to Big Tech. Some of it seems fine, stable and can be relied on. Some of it feels like shifting sand under your feet. React seems to move a lot on whims, I don&rsquo;t envy tracking that. Go seems like it might be fairly stable? With the current geo-political climate I don&rsquo;t find massive corporations to be a guarantee for stability. People may be getting fired even though they chose IBM. The world is wild right now.</p>
<p>There are programming languages that came up in the increasingly olden times before market penetration required corporate sponsorship. PHP, Python and Ruby all seemed to just take shape in the tech world as some kind of grassroots thing with a wide variety of users as stakeholders. That seems incredibly hard to do now and we have languages like Rust, Go, TypeScript and Swift that are supported by corporate entities until they achieve flight and usually beyond that. Mozilla is not a FAANG level megacorp but they certainly helped Rust take flight and now I hear Rust is beloved in Amazon <em>and</em> Microsoft.</p>
<p>There are still exceptions that come up. I think Zig is mostly community-driven and seems to have some traction. Not Rust levels of traction but definitely relevance. And my ecosystem of preference, Elixir, seemed to boost out of the Ruby world when Ruby was somewhat losing steam but still had a lot of momentum built up. It built on the power and reputation of Erlang combined with the niceties of Ruby and found some traction.</p>
<p>Elixir is controlled by the Elixir core team. If there is a corporate entity behind the core team it is Dashbit that employs a few of them and contributes massively to the wider Elixir ecosystem with Nx, Livebook and tons of various libraries. Dashbit is a small consultancy shop. They essentially offer high-profile Elixir developers as a subscription service. PR review, big picture assistance, emergency help. And their commercial offerings fund significant developer time for the Elixir ecosystem.</p>
<p>Originally Elixir was developed at a company, Plataformatec. Hardly a massive corporation and it was acqui-hired by Nubank in a deal which left José Valim with the rights to Elixir. So we essentially have a BDFL model (benevolent dictator for life) which seems to serve the language well.</p>
<p>There is a wide variety of companies using Elixir but none of them have massive input or hold particularly much sway over the development of the language. Most of the development of the ecosystem happen thanks to the efforts of lots of volunteers. Some are supported, funded or helped by the companies they work at. Some are not. Some run their own business and make time for community work (hello!).</p>
<p>The biggest single corporation that has a massive impact on Elixir is Ericsson. Through Erlang/OTP. They fund the development of the foundations of the ecosystem of the BEAM virtual machine and all related parts. They get contributions from other large users, such as Meta. I do not like Meta as a company but the WhatsApp part of Meta that I&rsquo;ve met, the people who work on the Erlang parts, have been thoroughly decent individuals and they clearly contribute back. If Ericsson stopped development of Erlang it would be a problem for Elixir. Thankfully the telecom giant actively uses Erlang and continues to fund and maintain the team.</p>
<p>There is also the Erlang Ecosystem Foundation that operates a kind of unifying entity for BEAM languages and related efforts. It provides a way to perform common work across Erlang, Elixir and Gleam for example where there might not have been an obvious way to handle funding previously. It seems to work okay, it is not particularly loud or in-your-face day-to-day as a user of the ecosystem. Right now there is a lot of focus on the increasing compliance burden for languages to stay relevant. So SBOM, the recent EU CRA, similar demands in the US.</p>
<p>Beyond the core language there are a bundle of companies that have contributed or are currently contributing significantly to Elixir as an ecosystem. I spend a lot of time in the Nerves/IoT parts of the ecosystem and SmartRent who employ the creator of Nerves and use Nerves heavily in products also contribute a lot back into the ecosystem. Tons of libraries as well as development on NervesHub and experiences from their production environments continuously improving Nerves itself.</p>
<p>For many years DockYard employed Chris McCord to work on Phoenix, that also lead to LiveView. They also develop various other projects for the community. These days Chris works at Fly who similarly fund a lot of his work on Phoenix and LiveView.</p>
<p>Membrane the media framework is entirely developed by Software Mansion, a consultancy in Poland who specialize in streaming media. Ash Framework is largely supported and funded by Alembic, a consultancy based out of Australia. You&rsquo;ll find the name of some consultancy or other small company on a lot of important libraries in the Elixir space as they often pick up the maintenance of things they depend on.</p>
<p>This state of affairs is not a rocketship recipe. It is a steady-going concern and an awesome ecosystem to live in where you don&rsquo;t have to worry about anything very drastic happening because Elixir Megacorp failed to hit Q3 targets. If you are looking for an ecosystem which has limited exposure to megacorps but is still production-grade, productive and satisfying I think you might like Elixir.</p>
<p>The model certainly has challenges, especially around decision-making and concerted efforts. I think it is preferable to a lot of other variants. And I&rsquo;m not sure you can do the same thing today. It seems incredibly hard to establish a new language and get relevant traction. Gleam seems to be on the trajectory which is awesome. I hope we see more as well. I don&rsquo;t think Rust is the most effective way to solve all problems and I definitely don&rsquo;t want a future entirely built in TypeScript.</p>
<p>Are there languages I&rsquo;m overlooking that are also recent and have had success? Are you comfortable with the megacorp languages? I&rsquo;d be curious to hear your thoughts. You can reach me in the fediverse as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> or email <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>The Elixir Shirt</title>
      <link>https://underjord.io/the-elixir-shirt.html</link>
      <pubDate>Fri, 13 Dec 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/the-elixir-shirt.html</guid>
      <description>I wanted there to be an Elixir shirt you could buy. I talked to some people. Made some calculations. Took some debatable photos. And now you can pre-order The Elixir Shirt.
The Officialness This is an officially approved use of the Elixir logo trademark, by explicit permission from the Elixir core team. Usually the trademark can&amp;rsquo;t be used commercially. I was granted permission to make and sell products with the brand.</description>
      <content:encoded><![CDATA[ <p>I wanted there to be an Elixir shirt you could buy. I talked to some people. Made some calculations. Took some debatable photos. And now you can <a href="https://oswag.org/">pre-order The Elixir Shirt</a>.</p>
<img class="blog-image" src="https://payhip.com/cdn-cgi/image/format=auto/https://pe56d.s3.amazonaws.com/o_1ietj0a679ff1c111qtd1fgv7s4n.jpg" alt="Picture of Nerves and Elixir shirts on a table with some junk" />
<h2 id="the-officialness">The Officialness</h2>
<p>This is an officially approved use of the Elixir logo trademark, by explicit permission from the Elixir core team. Usually the trademark can&rsquo;t be used commercially. I was granted permission to make and sell products with the brand. We will make a small margin which if we&rsquo;re lucky compensates my wife for the time spent packing and shipping. There is no software developer salary margins possible here. I did offer to kick back profits to the project but it did not seem worth anyone&rsquo;s time in terms of book-keeping.</p>
<h2 id="the-shirt">The Shirt</h2>
<p>We don&rsquo;t want to do quick and dirty when putting things in the world. We&rsquo;ve tried a bit of print-on-demand and it is awful quality. Always. So this is transfer print which we&rsquo;ve tried for our company shirts before and which has held up very well for more than 3 years now. On the same shirts that we know and trust. Selected with the same principles we use for ourselves. Organic, Fair Trade and long-lasting.</p>
<p>When Frank had tried his Nerves shirt for the first time during Code BEAM this year he literally told me, as if surprised, &ldquo;this is a very high quality shirt!&rdquo;. I could not have been more pleased.</p>
<h2 id="the-path">The Path</h2>
<p>I&rsquo;ve had lots of fun with this. To a large degree because it is a big mess of people. Frank and Nerves was on board early which is why we made some shirts up front there and we can already sell the Nerves shirt, also we know it won&rsquo;t move any major volume (yet?). José (Valim, creator of Elixir) was very open-minded about the whole deal and checked in with the team when I had a more complete plan and had verified I was happy with how the Nerves shirt came out.</p>
<p>Then there was the local print-shop that I already knew did good work and was easy to talk to. We sat down with them and talked through the possibilities, any advantages of scale and larger orders, where the costs are. They are lovely to collaborate with and I love working with someone local. A mix of emails, handshakes, phone-calls and meetings. Human-scale.</p>
<p>Most importantly I&rsquo;ve worked on it with my practical-minded wife who checks my sanity-levels as we go but also trusts my gut a bit, leans in and helps me make a wild idea happen. And she takes on a bigger role in this working out than I do. She is the logistics department, I&rsquo;m just the backup help if things take off. My main role is as the marketing department I suppose.</p>
<p>Putting physical things in the real world is kind of fantastic so I&rsquo;m very excited to try and do more of it. And we&rsquo;ve tried to do it in a way that we can stand behind fully. The process is hyper-local, small-scale and very personal. We have made choices that make for a more costly but less wasteful item.</p>
<h2 id="potential-futures">Potential futures?</h2>
<p>If this works out well we have many more paths, plans and options. But really. I expect this to be quite low-volume. A routine trickle if anything with occasional bumps for conference season when alchemists want to dress up perhaps?</p>
<p>Mostly I&rsquo;m happy if people get shirts for the projects I enjoy so much and are happy with them.</p>
<p>Let me know how you like this little venture. I&rsquo;m at <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> and <a href="mailto:lars@underjord.io">lars@underjord.io</a>, as per usual.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>From big Kubers down to small GenServers</title>
      <link>https://underjord.io/from-big-kubers-down-to-small-genservers.html</link>
      <pubDate>Fri, 06 Dec 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/from-big-kubers-down-to-small-genservers.html</guid>
      <description>Me and the team had a meeting which we dedicated to talking about architecture. Sort of an ad-hoc exploration of patterns and concepts. How systems usually fit together. Which is mostly through various types of queues. And a bunch of common patterns like request/response, publish/subscribe and workers on a queue. And most request/response is essentially workers on a queue going in two directions.
This post was originally written as part of my newsletter which is a weekly thing that I&amp;rsquo;ve done for probably 5 years now.</description>
      <content:encoded><![CDATA[ <p>Me and the team had a meeting which we dedicated to talking about architecture. Sort of an ad-hoc exploration of patterns and concepts. How systems usually fit together. Which is mostly through <a href="https://www.youtube.com/watch?v=mQpPJR-NI9M">various types of queues</a>. And a bunch of common patterns like request/response, publish/subscribe and workers on a queue. And most request/response is essentially workers on a queue going in two directions.</p>
<p><em>This post was originally written as part of</em> <a href="/newsletter.html">my newsletter</a> <em>which is a weekly thing that I&rsquo;ve done for probably 5 years now. Reflections on tech, business, life and a bunch of Elixir. Easy way to keep up with my various .. things, as well.</em></p>
<p>These patterns are nearly fractal in that looking closer you see the pattern again further down. I think the queues kind of stop showing up when you go lower than the stack in memory but I don&rsquo;t know enough about processors to be sure what happens down there. Maybe the electrons are really just lining up but I kind of doubt it.</p>
<p>I think this is why people confuse Kubernetes and Erlang for example. They operate and orchestrate systems made up of these patterns. But Kubers does it at the level of machines and containers (mostly, maybe call it fleet level) and Erlang does it at a level just slightly outside of the application level (system level? System is very overloaded as a term. I should consult the OSI model, but I won&rsquo;t.). They share strategies, and some architectural shapes but the zoom dial is set to very different levels. Kubes is zoomed out a fair bit. Erlang is closer to the action.</p>
<p>And people correlate microservices and GenServers which is kind of accurate but not 1:1 either. A microservice can be a worker, a broker, or a server doing request/response. A GenServer can also be all those things. They can even be at the same level of abstraction, doing the same workload and then they are at the same level on the zoom dial. But of course making very different trade-offs in terms of overhead and isolation.</p>
<p>If you get familiar with fundamental patterns you will see them everywhere. And that becomes a way to grasp the architecture of things, whether internal to an application, parts of a larger system or just the pseudo-architecture sketched out on a whiteboard for how something needs to fit together.</p>
<p>This has a similar mouthfeel to me as some other unavoidable computer things do. There are no continuous streams in the digital realm, just bigger and smaller batch sizes and the associated latency/efficiency trade-offs. There is no difference between something being slow to respond or gone and in the end we just make a decision about the appropriate timeout. Just weird stuff where I want to go &ldquo;but surely that can&rsquo;t be it&rdquo;.</p>
<p>Computers are kind of silly that way. Thankfully the can be very silly, very fast so it works out.</p>
<p>Did I miss a fundamental idea or pattern that you care about? Let me know on the fediverse via <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> (or Bluesky) or via good ol email at <a href="mailto:lars@underjord.io">lars@underjord.io</a>. Thanks for reading.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Voice Activity Detection in Elixir and Membrane</title>
      <link>https://underjord.io/voice-activity-detection-elixir-membrane.html</link>
      <pubDate>Wed, 27 Nov 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/voice-activity-detection-elixir-membrane.html</guid>
      <description>I hacked on something quite useful in the last few weeks, off and on. Voice Activity Detection in Elixir with Silero VAD through ONNX. I&amp;rsquo;ll show what I did and try to give an idea of what it is and why it is useful.
It boiled down to this gist as a proof of concept. Should work on most Elixir installs. These days Membrane will even try to pull pre-compiled dependencies for the libraries it wants.</description>
      <content:encoded><![CDATA[ <p>I hacked on something quite useful in the last few weeks, off and on. Voice Activity Detection in Elixir with Silero VAD through ONNX. I&rsquo;ll show what I did and try to give an idea of what it is and why it is useful.</p>
<p>It boiled down to <a href="https://gist.github.com/lawik/df61c7d37939df1258a67fa4b7573a49">this gist</a> as a proof of concept. Should work on most Elixir installs. These days Membrane will even try to pull pre-compiled dependencies for the libraries it wants. This was pleasant news as it can otherwise be a hassle to pull the right shared libraries for media processing. I had completely missed that this was added. Update: Added the URL to <a href="https://raw.githubusercontent.com/snakers4/silero-vad/v4.0stable/files/silero_vad.onnx">the correct older version</a> of the model. You can also <a href="https://elixirforum.com/t/blog-post-voice-activity-detection-in-elixir-with-membrane/67709/2">fix it</a> and use the newer version.</p>
<p>Using Silero VAD from Elixir is not something I discovered or was first to write about. I leaned on <a href="https://dockyard.com/blog/2024/03/19/elixir-machine-learning-pre-trained-onnx-models-with-ortex">this post</a> by Sean Moriarty for DockYard. Finding the right version of Silero VAD to match Ortex was a bit of a hassle because the model has changed significantly and is out of step with Ortex, the Elixir library based on the Rust library Ort which provides and ONNX runtime. Got some good help in the Erlang Ecosystem Foundation&rsquo;s Slack, the #machine-learning channel is where that stuff happens. Shout-out and thanks to Travis Morton and Andrés Alejos.</p>
<p>I&rsquo;ve <a href="https://underjord.io/membrane-media-processing-and-liveview.html">covered Membrane before</a> and I&rsquo;ve <a href="https://www.youtube.com/watch?v=K51fj1JGQEY">used it in talks</a>. For that talk I did voice controlled slides and a bunch of fun little tricks where Membrane ran the pipeline that fed my voice into the Whisper model. A VAD model would actually have been great there.</p>
<p>Voice Activity Detection or VAD, is the weird art of determining if audio signal is voice or not. A hotdog or not for the human voice. And this VAD model is a lot lighter to run than Whisper, it is smaller and faster. And it tends to do a good job rejecting keystrokes and various other non-voice noises. And by running this in Elixir it becomes very doable to get near-realtime messages to your UI about whether there is voice activity. More importantly we only need to send the chunks that seem voice-like on for processing or only transmit those to a listener on the other side. No data is the best compression.</p>
<p>Time to unroll that gist. The important dependencies are, annotated below with comments:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span>install([
  <span style="color:#75715e"># ONNX Runtime bindings to Rust library, adapted for Elixir</span>
  {<span style="color:#e6db74">:ortex</span>, <span style="color:#e6db74">&#34;== 0.1.9&#34;</span>},
  <span style="color:#75715e"># Numerical Elixir  (Nx) used for dealing with ML data types</span>
  {<span style="color:#e6db74">:nx</span>, <span style="color:#e6db74">&#34;== 0.7.0&#34;</span>},
  <span style="color:#75715e"># Membrane itself</span>
  {<span style="color:#e6db74">:membrane_core</span>, <span style="color:#e6db74">&#34;~&gt; 1.0&#34;</span>},
  <span style="color:#75715e"># Membrane element for the cross-platform audio device library</span>
  <span style="color:#75715e"># portaudio, used for opening the microphone</span>
  {<span style="color:#e6db74">:membrane_portaudio_plugin</span>, <span style="color:#e6db74">&#34;~&gt; 0.19.2&#34;</span>},
  <span style="color:#75715e"># Membrane element for resampling audio using ffmpeg</span>
  {<span style="color:#e6db74">:membrane_ffmpeg_swresample_plugin</span>, <span style="color:#e6db74">&#34;~&gt; 0.20.2&#34;</span>},
  <span style="color:#75715e"># Membrane element for the LAME MP3 encoder, to produce an</span>
  <span style="color:#75715e"># output .mp3 file.</span>
  {<span style="color:#e6db74">:membrane_mp3_lame_plugin</span>, <span style="color:#e6db74">&#34;~&gt; 0.18.2&#34;</span>}
  <span style="color:#75715e"># Used to write a file as the final output</span>
  {<span style="color:#e6db74">:membrane_file_plugin</span>, <span style="color:#e6db74">&#34;~&gt; 0.17.0&#34;</span>},
])</code></pre></div>
  </div>

<p>Next we can look at the Membrane pipeline. A Membrane pipeline is mostly <a href="https://hexdocs.pm/elixir/GenServer.html">a GenServer</a> talking to a pile of element GenServers. There might be a Supervisor in there somewhere, I haven&rsquo;t really checked. I will annotate the pipeline with comments for what I think is worth pointing out:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Membrane.Demo.SimplePipeline</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">Membrane.Pipeline</span>
  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_init(_ctx, _) <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># This is a simplistic pipeline, each element only feeds into the</span>
    <span style="color:#75715e"># next one and it doesn&#39;t need anything else</span>
    spec <span style="color:#f92672">=</span>
      <span style="color:#75715e"># We configure the PortAudio source to open the microphone</span>
      <span style="color:#75715e"># at the specs that Silero VAD actually wants</span>
      <span style="color:#75715e"># signed 16-bit samples at 16000 Hz</span>
      <span style="color:#75715e"># the buffer size is specified in frames</span>
      <span style="color:#75715e"># a frame is according to the docs:</span>
      <span style="color:#75715e"># &#34;For a stereo stream, a frame is two samples.&#34;</span>
      <span style="color:#75715e"># This is a mono stream.</span>
      <span style="color:#75715e"># So we have 16000 / 1000 = 16 frames per ms</span>
      <span style="color:#75715e"># If I want 100ms chunks to process a buffer size of</span>
      <span style="color:#75715e"># 1600 frames will do.</span>
      <span style="color:#75715e"># PortAudio produces RawAudio (PCM)</span>
      child(<span style="color:#e6db74">:source</span>, %<span style="color:#a6e22e">Membrane.PortAudio.Source</span>{
        <span style="color:#e6db74">channels</span>: <span style="color:#ae81ff">1</span>,
        <span style="color:#e6db74">sample_format</span>: <span style="color:#e6db74">:s16le</span>,
        <span style="color:#e6db74">sample_rate</span>: <span style="color:#ae81ff">16000</span>,
        <span style="color:#e6db74">portaudio_buffer_size</span>: <span style="color:#ae81ff">1600</span>
      })
      <span style="color:#75715e"># My custom VAD element, will cover that later</span>
      <span style="color:#f92672">|&gt;</span> child(<span style="color:#e6db74">:vad</span>, <span style="color:#a6e22e">VAD</span>)
      <span style="color:#75715e"># As the audio gets passed through we upsample it to suit</span>
      <span style="color:#75715e"># conversion to MP3</span>
      <span style="color:#f92672">|&gt;</span> child(<span style="color:#e6db74">:converter</span>, %<span style="color:#a6e22e">Membrane.FFmpeg.SWResample.Converter</span>{
        <span style="color:#e6db74">output_stream_format</span>: %<span style="color:#a6e22e">Membrane.RawAudio</span>{
          <span style="color:#e6db74">sample_format</span>: <span style="color:#e6db74">:s32le</span>,
          <span style="color:#e6db74">sample_rate</span>: <span style="color:#ae81ff">44_100</span>,
          <span style="color:#e6db74">channels</span>: <span style="color:#ae81ff">2</span>
        }
      })
      <span style="color:#75715e"># Change the raw audio into an MP3</span>
      <span style="color:#f92672">|&gt;</span> child(<span style="color:#e6db74">:encoder</span>, <span style="color:#a6e22e">Membrane.MP3.Lame.Encoder</span>)
      <span style="color:#75715e"># Write it to a file on disk</span>
      <span style="color:#f92672">|&gt;</span> child(<span style="color:#e6db74">:file</span>, %<span style="color:#a6e22e">Membrane.File.Sink</span>{<span style="color:#e6db74">location</span>: <span style="color:#e6db74">&#34;local.mp3&#34;</span>})

    {[<span style="color:#e6db74">spec</span>: spec], %{}}
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>Let&rsquo;s look at the element. I over-complicated this because I hadn&rsquo;t tuned the PortAudio config when I built it. So I needed to capture the samples up until my desired chunk-size and only then process them. This could be simplified but I know this runs and does the job so you get what you get:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">VAD</span> <span style="color:#66d9ef">do</span>
  <span style="color:#75715e"># This is a Filter, meaning it takes input AND produces output</span>
  <span style="color:#75715e"># otherwise it would be a Source (only output) or Sink (only input)</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">Membrane.Filter</span>

  <span style="color:#75715e"># An input pad designs what the expected input is</span>
  <span style="color:#75715e"># technically Membrane can be used to sling any data</span>
  <span style="color:#75715e"># and I&#39;d be curious to do weirder things with that</span>
  <span style="color:#75715e"># but now we stick to raw audio in buffers</span>
  def_input_pad <span style="color:#e6db74">:input</span>,
    <span style="color:#e6db74">availability</span>: <span style="color:#e6db74">:always</span>,
    <span style="color:#e6db74">flow_control</span>: <span style="color:#e6db74">:manual</span>,
    <span style="color:#e6db74">demand_unit</span>: <span style="color:#e6db74">:buffers</span>,
    <span style="color:#e6db74">accepted_format</span>: <span style="color:#a6e22e">Membrane.RawAudio</span>

  <span style="color:#75715e"># This element passes on RawAudio after processing</span>
  def_output_pad <span style="color:#e6db74">:output</span>,
    <span style="color:#e6db74">availability</span>: <span style="color:#e6db74">:always</span>,
    <span style="color:#e6db74">flow_control</span>: <span style="color:#e6db74">:manual</span>,
    <span style="color:#e6db74">accepted_format</span>: <span style="color:#a6e22e">Membrane.RawAudio</span>

  <span style="color:#75715e"># As we start the pipeline it will initialize the element</span>
  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_init(_ctx, _mod) <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># Load our model from file</span>
    model <span style="color:#f92672">=</span> <span style="color:#a6e22e">Ortex</span><span style="color:#f92672">.</span>load(<span style="color:#e6db74">&#34;./silero_vad_likely.onnx&#34;</span>)

    min_ms <span style="color:#f92672">=</span> <span style="color:#ae81ff">100</span>

    sample_rate_hz <span style="color:#f92672">=</span> <span style="color:#ae81ff">16000</span>
    sr <span style="color:#f92672">=</span> <span style="color:#a6e22e">Nx</span><span style="color:#f92672">.</span>tensor(sample_rate_hz, <span style="color:#e6db74">type</span>: <span style="color:#e6db74">:s64</span>)
    n_samples <span style="color:#f92672">=</span> min_ms <span style="color:#f92672">*</span> (sample_rate_hz <span style="color:#f92672">/</span> <span style="color:#ae81ff">1000</span>)
    bytes_per_chunk <span style="color:#f92672">=</span> n_samples <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span>

    <span style="color:#75715e"># Set up the initial state, we will reduce over this as data comes in</span>
    init_state <span style="color:#f92672">=</span> %{<span style="color:#e6db74">h</span>: <span style="color:#a6e22e">Nx</span><span style="color:#f92672">.</span>broadcast(<span style="color:#ae81ff">0.0</span>, {<span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">64</span>}), <span style="color:#e6db74">c</span>: <span style="color:#a6e22e">Nx</span><span style="color:#f92672">.</span>broadcast(<span style="color:#ae81ff">0.0</span>, {<span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">64</span>}), <span style="color:#e6db74">n</span>: <span style="color:#ae81ff">0</span>, <span style="color:#e6db74">sr</span>: sr}
    state <span style="color:#f92672">=</span> %{<span style="color:#e6db74">run_state</span>: init_state, <span style="color:#e6db74">model</span>: model, <span style="color:#e6db74">bytes</span>: bytes_per_chunk, <span style="color:#e6db74">buffered</span>: []}
    {[], state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_playing(_ctx, state) <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># When the pipeline is active, &#34;playing&#34;</span>
    <span style="color:#75715e"># we trigger demand which will tell the preceding</span>
    <span style="color:#75715e"># element that we want input</span>
    {[<span style="color:#e6db74">demand</span>: {<span style="color:#e6db74">:input</span>, <span style="color:#ae81ff">1</span>}], state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_demand(<span style="color:#e6db74">:output</span>, size, <span style="color:#e6db74">:buffers</span>, _ctx, state) <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># If later elements in the pipeline make demands of us</span>
    <span style="color:#75715e"># we make demands as well. The samples must flow.</span>
    {[<span style="color:#e6db74">demand</span>: {<span style="color:#e6db74">:input</span>, size}], state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#75715e"># This does most of the real work</span>
  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_buffer(<span style="color:#e6db74">:input</span>, %<span style="color:#a6e22e">Membrane.Buffer</span>{<span style="color:#e6db74">payload</span>: data} <span style="color:#f92672">=</span> buffer, _context, state) <span style="color:#66d9ef">do</span>
    %{<span style="color:#e6db74">n</span>: n, <span style="color:#e6db74">sr</span>: sr, <span style="color:#e6db74">c</span>: c, <span style="color:#e6db74">h</span>: h} <span style="color:#f92672">=</span> state<span style="color:#f92672">.</span>run_state
    buffered <span style="color:#f92672">=</span> [state<span style="color:#f92672">.</span>buffered, data]
    <span style="color:#75715e"># This is the now-unnecessary insurance that we&#39;ve built up</span>
    <span style="color:#75715e"># enough buffer to do meaningful processing</span>
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>iodata_length(buffered) <span style="color:#f92672">&gt;=</span> state<span style="color:#f92672">.</span>bytes <span style="color:#66d9ef">do</span>
      data <span style="color:#f92672">=</span> <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>iodata_to_binary(buffered)
      <span style="color:#75715e"># Turn the data into a shape Silero VAD expects</span>
      input <span style="color:#f92672">=</span> data
        <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Nx</span><span style="color:#f92672">.</span>from_binary(<span style="color:#e6db74">:s16</span>)
        <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Nx</span><span style="color:#f92672">.</span>as_type(<span style="color:#e6db74">:f32</span>)
        <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">List</span><span style="color:#f92672">.</span>wrap()
        <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Nx</span><span style="color:#f92672">.</span>stack()

      <span style="color:#75715e"># Run the model on the input data and get updated data</span>
      {output, hn, cn} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Ortex</span><span style="color:#f92672">.</span>run(state<span style="color:#f92672">.</span>model, {input, sr, h, c})
      <span style="color:#75715e"># Turn the output into a probability between 0.0 and 1.0</span>
      prob <span style="color:#f92672">=</span> output <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Nx</span><span style="color:#f92672">.</span>squeeze() <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Nx</span><span style="color:#f92672">.</span>to_number()

      <span style="color:#75715e"># Our only output right now</span>
      <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>puts(<span style="color:#e6db74">&#34;Chunk </span><span style="color:#960050;background-color:#1e0010">#</span><span style="color:#e6db74">#{</span>n<span style="color:#e6db74">}</span><span style="color:#e6db74">: </span><span style="color:#e6db74">#{</span><span style="color:#a6e22e">Float</span><span style="color:#f92672">.</span>round(prob,<span style="color:#ae81ff">3</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
      run_state <span style="color:#f92672">=</span> %{<span style="color:#e6db74">c</span>: cn, <span style="color:#e6db74">h</span>: hn, <span style="color:#e6db74">n</span>: n <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, <span style="color:#e6db74">sr</span>: sr}
      state <span style="color:#f92672">=</span> %{state <span style="color:#f92672">|</span> <span style="color:#e6db74">run_state</span>: run_state, <span style="color:#e6db74">buffered</span>: []}

      <span style="color:#75715e"># This 0.9 is not the right threshold, I had some off calculations</span>
      <span style="color:#75715e"># that made the probabilities start around 0.5 but fixing that</span>
      <span style="color:#75715e"># made it very consistent, I could probably threshold around 0.2</span>
      <span style="color:#66d9ef">if</span> prob <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0.9</span> <span style="color:#66d9ef">do</span>
        <span style="color:#75715e"># This would be where I either trigger a Membrane notification</span>
        <span style="color:#75715e"># or use some other Erlang messaging mechanism to notify whoever</span>
        <span style="color:#75715e"># cares about voice activity that it is happening</span>
        <span style="color:#75715e"># We also pass the buffer forward.</span>
        {[<span style="color:#e6db74">demand</span>: {<span style="color:#e6db74">:input</span>, <span style="color:#ae81ff">1</span>}, <span style="color:#e6db74">buffer</span>: {<span style="color:#e6db74">:output</span>, buffer}], state}
      <span style="color:#66d9ef">else</span>
        buffer_size <span style="color:#f92672">=</span> byte_size(buffer<span style="color:#f92672">.</span>payload) <span style="color:#f92672">*</span> <span style="color:#ae81ff">8</span>
        <span style="color:#75715e"># Two fun options:</span>
        <span style="color:#75715e"># 1. pass nothing forward, this makes for an MP3 with no gaps between speech</span>
        <span style="color:#75715e"># 2. pass silence forward, this would make for clean silences between speech</span>
        {[<span style="color:#e6db74">demand</span>: {<span style="color:#e6db74">:input</span>, <span style="color:#ae81ff">1</span>}], state}
        <span style="color:#75715e">#{[demand: {:input, 1}, buffer: {:output, %{buffer | payload: &lt;&lt;0::size(buffer_size)&gt;&gt;}}], state}</span>
      <span style="color:#66d9ef">end</span>
    <span style="color:#66d9ef">else</span>
      %{state <span style="color:#f92672">|</span> <span style="color:#e6db74">buffered</span>: buffered}
      {[<span style="color:#e6db74">demand</span>: {<span style="color:#e6db74">:input</span>, <span style="color:#ae81ff">1</span>}], state}
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>The final part is only starting and sleeping:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#a6e22e">Membrane.Pipeline</span><span style="color:#f92672">.</span>start_link(<span style="color:#a6e22e">Membrane.Demo.SimplePipeline</span>, [])
<span style="color:#e6db74">:timer</span><span style="color:#f92672">.</span>sleep(<span style="color:#e6db74">:infinity</span>)</code></pre></div>
  </div>

<p>My goal with this was actually to use it in <a href="https://elixirforum.com/t/project-the-grand-kiosk-the-seeed-studio-reterminal-dm-with-nerves/66321">The Grand Kiosk</a> a Nerves demo for <a href="https://www.seeedstudio.com/reTerminal-DM-p-5616.html">the Seeed Studio ReTerminal DM</a> that I am working on. It has a mic array. And Membrane works fine with Nerves even though the precompiled stuff isn&rsquo;t available for Nerves targets so I do need to add it in my custom system. But I know how to do all that. Running this as a filter for hailing Whisper and having Whisper interpret speech that I can then use for text-based commands seems like fun. I want to see if I can do a nice job with vector embeddings for fuzzy-matching pre-defined voice commands as well.</p>
<p>Unfortunately Ortex won&rsquo;t cross-compile because <a href="https://github.com/elixir-nx/ortex/issues/41">some underlying Rust library won&rsquo;t cross-compile</a> quite right. For now I am stuck. I toyed with trying to hack out the bad arg from the Rust deps but I don&rsquo;t know Cargo well enough to do it right and might not have found the right dep to hack.</p>
<p>There will be a path forward eventually I&rsquo;m sure. I was happy to see that once I got the right data shape into Silero VAD all my previous experience with Membrane elements made those parts just slot together quite nicely. And it is a pretty neat little example of an Elixir script.</p>
<p>Thanks for reading. If you have questions you can get a hold of me via the fediverse <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> or email <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Bodging GenServers Together</title>
      <link>https://underjord.io/bodging-genservers-together.html</link>
      <pubDate>Fri, 22 Nov 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/bodging-genservers-together.html</guid>
      <description>What feels like forever ago but what was probably a year and a half I gave a talk about Lively LiveView with Membrane. Video is available for the curious. It was a stunt talk but also a talk about creativity and how Elixir let&amp;rsquo;s me plug things together and try things that feel magical. That feeling has never left me.
Some of the magic is a goddamn pain. Take the Membrane framework.</description>
      <content:encoded><![CDATA[ <p>What feels like forever ago but what was probably a year and a half I gave a talk about Lively LiveView with Membrane. Video <a href="https://www.youtube.com/watch?v=K51fj1JGQEY">is available</a> for the curious. It was a stunt talk but also a talk about creativity and how Elixir let&rsquo;s me plug things together and try things that feel magical. That feeling has never left me.</p>
<p>Some of the magic is a goddamn pain. Take the <a href="https://hexdocs.pm/membrane_core/readme.html">Membrane framework</a>. I really like what it enables me to do but it is also a bit messy. Setting up pipelines is not trivial, writing custom elements is a bit finicky and requires some practice. But more centrally the dependencies, build requirements and underlying nastiness of video formats is really what makes everything harder than you want it to be. Membrane gives you a lot of capability to do live things, within Elixir, but you are playing with media and media as a domain is terrible.</p>
<p>If you spend a lot of time in the browser you are used to the platform papering over the difficulty of opening a microphone in a cross-platform manner or playing video.</p>
<p>Something I want to do in the near term is finish up my tech demo of the <a href="https://www.seeedstudio.com/reTerminal-DM-p-5616.html">Seeed Studio ReTerminal DM</a> and that includes making it do some unexpected stuff, all from Elixir. This actually involves &ldquo;AI&rdquo;, meaning Machine Learning. LLMs are mostly impractical for local use. They definitely don&rsquo;t run well on a Raspberry Pi 4 CPU. But there are models that are perfectly feasible.</p>
<p>Take a Voice-Activity Detection model, which are lightweight models designed to determine what parts of an audio stream are speech. Sean Moriarty has <a href="https://dockyard.com/blog/2024/03/19/elixir-machine-learning-pre-trained-onnx-models-with-ortex">covered this</a> as a part of his <a href="https://seanmoriarity.com/2024/02/25/implementing-natural-conversational-agents-with-elixir/">experiments in voice assistance</a>. It is a relatively small and cheap-to-run model that will save you a lot of performance where you don&rsquo;t need to bother to process things not identified as speech. The device I run has microphones, so it can do this. And then we can take the voice-parts and throw them at the heavier Whisper model and get text. Then we can decide how we want to map text to action. This is all pretty useful applications of ML, quite practical and hands-on. Plumbing and pre-trained models.</p>
<p>ML is also goddamn painful. Whenever you paint outside the lines a bit things get really weird really quickly. The VAD model used in the guide has gone beyond the ONNX support in libraries and the input arguments have changed shape. So I have to pin down the right old model to use and wrangle the inputs just so. It is a deep domain and when the tools pave over the hard parts everything is nice and when the hard parts peak up you may want to scream. But I don&rsquo;t have to shuttle data back and forth to a Python script so I prefer it.</p>
<p>I think there is bit more I could do in terms of Machine Learning as well. If I want something close to LLMs language semantics for recognizing commands/actions to take I could use an embedding model and make vectors of all available commands, make a vector of the command given and it should let me <a href="https://github.com/elixir-nx/hnswlib">perform a vector search</a> on language semantics more than just letter-level proximity. Pulling out arguments and such would still be utterly painful.</p>
<p>LLMs are decent at pulling out arguments but a 3B model tuned to function calling (looked at Galive Function Calling V1 for example) seems to require about 10GB of memory for your GPU, that&rsquo;s a nice big enthusiast Nvidia GPU you need there. This is not feasible for an RPi. Of course I could pass the inputs off to an LLM API service but I find that uninteresting as a demo.</p>
<p>Suffice to say, I get to work within some creative constraints which is not all that bad. I think I can get reasonably responsive voice commands and that would be novel and an interesting demo. It will allow a lot of fun LiveView stuff since all of it is streaming. I can get events about when the VAD detects your voice and immediately start indicating it is paying attention, then as soon as the VTT model starts to spit out words I can show you what it is hearing, then I could do various interesting things in terms of searching for the closest commands, maybe show you what you are closest to at every turn. Depends on how fast the vector search is.</p>
<p>Membrane pipelines are just GenServers. Nx Servings that underpin Bumblebee are just GenServers. LiveViews are just GenServers. Nerves was born to start GenServers for you.</p>
<p>Do you ever bodge together GenServers? What is your place of joyful experimentation and where does it get painful? If you want to discuss you can reach me on the fediverse <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> or <a href="mailto:lars@underjord.io">lars@underjord.io</a>. Also on Bluesky recently, will see how long that place lasts.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>How I use Erlang Hot Code Updates</title>
      <link>https://underjord.io/how-i-use-erlang-hot-code-updates.html</link>
      <pubDate>Tue, 19 Nov 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/how-i-use-erlang-hot-code-updates.html</guid>
      <description>One of the Erlang ecosystem&amp;rsquo;s spiciest nerd snipes are hot code updates. Because it can do it. In ways that almost no other runtime can.
I use Elixir which builds on Erlang and has the same capabilities.
The standard way of doing Elixir releases via mix release does not support Erlang hot code updates. As in, it will not generate the necessary files for you. And if you do want to do it there are several blog posts you need to stitch together or you need to use the Erlang docs in great detail.</description>
      <content:encoded><![CDATA[ <p>One of the Erlang ecosystem&rsquo;s spiciest nerd snipes are hot code updates. Because it can do it. In ways that almost no other runtime can.</p>
<p>I use Elixir which builds on Erlang and has the same capabilities.</p>
<p>The standard way of doing Elixir releases via <code>mix release</code> does not support Erlang hot code updates. As in, it will not generate the necessary files for you. And if you do want to do it there are several blog posts you need to stitch together or you need to use the Erlang docs in great detail. Learn You Some Erlang has <a href="https://learnyousomeerlang.com/relups">documented much of it</a> of course. AppSignal also has a neat guide on <a href="https://blog.appsignal.com/2021/07/27/a-guide-to-hot-code-reloading-in-elixir.html">hot code reloading in Elixir</a>.</p>
<p>Bryan Hunter and Chris Keathley are both people I listen when they speak because it tends to be interesting, challenging and something I just might not have heard before. Both have described hot code updates as something that people should learn and use. I imagine Whatsapp&rsquo;s initial engineering crew would agree. They did pretty well.</p>
<p>Of course we do use it. But mostly for trivial things.</p>
<p>Anyone using IEx <code>r MyModule</code> or <code>recompile</code> are doing hot code updates. But reloading a module in development on you local machine doesn&rsquo;t feel all that spicy. It is cool and useful but feels like an incremental compile or watcher/builder type of thing.</p>
<p>I use it constantly <a href="https://underjord.io/unpacking-elixir-iot-embedded-nerves.html">with Nerves</a>. When developing on an embedded Elixir device and needing to tune some numbers, or reworking a module, pasting into the IEx is faster than uploading new firmware and waiting for the reboot. I stop and start parts of the applications or just terminate the relevant GenServer if I need a state reset.</p>
<p>I&rsquo;ve also used it for remote devices over <a href="https://github.com/nerves-hub/nerves_hub_web">NervesHub</a>&rsquo;s built in web console that offers an IEx prompt. When debugging a misbehaving Real Time Clock it was pretty nice to just paste chunks of utility functions and relevant I2C-calls over to the device to get clear answers about what it was doing.</p>
<p>I&rsquo;d love to see more tooling for the full-blown delivery of hot code updates on top of Elixir&rsquo;s <code>mix release</code> tooling. Or in the vein of the predecessor <code>distillery</code>. I don&rsquo;t think there are any shortcuts for doing a correct hot code update. Much like database migrations they require care. And you&rsquo;ll need to know how you dependencies react to hot code updates and many other interesting topics.</p>
<p>This was just a quick one in case people are curious how Erlang&rsquo;s hot code updates are actually used, day-to-day, in Elixir.</p>
<p>If you have comments or questions you can find me on the fediverse via <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> or email me at <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Anatomy of Embedded Elixir</title>
      <link>https://underjord.io/anatomy-of-embedded-elixir.html</link>
      <pubDate>Mon, 07 Oct 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/anatomy-of-embedded-elixir.html</guid>
      <description>Working on the Nerves project, the Embedded framework for Elixir, has given me an increased appreciation for how Frank Hunleth and his collaborators through the years have structured things. And while I&amp;rsquo;ve found crossing into the Linux-heavy part of it difficult and frustrating there has been reasonable steps to take all the way from building the application layer in Elixir all the way back to fighting the bootloader. I&amp;rsquo;ll try to detail how a Nerves system is built up in this post.</description>
      <content:encoded><![CDATA[ <p>Working on <a href="https://nerves-project.org">the Nerves project</a>, the Embedded framework for Elixir, has given me an increased appreciation for how Frank Hunleth and his collaborators through the years have structured things. And while I&rsquo;ve found crossing into the Linux-heavy part of it difficult and frustrating there has been reasonable steps to take all the way from building the application layer in Elixir all the way back to fighting the bootloader. I&rsquo;ll try to detail how a Nerves system is built up in this post.</p>
<h2 id="the-erlang-of-it-all">The Erlang of it all</h2>
<p>The reason to do Embedded Linux with Elixir is the BEAM. And yes, I have to type Linux every time or someone goes &ldquo;hurr, hurr, oh you meant Linux, not real embedded, that&rsquo;s microcontrollers only&rdquo;, some are friendly, some are not, all are compelled to intervene. Anyway, the BEAM virtual machine treats your application as a system, and makes it possible to operate it at runtime and while this is powerful in your web app it is really important in Nerves. Your code gets assembled into <strong>an Erlang release</strong>. A tarball of various bits along with the necessary runtime parts.</p>
<p>That release contains many Erlang Applications. Some are system applications such as <code>os_mon</code>  the OS monitor, <code>alarm_handler</code>  the .. alarm .. handler. Then we have Nerves applications that come from Nerves-related libraries. Many of these will generally work on any Linux distribution but were developed for use with Nerves. Some provide facilities that other applications usually perform in Linux. These generally allow you to integrate the system-level information into your application in a nicer way with message passing and API structure we know and love:</p>
<ul>
<li><a href="https://github.com/nerves-time/nerves_time">nerves_time</a> manages time, NTP, storing latest known time across power going down, other time sources.</li>
<li><a href="https://github.com/nerves-networking/vintage_net">vintage_net</a> Good ol' networking. Manages Linux networking but from Elixir. This does delegate work to the regular network stack and tools by calling <code>ip</code> or <code>wpa_supplicant</code> as necessary. However we can react to activity in the networking layer as message subscriptions and much more.</li>
<li><a href="https://github.com/nerves-project/nerves_runtime">nerves_runtime</a> is probably the most Nerves-specific as it bundles a number of important bits. Among them <a href="https://github.com/nerves-project/nerves_uevent">nerves_uevent</a> which replaces <code>udev</code> as a way to load drivers as requested and *gestures vaguely* <em>other stuff</em>.</li>
<li><a href="https://github.com/nerves-hub/nerves_hub_link">nerves_hub_link</a> entirely optional but used if you use NervesHub for firmware/OTA updates.</li>
</ul>
<p>Then there is usually your app. Let&rsquo;s call it <code>my_app</code>. It can be one or more applications along with any applications provided by your dependencies. Generally if a dependency manages its own lifetime and state it will run an application (NervesHubLink is for example quite independent, by design). If you should manage its state and lifetime, it will run in your application supervision tree (Ecto Repo, Phoenix&rsquo;s Endpoint and Phoenix PubSub are typical examples). Because applications are independent parts of your system you can start and stop them. They can depend on each other being started.</p>
<p>Each application has its own supervision tree meaning it can run multiple things and decide how they restart on failure and that they fail independently of other applications if the entire application fails. Your Nerves system can start the Erlang release and your main application can fail to start. The system can still connect to your update system, it can still correct the time, you may have a mechanism telling it to reboot from this broken state at some point but a programming error that breaks at runtime due to some stray piece of data lodged in an unexpected way will not prevent you from sending out an update.</p>
<p>The BEAM replaces systemd and any other orchestrating entity in a Nerves system. This means that if you run external binaries you probably integrate them into your supervision tree in some application using <a href="https://github.com/fhunleth/muontrap">MuonTrap</a>. You can even integrate container runtimes and run containers in a similar manner.</p>
<p>This is the Erlang release and the small pile of applications it ships. It includes:</p>
<ul>
<li>Your Elixir application(s)</li>
<li>Your regular dependencies applications</li>
<li>Your Nerves-related dependencies</li>
<li>Built-in Erlang applications you depend on</li>
</ul>
<h2 id="erlinit">erlinit</h2>
<p>PID 0 in Linux, the thing that starts everything needs to do a bit of work. In Nerves we have <a href="https://github.com/nerves-project/erlinit">erlinit</a> that takes care of mounting <code>/dev</code> and other duties before starting the Erlang release. So this is a thin sliver under the Erlang release on top of the root filesystem.</p>
<h2 id="partitions">Partitions</h2>
<p>The official Nerves systems boot through a boot partition with the necessary files, resources, device trees and whatnot required to start Linux and then that mounts the SquashFS root filesystem and runs (erl)init from there.</p>
<p>This partition structure is shown in the Nerves system&rsquo;s <code>fwup.conf</code> (<a href="https://github.com/nerves-project/nerves_system_rpi4/blob/main/fwup.conf">rpi4 example</a>) and if you check you will see it has Boot A, Boot B, Root A, Root B. And this is for the A/B firmware update scheme used in firmware updates. A new update is applied to the partition not currently used (let&rsquo;s say B) and then we reboot with B as the primary partition. If that goes well we persist the change and all is good but we know we can switch back to the previous version if something is wrong.</p>
<h2 id="where-the-linux-at-run-time-vs-build-time">Where the Linux at? Run-time vs Build-time</h2>
<p>Between the partitions and erlinit. We&rsquo;ve mostly been concerned with the run-time system now. All this stuff also has a build-time component to it of course but they are run-time abstractions in my mind. They are meant to be in the system when it runs. And Linux, the kernel, weaves in as the boot partition gets mounted by whichever bootloader you have and then you tell Linux where to get your rootfs and it will go and start erlinit helpfully for you. But explaining starting Linux without mentioning the partitioning first seemed unhelpful.</p>
<p>Now we mostly leave run-time concerns.</p>
<h2 id="the-firmware">The firmware</h2>
<p>The .fw file used by <a href="https://github.com/fwup-home/fwup">fwup</a> is quite simple. Coming up with the ideas behind it was probably less simple. It is a zip archive with a file called <code>meta.conf</code> and a directory called <code>data</code> which contains files that will be used in writing the final image. The conf file contains some useful metadata fields and then a bunch of instructions for writing the partitions and putting the files from the data directory onto the card. <code>fwup</code> organized the zip archive so that it can be streamed to devices to allow for quick and memory efficient updates. It also supports firmware validation (hashes and signatures) as well as delta updates (only shipping the binary diffs between the current and desired firmware)</p>
<p>This is all defined in <code>fwup.conf</code> in the Nerves system. If you&rsquo;ve read the <code>fwup.conf</code> the <code>meta.conf</code> produced will make a ton of sense.</p>
<p>Now we entirely leave runtime concerns.</p>
<h2 id="your-project">Your project</h2>
<p>We are now looking at build-time things, scaffolding and tooling and we need to revisit how your stuff fits in with the Nerves stuff. First time users of Nerves will just generate or clone and Elixir project with some Nerves stuff in it and write Elixir code typically. They build with a new command <code>mix firmware</code> when they want to run on hardware but the project is fairly normal. The config has a mildly different structure usually with a distinction between <code>host</code> and <code>target</code> but nothing wild.</p>
<p>Your project simply has a dependency on one or more hardware-specific Nerves systems depending on which hardware you support. When building, a pre-built version of the system will hopefully be pulled, your Erlang release gets plopped into the rootfs, the <code>fwup.conf</code> gets applied to generate <code>.fw</code> firmware file for you.</p>
<h2 id="the-nerves-system">The Nerves System</h2>
<p>Maybe the Raspberry Pi 5, the TI SK AM62B-P1, the Seeed Studio ReTerminal DM or a good ol' BeagleBone Black (or Blue, or Green). They all get a specific Nerves system for them and if you build a variant of the hardware you get to make a variant of the system to match your customizations. These highly specific systems make for a knowable setup for everything that concerns you hardware that is easy to own and maintain in the long term.</p>
<p>There are a bunch of bits and bobs that vary per system but typically you will find:</p>
<ul>
<li><code>nerves_defconfig</code>
Buildroot configuration for what gets included in the system, system applications, shared libraries, lots of stuff.</li>
<li><code>linux-x.y_defconfig</code>
Linux configuration for what gets built into the kernel, drivers, modules overlays and whatchamacallits.</li>
<li><code>linux/</code>
A directory with any patches to Linux that the system needs.</li>
<li><code>fwup.conf</code>
As previously discussed, defines the partition and composition of the firmware image.</li>
</ul>
<p>The specific system builds on a more general <a href="https://github.com/nerves-project/nerves_system_br">nerves_system_br</a> a base to hold the general buildroot config. It also contains any patches to buildroot packages of which there are typically a few. Not everything is suitable for upstreaming.</p>
<p>You generally don&rsquo;t need to look at the Nerves system unless you need to customize drivers or ship extra code that you can&rsquo;t get from your Elixir dependencies. It&rsquo;s even more rare that you need to touch the base <code>nerves_system_br</code>.</p>
<h2 id="buildroot-itself">Buildroot itself</h2>
<p>Used to pull together a Linux system and provide a way to add and configure system-level components at build-time <a href="https://buildroot.org/">Buildroot</a> is a kind of old-school project but active and going steady. From my understanding it is also very straightforward in how it is put together. I know enough to use it in Nerves and I could probably use it vanilla if I tried but I&rsquo;m not good at Buildroot yet. Also not good at Linux config yet. Before I did Nerves I&rsquo;d rarely even whispered about recompiling the kernel. Now I do it pretty much daily for various projects and experiments.</p>
<h2 id="tooling-and-framework">Tooling and framework</h2>
<p>The concept of a System is a Nerves thing but what it does in total is not. The base system of <code>nerves_system_br</code> and the specific system for your board together encompass something every Embedded Linux project needs and in the end creates. Either implicitly or explicitly:</p>
<ul>
<li>A way to acquire a built Linux kernel with all relevant drivers for the hardware.</li>
<li>A way to include other software.</li>
<li>A way to specify the partition scheme, including boot concerns.</li>
</ul>
<p>The system lays out what it does very explicitly, flattened. It has minimal indirection. It stays manageable by being as small as feasible. This is an intentional design decision. Elixir is your app layer and then when you need to pop the hood of the system it is all laid out there. There is not a sprawling system of interlocked smaller hoods to pop in the right order to get at whatever gasket is fraying.</p>
<p>To go beyond the fundamentals there is the tooling for integrating with Mix and further smart choices, such as using fwup, which enables further features, niceties and good practices. I should get into all the design choices made to keep your Nerves firmware from becoming a brick. There are a lot of choices that serve that specific purpose. That&rsquo;s for another post.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>A statically defined, mostly deterministic, set of parts make up the system. Add your code on top. It compiles to a lean, compressed, firmware definition that can be signed and wrangled as need be. A single artifact that wraps up you entire device firmware and how to update it.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Putting code on a Nerves device</title>
      <link>https://underjord.io/putting-code-on-a-nerves-device.html</link>
      <pubDate>Mon, 16 Sep 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/putting-code-on-a-nerves-device.html</guid>
      <description>The Nerves project is a way of building embedded Linux devices where the BEAM virtual machine takes care of running things. This does not constrain what you can run in any significant way.
The easiest way to think of it is as a replacement for systemd. Though it is not only that. I will attempt to explain the different ways you can run things via the BEAM and consequently, on Nerves.</description>
      <content:encoded><![CDATA[ <p>The <a href="https://nerves-project.org">Nerves project</a> is a way of building embedded Linux devices where the BEAM virtual machine takes care of running things. This does not constrain what you can run in any significant way.</p>
<p>The easiest way to think of it is as a replacement for systemd. Though it is not only that. I will attempt to explain the different ways you can run things via the BEAM and consequently, on Nerves.</p>
<h2 id="1-beam-code-elixir-erlang--friends">1. BEAM code (Elixir, Erlang &amp; friends)</h2>
<p>If you write your darned application the it was intended. In a Elixir, or Erlang, or probably Gleam, potentially even LfE. Then you&rsquo;ll have the loveliest time. Not because that&rsquo;s given preferential treatment, rather just because these languages are capably of utilizing the whole runtime.</p>
<p>You get light-weight processes that communicate through message passing. Resilient pre-emptive scheduling. Consistently low-latency. Hot code updates which we use heavily in Nerves because it is really nice to just paste code into the device during development instead of reflashing.</p>
<p>And you get easy access to libraries, the Phoenix web framework and the parts just fit together. Beyond that the Nerves runtime is built in this same manner and you can interact with things like your network configuration, firmware update and plenty of hardware via message passing and function calls. This makes it very easy to write systems that react <em>live</em> to changes.</p>
<p>In my world-view this is where you want to spend the bulk of your development time because you get quick iteration speed, great capabilities and a lovely language that does a decent job of keeping you from making a mess.</p>
<h2 id="2-native-implemented-functions-or-c--beam">2. Native Implemented Functions or C + BEAM</h2>
<p>This is number two in terms of how close to the BEAM it is, not for how recommended it is. The risk of NIFs must be stated. You segfault in a NIF and the BEAM cannot protect you and your undermine the resilience of it. The Nerves variant of the Erlang heart module might still save you. But a lot of the safeties of the higher level of abstraction go out the window. You are running code directly in the BEAM.</p>
<p>The various kinds of NIFs are a bit much to get into here. If you can slice the work in thin timeslices the old-school NIFs are rad because the scheduler can treat them much like it treats any BEAM process. But there are also dirty NIFs that run on a different life-cycle with &ldquo;worse&rdquo; guarantees but a much more manageable programming model.</p>
<p>C is the basic way of doing this. Then you have Zig via the tool <a href="https://github.com/E-xyza/zigler">Zigler</a>. And of course Rust via <a href="https://github.com/rusterlium/rustler">Rustler</a>. Both Zig and Rust to some extent reduce the risk of you segfaulting the BEAM so they are potentially preferable.</p>
<h2 id="3-ports">3. Ports</h2>
<p>There are several ways of running Ports. This is an Erlang concept where a Port is an external process that you start from the BEAM and communicate with via stdin and stdout. In Nerves we typically use a Linux-specific and more featureful <a href="https://github.com/fhunleth/muontrap">Muontrap</a> library. It supports cgroup stuff to control memory and CPU usage of the processes. This can be plugged right into your BEAM supervision tree and have it&rsquo;s lifecycle managed like a proper gentleprocess.</p>
<p>So if you have a vendor-provided binary you have to run, some Python script, some nice statically compiled Go binary. This is a good path. With tools like <a href="https://github.com/nerves-networking/beam_notify">beam_notify</a> you can even pass reasonable BEAM messages from shell scripts.</p>
<h2 id="thats-it">That&rsquo;s it?</h2>
<p>These three cover everything you need. Especially number 3, the Ports. You can run processes on Linux. What else do you need?</p>
<p>But there are several additional interesting things you can do.</p>
<h2 id="4-c-node">4. C Node</h2>
<p>You can build an application that pretends it is an Erlang node and.. I would say that this is a stretch for most Nerves usage. Let me know if you have different ideas. But it <em>is</em> one of the Erlang mechanisms for interop.</p>
<h2 id="5-containers">5. Containers</h2>
<p>Apparently containers are cool.</p>
<p>Actually, they are cool. I don&rsquo;t like most of the tools around them but it is very practical sometimes to just pack up and ship the developer machine to not figure out what exact combo of deps will make node happy. By default Nerves does not ship a container runtime because they are a lot of extra stuff when not needed and in most embedded projects you can just ship a firmware update when you want to ship new code. But it can make sense either because you get containers from somewhere or if you have a need for very isolated and selective updates. Or you just really like it architecturally.</p>
<p>Several people have run Docker and variants on Nerves for this. Steffen Deusch put together <a href="https://github.com/nerves-containers">a few systems</a> using Balena Engine which I hadn&rsquo;t heard of. It is a container runtime that keeps things quite lean in the service of embedded use. I&rsquo;ve tried it and had it work. It is not an officially maintained set of systems. If enough people request containers we might consider it.</p>
<h2 id="6-special-language-implementations">6. Special language implementations</h2>
<p>If you want a sandboxed language to run on device I would look at luerl which is an implementation of Lua in Erlang which has some really cool facets to it. Every run of Lua code returns a new state of the Lua program which means you can fork off the execution or save, load and so on. Quite neat.</p>
<p>The highly experimental <a href="https://github.com/cocoa-xu/pythonx">pythonx</a> library also offers and embedded language but this time by putting Python in a NIF. Quite wild, very early. But damn if that is not a fast path to AI/ML work.</p>
<h2 id="conclusion">Conclusion</h2>
<p>You can ship your machine learning as Python if you want to. Either include Python in your system and run via a Port or run it in a container. Nerves doesn&rsquo;t care. There are a lot of nice libraries for improved interop between Python and Erlang programs actually.</p>
<p>You can run you cool Go binaries and your uncool hardware vendor software.</p>
<p>For most of the work I do recommend using the Elixir that is right there at your fingertips.</p>
<p>Regardless the Nerves project provides you what you need.</p>
<hr>
<p>If you have any questions about Nerves or this topic. Don&rsquo;t hesitate to reach out, I am <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> on the Fediverse and my email is <a href="mailto:lars@underjord.io">lars@underjord.io</a>. Hope you can run <em>your</em> code in a satisfactory way.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>To Nerves from Elixir</title>
      <link>https://underjord.io/to-nerves-from-elixir.html</link>
      <pubDate>Fri, 30 Aug 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/to-nerves-from-elixir.html</guid>
      <description>I adore Nerves. I recently joined the core team. And I&amp;rsquo;ll be doing my best to help people get along with this lovely way to co-mingle hardware and massively concurrent reliable software.
Getting started Assuming you have a comfortable Elixir &amp;amp; Erlang installed, preferrably the most recent. Most conveniently through asdf/mise, but I don&amp;rsquo;t judge. Instructions are lifted from the official docs but I will try to keep it leaner as I assume you are comfortable with Elixir.</description>
      <content:encoded><![CDATA[ <p>I adore <a href="https://nerves-project.org">Nerves</a>. I recently joined the core team. And I&rsquo;ll be doing my best to help people get along with this lovely way to co-mingle hardware and massively concurrent reliable software.</p>
<h2 id="getting-started">Getting started</h2>
<p>Assuming you have a comfortable Elixir &amp; Erlang installed, preferrably the most recent. Most conveniently through asdf/mise, but I don&rsquo;t judge. Instructions are lifted from <a href="https://hexdocs.pm/nerves/installation.html">the official docs</a> but I will try to keep it leaner as I assume you are comfortable with Elixir.</p>
<p>Install deps for your OS:</p>
<h3 id="macos--homebrew">MacOS + Homebrew</h3>

  <div class="code  shell " >
    <div class="meta">
      <span class="language">shell</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">brew install fwup squashfs coreutils xz pkg-config</code></pre></div>
  </div>

<h3 id="linux--ubuntudebian">Linux + Ubuntu/Debian</h3>

  <div class="code  shell " >
    <div class="meta">
      <span class="language">shell</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sudo apt install build-essential automake autoconf git squashfs-tools ssh-askpass pkg-config curl libmnl-dev</code></pre></div>
  </div>

<p>Have a Raspberry Pi computer (not the Picos, those are microcontrollers), with SD-card, available. It will assume a Raspberry Pi Zero, ie. using <code>rpi0</code> for referring to the target system. You can replace that in any command with <a href="https://hexdocs.pm/nerves/systems.html#compatibility">another supported system</a>:</p>

  <div class="code  shell " >
    <div class="meta">
      <span class="language">shell</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#75715e"># Install the Nerves tooling called nerves_bootstrap</span>
mix archive.install hex nerves_bootstrap

<span style="color:#75715e"># Use the Nerves tooling to create a new Elixir + Nerves project</span>
mix nerves.new my_thing
<span style="color:#75715e"># it will ask to install deps, you can yes, otherwise: mix deps.get</span>

cd my_thing
export MIX_TARGET<span style="color:#f92672">=</span>rpi0
<span style="color:#75715e"># Getting deps with the target set will fetch the base linux system</span>
mix deps.get
mix firmware
mix burn</code></pre></div>
  </div>

<p>The <code>mix burn</code> should prompt you for which SD-card you want to flash the firmware to.</p>
<p>Then start the device with that SD card in it. Give it power and it should turn on. If you have it connected with a data-cable it should show up via USB networking which is convenient. It should be announcing as <code>nerves.local</code> and have picked out one of your SSH public keys to embed in the firmware by default. Try <code>ssh nerves.local</code> to see if it is up. If not you may have to adapt a bit further and set up WiFi.</p>
<p>If you have networking the next update is still a <code>mix firmware</code> to compile but the deploy becomes:</p>

  <div class="code  shell " >
    <div class="meta">
      <span class="language">shell</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix upload nerves.local</code></pre></div>
  </div>

<p>That&rsquo;s ridiculous.</p>
<p>You can also update specific modules by just pasting them over the ssh connection into IEx which is surprisingly practical.</p>
<h2 id="set-up-wi-fi">Set up Wi-Fi</h2>
<p>Top of the generally debunked hierarchy of needs is of course Wi-Fi. You can edit <code>config/target.exs</code> to edit the config for hardware devices. <code>host.exs</code> is for running the project locally on your computer.</p>
<p>The networking libraries for Nerves are known as VintageNet and we can configure the VintageNetWiFi technology like this by replacing the default <code>&quot;wlan0&quot;</code> entry:</p>

  <div class="code  elixir "  data-file="config/target.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">config/target.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">{<span style="color:#e6db74">&#34;wlan0&#34;</span>,
      %{
        <span style="color:#e6db74">type</span>: <span style="color:#a6e22e">VintageNetWiFi</span>,
        <span style="color:#e6db74">vintage_net_wifi</span>: %{
          <span style="color:#e6db74">networks</span>: [
            %{
              <span style="color:#e6db74">key_mgmt</span>: <span style="color:#e6db74">:wpa_psk</span>,
              <span style="color:#e6db74">ssid</span>: <span style="color:#e6db74">&#34;my_network_ssid&#34;</span>,
              <span style="color:#e6db74">psk</span>: <span style="color:#e6db74">&#34;a_passphrase_or_psk&#34;</span>,
            }
          ]
        },
        <span style="color:#e6db74">ipv4</span>: %{<span style="color:#e6db74">method</span>: <span style="color:#e6db74">:dhcp</span>},
      }
    }</code></pre></div>
  </div>

<p>Re-burn the SD-card and with a little bit of time your Pi should connect to your wireless network. If you have one of the larger Pis and an ethernet cable that also works great.</p>
<h2 id="nerves-livebook">Nerves Livebook</h2>
<p>If you are a bit lost on what to do and inspiration hasn&rsquo;t sparked immediately you can instead get some neat tutorials going from <a href="https://github.com/nerves-livebook/nerves_livebook">Nerves Livebook</a>. There you get pre-baked firmware with special environment variables for setting up Wi-Fi. Note that it starts a bit slowly, especially on something like a Pi Zero. So be patient. Follow the instructions linked because they are friendly and practical.</p>
<h2 id="going-further-with-phoenix">Going further with Phoenix</h2>
<p>The Nerves documentation has a part about UI where we show how to do a so-called Poncho project (less bad than an umbrella project, but it&rsquo;s still raining). Redwire Labs published their approach which I know others have also done and <a href="https://nerves.redwirelabs.com/runbooks/naked-phoenix">dubbed it naked Phoenix</a>. I like this approach so if you want to integrate Phoenix. Try it.</p>
<h2 id="all-just-elixir-">All just Elixir *</h2>
<p>If you&rsquo;ve dealt with your fair share of Mix projects and Elixir applications Nerves is fairly straightforward because you are just building an Elixir thing. Thing can get a little bit spicier if you need some special dependencies but usually that&rsquo;s actually fairly simple. Either the library pulls it in for you or you need to <a href="https://hexdocs.pm/nerves/customizing-systems.html#buildroot-package-configuration">install packages via buildroot</a>. It is more involved than you might be used to for apt. Pretty much a slightly old-school Docker image situation. The nuisance is the build time and that&rsquo;s the flip side of building a tiny dense BEAM-centric linux from scratch instead of shipping Ubuntu and hoping for the best. I can tell you I&rsquo;ve had more luck reviving a Nerves-project 2 years down the line than I&rsquo;ve had when I&rsquo;ve hacked together something on top of Raspberry Pi OS.</p>
<p>There are some things that have fancy deps that aren&rsquo;t possible to cross-compile or aren&rsquo;t ready for it. But I&rsquo;ve seen most of Membrane compile without issue. I&rsquo;ve installed SQL Cipher as an encrypted alternative to SQLite. There&rsquo;s some nuance there but it is the same that lives in your Dockerfile plus a bit of architectural nuance.</p>
<p>You also get some cool stuff you usually don&rsquo;t poke at on a server. VintageNet for wrangling network interfaces. NervesTime for both Real-Time Clocks, ntpd and so on. The Nerves heart variant of the Erlang heart. A/B firmware partitions and firmware validation.</p>
<p>Things that are more fun on-device than on your average server is stuff like connecting a camera and using <a href="https://github.com/cocoa-xu/evision">evision</a>. I&rsquo;ve added a Coral TPU over USB and had some fun via <a href="https://github.com/cocoa-xu/tflite_elixir">tflite_elixir</a>.</p>
<p>Find some devices to talk to, you now have a little server that can pull down your calendar, parse it and control things based on that, or based on a public RSS feed you like. There are so many possible things. I have hacked on <a href="https://github.com/lawik/keylight">my Elgato Keylights</a> and my <a href="https://github.com/lawik/streamdex">Elgato StreamDeck</a>.</p>
<p>And you can get incredible mileage out of OTP functionality like GenServers and the binary pattern matching to do things with hardware. You really get to do the stuff Erlang was made for.</p>
<h2 id="more-nerves">More Nerves</h2>
<p>Things to read further on:</p>
<ul>
<li><a href="https://www.nerves-hub.org/">NervesHub</a>
Really fancy firmware updates, including binary delta updates, for Nerves devices. Me, Josh and the Nerves team are making them cooler by the day. Includes <a href="https://github.com/nerves-hub/nerves_hub_link">nerves_hub_link</a> and <a href="https://github.com/nerves-hub/nerves_hub_cli">nerves_hub_cli</a>.</li>
<li><a href="https://github.com/nerves-hub/nerves_key">NervesKey</a>
Opinionated abstractions on top of hardware encryption using the ATECC608-series from Microchip. This gives your device an identity and I&rsquo;ve <a href="https://www.youtube.com/watch?v=Yd-qWQ65eQ8">been doing stuff with it</a>.</li>
<li><a href="https://hexdocs.pm/mobius/readme.html">Mobius</a>
On-device metrics, I want to look into this one and try to combine it with Explorer in some neat fashion.</li>
<li><a href="https://hexdocs.pm/scenic/welcome.html">Scenic</a>
Not currently the liveliest project but it does allow you to make UI all in Elixir and is used in production.</li>
<li><a href="https://elixir-circuits.github.io/">Sensors &amp; peripherals</a>
There are many devices supported to be used directly from Elixir that you can pick up off of Adafruit or Pimoroni and just have fun with. I&rsquo;ve implemented the <a href="https://hex.pm/packages/inky">Inky eInk display</a> and the <a href="https://hex.pm/packages/vcnl4040">VCNL4040</a> ambient light and proximity sensor.</li>
</ul>
<h2 id="exit">exit</h2>
<p>That&rsquo;s it for now. Try Nerves and let me know if you run into things that feel confusing or weird about Nerves and I&rsquo;m happy to try and clarify, improve and generally help you succeed with Nerves.</p>
<p>I am reachable on email <a href="mailto:lars@underjord.io">lars@underjord.io</a> and on the fedi <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Catching up with Elixir</title>
      <link>https://underjord.io/catching-up-with-elixir.html</link>
      <pubDate>Wed, 28 Aug 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/catching-up-with-elixir.html</guid>
      <description>I&amp;rsquo;ve kept very busy recently and as I look at what I published last it has clearly kept me from blogging. I don&amp;rsquo;t love that. I like having a blog and I like tending to it. I&amp;rsquo;ve not missed a beat on the newsletter&amp;rsquo;s weekly cadence but that might not be your jam. Let&amp;rsquo;s catch up. I&amp;rsquo;ve been diving into embedded Linux with Elixir.
My latest non-newsletter issue was about The Future Stack.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;ve kept very busy recently and as I look at what I published last it has clearly kept me from blogging. I don&rsquo;t love that. I like having a blog and I like tending to it. I&rsquo;ve not missed a beat on <a href="https://underjord.io/newsletter.html">the newsletter</a>&rsquo;s weekly cadence but that might not be your jam. Let&rsquo;s catch up. I&rsquo;ve been diving into embedded Linux with Elixir.</p>
<p>My <a href="https://underjord.io/the-future-stack.html">latest non-newsletter issue</a> was about <a href="https://www.youtube.com/watch?v=buy_ke9s-sM&amp;pp=ygUNZ2lnIGNpdHkgbGFycw%3D%3D">The Future Stack</a>. I think that idea is interesting and holds some water though I&rsquo;m still not convinced anyone will solve the database without immense trade-offs. I&rsquo;m pretty curious about geo-located tenancy via SQLite as a way to tune perf to customers but I don&rsquo;t need it for what I do so not trying it. That talk was supported by Tigris and while I still <a href="https://www.tigrisdata.com/blog/metadata-quering-with-elixir/">work a</a> <a href="https://www.tigrisdata.com/blog/eager-and-lazy-caching/">little bit</a> with them I am mostly wrapping that up as I have other commitments that drain my time. I really like their product and the team has been super friendly. Strong recommend. Hopefully you will see more about them from the perspectives of people I know that write well and have cool ideas.</p>
<p>My latest blog post was a <a href="https://underjord.io/newsletter-building-up-the-nerves.html">newsletter cross-post</a>. About Nerves adoption. Nerves being the embedded Linux framework for Elixir. These things happened in sequence:</p>
<p>Random company I&rsquo;d never heard of reached out with a need for a Nerves consultant to show them the ropes and guide them in Elixir. Nerves Client #1.</p>
<ul>
<li>Work <a href="https://elixirforum.com/t/secure-boot-with-raspberry-pi-cm4-and-nerves/63160">Secure Boot for CM4</a> devices.</li>
<li><a href="https://elixirforum.com/t/vintagenet-with-wired-802-1x-under-nerves/63228">Wired 802.1x with EAP-TLS via NervesKey</a> (ATECC608)</li>
<li>And me <a href="https://www.youtube.com/watch?v=Yd-qWQ65eQ8">doing a talk</a> on securing Nerves devices and all the detours it involved.</li>
</ul>
<p>Frank&rsquo;s team at SmartRent needed som reinforcements. Nerves Client #2.</p>
<ul>
<li>I&rsquo;ve shipped <a href="https://hex.pm/packages/vcnl4040">an Ambient Light sensor driver</a> in Elixir (VCNL4040).</li>
<li>I&rsquo;ve tuned more ambient light vs. backlight than I could ever dream.</li>
<li>Worked with ZWave PTI (package tracing interface) but thankfully they had a Ben that solved most of it.</li>
<li>I&rsquo;ve fought Real-Time Clocks.</li>
</ul>
<p>At this point I made a decision to pivot Underjord towards Nerves. I like a niche and heck if that ain&rsquo;t one. Not kicking out any clients. Just focusing in on that for future work. And specifically Nerves with a security tinge because I always find that interesting.</p>
<p>Then Josh Kalderimis shows up out of nowhere doing <a href="https://nervescloud.com">NervesCloud</a> and somehow derails me into co-founding a hosted version of <a href="https://github.com/nerves-hub/nerves_hub_web">NervesHub</a> with him. So I&rsquo;m doing a startup. This was unexpected. This further links my destiny and success with the Nerves community and ecosystem.</p>
<p>If you don&rsquo;t follow my other channels you may have missed my series of <a href="https://www.youtube.com/playlist?list=PLk_-dV_ai0LERQeaYYs7A5siKH4eEr4dI">talking at you about Nerves</a>. Currently releasing daily. I&rsquo;ve also been active on the socials about various Nerves efforts. I&rsquo;m gearing up for a talking in Berlin at Code BEAM in October where I will be covering a lot of ground. Then I have another talk in Malmö for Oredev where I will spread that Nerves flavor to the unknowing, unsuspecting general developer public.</p>
<p>If you follow Elixir you might see a lot of hobby-grade Nerves being done. That&rsquo;s cool and fun. It is not what it was built for though. It was built, much like Erlang to be robust, production-grade, pragmatic, nuanced and practical. With that Elixir flair. And starting NervesCloud has led to seeing a new Nerves company nearly every week. Small and larger. Or companies doing Elixir on hardware that really would benefit from Nerves. Lots of interesting stuff happening out there.</p>
<p>And just a few days back I talked to Frank Hunleth and it was voted that I&rsquo;d join the Nerves core team. It is not a shock as I&rsquo;ve been running the newsletter for several years now and my work in the team will really be about communication more than anything.</p>
<p>I think Nerves is a fantastic way of building embedded Linux devices. It uses the simplest standard tool to get you a minimal Linux, namely Buildroot. Then it boots you into the BEAM and it takes the wheel. From there you generally write Elixir (or if you prefer, Erlang, Gleam). The supervision trees, the stateful GenServers, the binary pattern matching, the message passing. It all makes such sense in an embedded device where what you are developing is not just a one-purpose service. It is a full system.</p>
<p>Erlang was built for telecom switches that would now be very comparable to &ldquo;edge&rdquo; devices. They exist in inconvenient geographical places. They need to be reliable and they have a lot of stuff going on in them.</p>
<p>It seems like most embedded Linux projects grab an OS for their fundamental approach (Yocto, Buildroot, Raspberry Pi OS or a Debian derivative). Then the system orchestration is probably based on systemd. Then on top of that comes the wild west. Maybe Python? Maybe Node.js? Maybe C++ or Java.</p>
<p>Microcontrollers are a different conversation and that conversation is largely about Zephyr now it seems.</p>
<p>We&rsquo;re talking about embedded <em>Linux</em> and equivalent. Nerves brings opinion to this space. Elixir, OTP and the BEAM give you standard ways to do things. Firmware updates? NervesHub. Bad firmware recovery? fwup with A/B partitions. Web framework? Phoenix, absolutely top of the line. Web UI? LiveView and you typically lose the latency disadvantages. Ephemeral state? GenServers. PubSub mechanism? Message passing. Message passing between jobs. Message passing.</p>
<p>We&rsquo;re back at the graph from Sasa Juric&rsquo;s book Elixir in Action about what jobs in a modern web app can be replaced with just Erlang.</p>
<p>And the value of pre-emptive multi-tasking for all this is hard to overstate. The value of consistently low latency. The value of fault-tolerance.</p>
<p>For iteration speed during development you get a whole scale of options from host-side development through pasting code over SSH, on into deploying firmware in a minute and on into deploying firmware to specific devices over NervesHub.</p>
<p>I&rsquo;ve never been more professionally satisfied. I&rsquo;m up to my ears in dev-boards, weird hardware and <a href="https://elixirforum.com/t/bringing-up-cool-hardware-with-nerves/64566">awesome impulse purchases</a>. And consequently I&rsquo;m highly motivated in helping this reach people so that there is more market so that I can run business in this space for the foreseeable future. We&rsquo;ve got a good crew of collaborators and lots of plans.</p>
<p>Near team we have <a href="https://docs.google.com/forms/d/e/1FAIpQLSdeUfKIhU0lXqReFEB2DC6sw0gWvSgiaw0XaZrxW-wde9n64w/viewform">a Nerves Workshop</a> in Berlin on the 13th of October before Code BEAM Berlin. No cost. And then I have my talk where <a href="https://github.com/lawik/fleet">the community provided me a fleet</a> of <a href="https://youtube.com/shorts/vdrPufkXn3I">over 100 devices</a>. I&rsquo;ve <a href="https://www.youtube.com/watch?v=K23DDFreXr4">joked about</a> my entry into embedded. It is for real though. I want to learn all this stuff, go deeper and closer the circuit board all the time.</p>
<p>And it is super cool to get to improve on tools the community and commercial actors are using to manage hundreds of thousands of devices. We&rsquo;ll add device health information, geo-location, better deployments and so much more.</p>
<p>It feels weird to say but I essentially run to the office every day to get hack more on all this.</p>
<p>Have any thoughts or questions, you can reach me at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or more casually on the fediverse as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Newsletter issue, adept adoption</title>
      <link>https://underjord.io/newsletter-building-up-the-nerves.html</link>
      <pubDate>Fri, 19 Jul 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/newsletter-building-up-the-nerves.html</guid>
      <description>My newsletter-provider Campaign Monitor (not a recommendation, they are .. fine I guess) are having an outage. And I&amp;rsquo;ve been on an unbroken publishing streak for the newsletter for a significant number of years. Probably 4, I could check but can&amp;rsquo;t log into Campaign Monitor. So this goes on the website until I can send it properly. Dangit.
 Building up the Nerve(s) As we are doing NervesCloud we are thinking a lot about adoption for Nerves.</description>
      <content:encoded><![CDATA[ <p>My newsletter-provider Campaign Monitor (not a recommendation, they are .. fine I guess) are having an outage. And I&rsquo;ve been on an unbroken publishing streak for the newsletter for a significant number of years. Probably 4, I could check but can&rsquo;t log into Campaign Monitor. So this goes on the website until I can send it properly. Dangit.</p>
<hr>
<h2 id="building-up-the-nerves">Building up the Nerve(s)</h2>
<p>As we are doing <a href="https://nervescloud.com">NervesCloud</a> we are thinking a lot about adoption for Nerves. Our market is tightly bound to the number of folks doing IoT/embedded projects and choosing Nerves. The business can only thrive if Nerves thrives. Nerves can only thrive if Elixir thrives.</p>
<p>I think there are enough people working effectively at Elixir adoption and it is a long path as a community with no massive commercial backers. It is doing well, it is healthy. Currently Nerves i mostly a smaller circle within Elixir. There is a small part of the Nerves pie that is entirely from outside of Elixir. Other embedded developers coming into Elixir by way of Nerves, rather than moving from Elixir into Nerves. Both of these are valid adoption paths.</p>
<p>The Elixir to Nerves one is the one I feel comfortable with. I&rsquo;ve lived in that population for 6 years and know the Elixir community &amp; ecosystem well.</p>
<p>The other end is more mysterious to me and I&rsquo;m getting into listening to embedded podcasts, YouTube videos, blogs and trying to get familiar with that space.</p>
<p>Growing Nerves will require both. Partially because we do need to grow the entire pie. But also because we need the mix of experiences.</p>
<p>Most people moving from Elixir into Nerves are comfortable with web dev. Usually some databases, distributed systems and generally web service-oriented stuff. Most are way out of their comfort zone moving into custom Buildroot systems but can certainly learn to add their supporting libraries and compile a new system. I would imagine very few, including myself, would be confident that they could bring up a new board from one of the industry vendors.</p>
<p>Most people moving from embedded Linux as a general practice into Elixir are going from C or C++ and heading into a more high-level approach. They live and breathe the low-level stuff, they don&rsquo;t mind dealing with Linux, device trees and so on. What Elixir offers them is a productive high-level abstraction for stuff like UI, orchestrating various processes and communication in a crash-resistant packaging. They can unleash developers that don&rsquo;t know C on building features for their device without the risk of them segfaulting the hardware.</p>
<p>It is fairly uncommon to find people that cover this end-to-end. I think a team building with Nerves will get the advantage of these people meeting in the middle, somewhere in the Nerves firmware and cross-pollinating experiences. Shared language, shared concepts and easier shared understanding. Less need for hard silo walls and formal boundaries due to technical domains.</p>
<p>I think these are the fundamental paths to adoption through developers. Then there is always reaching decision makers and them pushing for adoption but I&rsquo;m not convinced that that&rsquo;s very useful without technical champions pushing for the tech. And arguably you could get people that arrive cleanly without Elixir experience and without embedded experience. I don&rsquo;t have high expectations there but if we hit some exceptional viral vein, sure, then that could be a factor. That&rsquo;s a lottery and I don&rsquo;t think it is smart to bank on.</p>
<p>I find Nerves to have very good fundamentals. A developer from embedded who&rsquo;ve used Buildroot before will find that it doesn&rsquo;t actually stop you from doing anything you usually do with Buildroot. Developers from Elixir will find that most stuff is just like any Elixir project. The Nerves libraries and tools support the area linking together the embedded side and the Elixir side.</p>
<p>There are also tons of possiblities for improvement. The zero to prototype story is quite good. But it could be a ton better. With more capable generators, more opinionated starting points and so on. I see us getting the TTWK, Time To Web Kiosk, down to 5 minutes, counting from the moment your Seeed ReTerminal DM gets delivered.</p>
<p>Nerves is mildly but somewhat firmly opinionated. And the opinions it has provides a lot of capabilities for NervesHub to provide a bunch of utility. Remote IEx console is a heck of a tool. Firmware updates, firmware deltas. And we can do a lot more here. There is so much we could offer by default for a NervesHub-connected device. There is also a NervesHub CLI, that one could be improved a bunch as well.</p>
<p>Building the good features and functionality, improving everything for the best possible DX and productivity are all great tools for adoption. If the thing is not any good and doesn&rsquo;t feel any good people will bounce off of it, if they even try.</p>
<p>Unfortunately the world is very noisy. You can be deep into embedded and never hear about all the fantastic tools out there. I am continuously surprised by how many folks in Elixir have not heard of Nerves, Membrane and even stuff like Nx. It is a big world and being noticed is difficult to achieve.</p>
<p>This is where conference talks, YouTube videos, novel stunts like the 15 minute blog in Rails come in. Just to break through and catch enough eyeballs. Inside the community and ecosystem you are in, and outside.</p>
<p>Both quality of the thing and outreach for the thing are important. And then we have brand.</p>
<p>The way the project comes across. There are many ways to do this. HTMX is an example of a very unusual brand built on memes. Elixir is known for a pragmatic and productive approach to Functional Programming. SQLite is known for the database being strong and the flesh being weak. Postgres is steady and reliable. Some projects try to be cool. Others try to be sweet and friendly. The follow-through on this is a mix of how docs and resources take care of you, how the community meets and treats you and visual aspects of branding.</p>
<p>Some stumble into their brand over time. I don&rsquo;t know that Postgres has intentionally built a brand. They built their thing well over many years and they have a culture and process that produces a certain something. It is not particularly about the elephant in their case. It is not the Nike swoosh. Modern projects have to be a bit more forward about this stuff, at least if they want to grow and gain adoption.</p>
<p>I don&rsquo;t know that Nerves knows what it is brand-wise yet. Curious to figure that out because it needs to speak to both the embedded Linux dev and the Elixir dev. It needs to show the clear advantages of a lot of very nuanced choices in a deeply complex field.</p>
<p>Challenges ahead in other words. I&rsquo;m pretty excited to try and find my words on these topics and learn to speak coherent embedded. I think I speak decent Elixir at this point. Though my first talk in this realm will be in Berlin to a Code BEAM audience, so certainly starting out at home, in my own community.</p>
<p>Thanks for reading.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>The Future Stack</title>
      <link>https://underjord.io/the-future-stack.html</link>
      <pubDate>Tue, 28 May 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/the-future-stack.html</guid>
      <description>Fly.io is a highly visible cloud provider in the Elixir ecosystem and they put forward an interesting promise. They don&amp;rsquo;t deliver on that promise currently but I think it would be very compelling if they get there. Especially for Elixir. Let&amp;rsquo;s dig in.
This work is sponsored and supported by Tigris. Object Storage that works like a CDN on Fly. They are also part of the subject of the post. You&amp;rsquo;ll see.</description>
      <content:encoded><![CDATA[ <p>Fly.io is a highly visible cloud provider in the Elixir ecosystem and they put forward an interesting promise. They don&rsquo;t deliver on that promise currently but I think it would be very compelling if they get there. Especially for Elixir. Let&rsquo;s dig in.</p>
<p><em>This work is sponsored and supported by Tigris. Object Storage that works like a CDN on Fly. They are also part of the subject of the post. You&rsquo;ll see. It is not mainly about them though they play an interesting part.</em></p>
<p>This post is adapted from a talk I gave <a href="https://underjord.io/chattanooga-gigcityelixir-nervesconf-2024.html">at GigCity Elixir in Chattanooga</a>. In the talk I went to the trouble of trying to underline the value and difficulty of making things simple. I will abbreviate those efforts significantly in this blog post. If you don&rsquo;t believe simplicity has value. Come back and read this later, when you do.</p>
<p>I will offer Gall&rsquo;s Law.</p>
<p><em>&ldquo;A complex system that works is invariably found to have evolved from a simple system that worked. The inverse proposition also appears to be true: A complex system designed from scratch never works and cannot be made to work. You have to start over, beginning with a working simple system.&quot;</em></p>
<p>This doesn&rsquo;t just tell us that simple systems are the way to go when creating new things. It should also help you see the high value of any existing complex systems that work.</p>
<h2 id="opinions-and-trade-offs">Opinions and trade-offs</h2>
<p>The software way to make a thing simple is abstraction. A higher-level of abstraction should be simpler than the levels below it. Using it should produce more or better outcomes for the same effort. This means sacrificing something in translating the complex to something simpler.</p>
<p>Either we place constraints on the allowed solutions and restrict future flexibility, remove functionality or we make trade-offs in performance, in correctness or in other aspects. I would call all of these &ldquo;opinionated&rdquo; approaches.</p>
<p>Phoenix LiveView is a highly opinionated approach to web application development with significant and clear trade-offs. It collapses several concerns into one solution: the backend logic, the API contract, the frontend API client and the DOM rendering. If LiveView fits your needs it removes a lot of concerns.</p>
<p>Databases are a bit of a special case here. They are the worst. And the most important. They are where trade-offs matter the most. This is also why they are the most common bottleneck. There are things we refuse to trade off.</p>
<p>Apparently we want to keep our data. We want it to be the same when we read it back as when we wrote it. We want it to be easy to query and manipulate. We want it to be fast. Fast can mean retrieval time for a single record, a whole-ass query with joins and also when many queries are made at the same time. We place high demand and are willing sacrifice very little with databases.</p>
<h2 id="history-time">History-time</h2>
<p>I didn&rsquo;t come up in Rails but to my understanding Rails is a web framework that was willing to trade off clarity and rigour for expressiveness and productivity. It was built in a language that was willing to trade away performance for simplicity and a certain type of elegance.</p>
<p>Heroku came up and offered something that fit with Rails. The trade-offs made for Heroku didn&rsquo;t constrain Rails at all and offered immense conveniences. They were complementary and harmonious. You could shove a Rails-shaped piece in the Heroku-shaped slot without friction.</p>
<p>Rails became a darling of the startup space. The startup is a very opinionated solution as well. Quickly explore a domain with disposable solutions. Create MVPs, ship prototypes, find product-market-fit then achieve growth. Performance, rigour and maintainability are not even on the list of startup priorities usually. There was no friction between the startup way and the Rails way.</p>
<p>The trade-offs are well aligned. The Rails startup on Heroku achieves their solution in the simplest possible way. Without friction or contradiction.</p>
<p>About 40 years ago now in Sweden there was a wild software lab. They had the task of creating a platform to operate telecom systems robustly. The requirements included performance aspects such as low latency, concurrent operation, operational aspects around deploying without downtime and strong focus on reliability. It must not go down. This was a very different industry. Decidedly not a startup and it had very different trade-offs to consider. And from this we got Erlang and OTP.</p>
<p>For completeness we must quote Dijkstra.</p>
<p><em>&ldquo;Simplicity is a prerequisite for reliability&rdquo;.</em>
<em>- Edsger W. Dijkstra</em></p>
<p>Erlang sacrifices some types of performance to get immutability. Immutability is a simpler view of the world. And it uses immutability to enable a set of simple building blocks. The Process. Send. Receive. This enabled safe concurrency. From this they built up more and more abstractions in service of the desired solution. And the result lives across Erlang, OTP and the BEAM virtual machine. It is a very complex system that is proven to work. And it is clearly derived from simpler parts. It has made very different choices from Ruby and Rails. And it certainly made different trade-offs from what it was competing against: C++</p>
<h2 id="changes">Changes</h2>
<p>I wish the story was that the world realized Heroku, Rails and startups were too inefficient and came together to push for more efficient use of our resources.</p>
<p>Instead someone put JavaScript on a server and it ate Rails' lunch.</p>
<p>Salesforce bought Heroku.</p>
<p>Startups have remained a thing. Off and on. And off. And on.</p>
<p>Startups did focus more on growth though. And I believe the interest in massive scalability, large engineering teams and unicorn valuations did shift technical priorities. This is not where Rails or Heroku shine. And it is where cloud native got rolling.</p>
<p>The trade-offs made for Ruby and Rails had direct impact on performance and scalability. And Heroku was never the cheapest option. At larger scale and explosive growth your priorities change significantly. The performance you had traded off becomes a priority. The efficient scalability your MVP didn&rsquo;t need is suddenly strangling your progress. High cost at great scale is very concerning to a business. Even with massive VC funding.</p>
<h2 id="constraints-and-friction">Constraints and friction</h2>
<p>When we talk about a good abstraction it is always contextual to what we are doing. The Rails on Heroku startup was very happy, the hit company trying to scale on top of Heroku and Rails might be quite frustrated.</p>
<p>If only someone would build an ecosystem that is really productive but scales differently from Rails&hellip;</p>
<p>&hellip;</p>
<p>A good abstraction necessarily imposes constraints on you. The best constraints sacrifice things you don&rsquo;t want to be doing anyway. A lot of Elixir devs find that immutability makes their code easier to understand and follow. It reduces confusion. But it IS a constraint, when we apply that constraint we have lost a potential way to build things.</p>
<p>We certainly want nice things. In fact i think we want all the nice things.</p>
<p>We want elastic scalability but we don&rsquo;t want to operate Kubernetes.
We want a horizontally scalable database but nobody actually likes eventual consistency.
We want the speed of a cache but we don&rsquo;t want to deal with getting it right.
We want fantastic UI and UX capabilities but we don&rsquo;t WANT a single-page app framework.</p>
<p>If we opt in to any of that complexity it ought to be because we consider the upside important enough.</p>
<p>A mismatched stack of trade-offs removes all of your space to manoeuvre. If you bring in Elixir and write it like enterprise Java you are taking the worst of both worlds. Meanwhile aligned trade-offs feel like you are getting away with something. This is how a lot of people feel about LiveView, a lot of upside for no discernable downside, to them.</p>
<h2 id="what-do-you-need">What do you need?</h2>
<p>We can only have everything if we have infinite time. And we are all very human. What do you want to spend your time on? What feels important to control? What will you pay to ignore? What power will you give up for convenience? What capability do you give up for productivity?</p>
<p>A project that will only run at massive scale might benefit from Bazel, Kubernetes and all that aspirational scale from day one. Assuming you can get to day two and three. Consult John Gall about that. If your project will never run at immense scale, or only ever in one country, you can trade off geo-distribution, you can trade-off future scalability and you can get a simpler system for that sacrifice.</p>
<p>Erlang was built for industry. It traded off particular performance and strictness aspects for an incredibly powerful runtime that excels in building robust services. It is a practical tool that doesn&rsquo;t even try to be idealistically perfect.</p>
<p>Elixir is also an industry language. It doesn&rsquo;t pick a fight with the trade-offs that Erlang made. It invests in them to enable more in the realm of services. Phoenix doubles down on that for web development. LiveView triples down on that for web app UI.</p>
<p>At any point one of us can feel that one of these constraints chafe. That&rsquo;s the point at which our specific need might diverge from the most opinionated approach. This means it is on us to either peel back a layer and accept more complexity or find another compatible approach with a better match in trade-offs. We can usually shift a trade-off or two. The canonical case is probably Discord bringing in Rust to optimize a particularly demanding data structure in their Elixir system.</p>
<h2 id="elixir-and-opinions">Elixir and Opinions</h2>
<p>If you take the constraints brought by LiveView you will find that it is a sweet-spot for a solo or small team building a web-based SaaS. An Elixir-developer can thrive end-to-end in such a project and reap many of the intended benefits of the abstraction. Bumblebee from Nx sneaks in and starts to expand this picture for many machine learning needs as well.</p>
<p>Phoenix is not extremely heavy in opinions. At least not technically. We debate Contexts as a design decision and they are fundamentally quite open-ended. They don&rsquo;t impose constraints and they don&rsquo;t deliver much guidance.</p>
<p>I have seen a fair number of developers point to this lack of opinion as a problem. To them this is a wide open space that should be filled with opinions to give direction and achieve further gains. Other developers appreciate the space to do whatever they prefer. Some find the conceptual guidance on contexts enough to work off of.</p>
<p>I just recently watched Keathley&rsquo;s talk from last year and I appreciate his willingness to just collapse the layers at the controller. Some people will love it and some will hate it. A clear signifier of an opinionated approach and active choice.</p>
<p>If you want more direction and are willing to accept a lot of new opinion from elsewhere you can add Ash Framework to the stack. It provides constraint, guidance and plenty of opinion. It will fill out everything between Bandit and Ecto if you want it to and it brings ideas on how authentication, authorization and tenancy works. Ash is interesting in this regard because you will find people that really dislike the approach and people that are all in. This indicates a framework that puts forth a strong opinion.</p>
<p>You want to make trade-offs that cost you as little as possible. Technically, which means humanly.</p>
<p>And depending on your team you may find that some technical opinions and constraints land very differently. It seems that generally developers with less experience benefit from more clarity, guidance and opinion. They are still learning and making technical decisions is quite difficult and demanding from that vantage point. More experienced developers might find constraints they disagree with quite cumbersome and frustrating. They may have other strong opinions they&rsquo;d prefer to apply. They might have pointier and sharper trade-offs they want to make.</p>
<p>Whether you should let your experienced devs go wild in this regard is a .. team question.</p>
<h2 id="the-platform">The Platform</h2>
<p>I think this idea of low-cost trade-offs is also what makes Fly interesting to the Elixir community. If we already take on the trade-offs of LiveView and the general strengths of our platform, the BEAM, we know that latency matters. And latency has always mattered. Everywhere. The more we can cut down on latency the better our chosen trade-off with LiveView works. Fly&rsquo;s whole vision is to put a web app close to the user.</p>
<p>But of course that is not enough. Because your app is generally not only your application server. You at the very least have data and files.</p>
<p>Today most developers want a managed database and S3-compatible object storage. These have become table-stakes.</p>
<p>S3 made object storage a thing and is a prime example of a simple solution. And it blew away vast complexity that came before it. Typically NFS. Which was terrible. S3 gives a starkly limited API that enables the cloud provider behind it to scale essentially infinitely and offer great reliability. But one thing S3 does not offer is great latency, especially if you only store your files in us-east-1. Did I mention I live in Sweden?</p>
<p>So do you take on the complexity of adding a CDN for your files? For my sake?</p>
<p>The people I work with at Tigris created the object storage solution offered on Fly now. They are essentially collapsing the concerns of object storage and a CDN into one. They make the assumption that you want both. They offer geo-distribution by default, both on upload and download. That is their contribution to the vision. Aligning with the Fly proposition.</p>
<p>They offer a very simple service which does a lot for you. The complexity is hidden behind 18 years of progress since S3 launched as well as aligned trade-offs.</p>
<p>I tried to push them on what they are sacrificing to provide something more than S3. And they didn&rsquo;t feel there was a sacrifice. Which seemed suspicious. So I dug in. And I think I know why now. They are selling you something. Literally. The fundamental selling point of cloud-based object storage is that you do not have to worry about disk space and you don&rsquo;t have to worry about losing data. Infinite storage, great reliability. Offering this requires taking on severe operational complexity.</p>
<p>I do not want their job.</p>
<p>The work Tigris has done for low latency centers around FoundationDB which is very capable as a scalable metadata store. Most of the challenges with FoundationDB are operational. Tigris are doubling down on managing operational complexity. Aligned trade-offs can feel like no trade-offs, to them it is all the same. Keep stuff running well.</p>
<p>It seems object storage is handled.</p>
<p>The managed SQL database part on Fly is unsolved. And Fly has clearly tried to solve this a few ways indicating that, yes, this is important to them. Fly has a managed database via Supabase now and that&rsquo;s cool, good. Their old one is kinda gnarly so Supabase should be much better. But Supabase is not a magical geo-replicated database. Supabase is Postgres. It doesn&rsquo;t complete the vision.</p>
<p>Fly are trying some stuff with SQLite and LiteFS which is interesting. But I have worked a bit with the Electric SQL folks as well and I think their approach has a lot of potential for this geo-distributed concept. CRDTs are very powerful if you accept eventual consistency. And Electric SQL launched pglite, which has the eye of both Supabase and Neon. So we might see something interesting happen there. Postgres on every server perhaps? I did that as an experiment with their SQLite variant.</p>
<p>Some people have brought up CouchDB (the DB that did everything first, in Erlang) which is not SQL. Cassandra, which is closer to SQL from what I gather but not quite, Discord switched to ScyllaDB from that. And related to all this Phoenix will soon ship with <a href="https://x.com/lawik/status/1791389748243648628">CockroachDB support</a>. All of the options have fairly significant trade-offs but might be great for your workload. I think elasticity and reshaping the cluster is kind of high on the list to fit the Fly model or you need to run a node in every region you want low latency, continuously. This is not typically how these work.</p>
<p>So why does it matter if Fly and their partners pull this off? I think it makes for a very rare alignment of priorities and trade-offs from end to end. Platform, runtime, frameworks and language all sharing values and expressing a similar overall shape. Meaning Elixir would be unconstrained by the platform and the platform doesn&rsquo;t offer a bunch of complexity that is irrelevant to a typical Elixir app. This should give us trade-offs that cost as little as possible. You should be able to put a Phoenix-shaped app in the Fly-shaped hole without friction.</p>
<p>And hilariously good latency for users.</p>
<p>It matters because the goal-posts keep moving and the minimum expectations keep going up. What has worked up to here will struggle in the near future. The need to make consistent trade-offs and leverage them as much as possible is especially important to be able to work as small, highly performant teams.</p>
<p>It would be a true new Heroku but for Phoenix. I will leave the part about finding an aligned business model to replace the startup as a question for you all because I don&rsquo;t know what that looks like but I am very curious. I think indie hacking and open coops could both fit. But give me your suggestions.</p>
<p>I am not trying to sell Fly. They do that well enough. I am selling Tigris a little bit but I don&rsquo;t really need to. I have high hopes that Tigris will prove themselves out regardless.</p>
<p>I think the overall idea has interesting implications for the mainstream web SaaS side of the Elixir ecosystem. Other applications will need very different infrastructure. I really like working with Nerves which is quite different. I know Elixir performs hilariously well on bare metal and that&rsquo;s a different approach. It all depends one what you are building.</p>
<h2 id="build-or-rent">Build or Rent</h2>
<p>Personally I am always tempted to DIY anything and everything. I don&rsquo;t want to rent infrastructure. I want to build it. This is my desire for control, knowledge and the satisfaction of building something. In practice I typically go with something more sensible, more pragmatic. At work one of my constraints is that I do not have eternity to build everything. Instead I have deadlines. So I pick something where someone made choices and placed constraints. Even if I don&rsquo;t want to.</p>
<p>And the trade-offs of cloud services, AWS, Fly, Tigris, all of them, is to take operational complexity out of your hands for money. They offer a constrained API surface you can interact with to fulfil your needs. They will leverage the constraints their system places on you to achieve efficiencies in scale and manage that operational complexity for all their customers, all at once. Hopefully as a complex system that works.</p>
<p>Most of us are willing to pay some amount to remove operational complexity but how much and in what way varies a lot. There is a scale from the rented dedicated server to a Heroku-style platform. Sometimes it even involves Kubernetes.</p>
<p>It goes back to managing complexity. The demands on our software systems only increase. As we build more software in response to those demands we need to find ways to keep things as simple as possible. To raise the abstraction level of the work. Ideally we want to keep reaping rewards from trade-offs we&rsquo;ve already made.</p>
<p>What do you think the future holds in terms of &ldquo;stack&rdquo;? Do you know compelling end-to-end ecosystems or things that align trade-offs exceptionally well? I can be reached and argued with on via email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or fediverse <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Chatting, Sharing (&amp; Shots) in Chattanooga - GigCity 2024 report</title>
      <link>https://underjord.io/chattanooga-gigcityelixir-nervesconf-2024.html</link>
      <pubDate>Thu, 23 May 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/chattanooga-gigcityelixir-nervesconf-2024.html</guid>
      <description>I went to Chattanooga in Tennessee from Sweden to hang out with the Elixir community. I&amp;rsquo;ve been part of Elixir in various ways for 6-7 years now and this is the first time things have lined up and I could go to the US for a conference: I had a sponsor for the trip. I was offered to not just speak but keynote. The conference is put on by friends, Bruce and Maggie Tate, who&amp;rsquo;ve supported a lot of great stuff in the Elixir community for a long time.</description>
      <content:encoded><![CDATA[ <p>I went to Chattanooga in Tennessee from Sweden to hang out with the Elixir community. I&rsquo;ve been part of Elixir in various ways for 6-7 years now and this is the first time things have lined up and I could go to the US for a conference: I had a sponsor for the trip. I was offered to not just speak but keynote. The conference is put on by friends, Bruce and Maggie Tate, who&rsquo;ve supported a lot of great stuff in the Elixir community for a long time. And Todd Resudek decided to do NervesConf in conjunction. A bit of a perfect storm for me to head over. So I did. I want to unpack it.</p>
<p><em>This blog post, my trip to Chattanooga and the work on my keynote presentation was all supported and enabled by Tigris. They offer a new approach to object storage in collaboration with Fly.io and I think you should check them out. Just run the command and see how you like it:</em> <code>fly storage create</code></p>
<h2 id="travel">Travel</h2>
<p>From Sweden I took the train to Copenhagen a few hours south. Almost forgot my jacket with the passport in it on the way but we caught it just in time to go back and fetch it. Copenhagen to Amsterdam was no trouble. Amsterdam to Atlanta was pretty smooth as I&rsquo;d ponied up for extra leg space. Atlanta to Chattanooga was delayed so instead of midnight I arrived 02.00 (2 AM) which I didn&rsquo;t love. Bruce was kind enough to get me anyway. So I got to the Tate residence, was shown the guest room and slept the sleep of the dead. I had two good nights of sleep, with two days of doing a lot of very little. I hung out with good people. I ate things. This was good recovery before the conference and good social time with people I&rsquo;ve known for about 5 years or so but have not had a ton of time to hang with.</p>
<h2 id="setup">Setup</h2>
<p>The core concept of GigCity Elixir is hospitality.</p>
<p>Bruce and Maggie are graceful projections of that facet of southern culture. They work it as deep into their conference as they can. I felt very welcome, I know one attendee lost their place to stay, was going to not come and the Tates insisted they show up. That attendee took over the guest room I&rsquo;d had as I went to the Moxy hotel. They take care of people. And they proactively support and encourage a diverse set of folks.</p>
<p>In the days before I was given a lovely tour of Chattanooga, got vegan junk food and had a great time chilling and resting.</p>
<p>GigCity Elixir as an idea is arguably Bruce&rsquo;s baby. GigCity Elixir as executed is inarguably Maggie&rsquo;s doing. She is a force and a delight. What a human. In the days leading up to the conf she helped me shop for cool sneakers! Apparently she&rsquo;d already pinned down everything for the conference and could spend time kindly goofing off with me and showing me novel things to eat and drink.</p>
<p>If you want a sense of a small-scale event, put together well with a strong local connection. Consult them, visit their conference. See how they do it.</p>
<h2 id="day-1-nervesconf">Day 1: NervesConf</h2>
<p>With Maggie Tate logistics and a Todd Resudek schedule NervesConf became the start of my three conference days. Other people got some Ash training by Zach Daniels and Josh Price of Alembic.</p>
<p>Nerves is special to me. It was where I entered the community for real. I did Elixir, I did Phoenix. But I talked to people in Nerves much, much more. It was right-sized compared to the more busy Slack channels. Meeting a bunch of the people that I&rsquo;ve run into over the years around Nerves was such a thrill. And getting to dig deeper into topics I&rsquo;ve explored recently around security, boards and parts was thrilling. What a high-bandwidth and high-knowledge environment. I learned a ton and any question had experienced answerers right next to you.</p>
<p>Jon gave a very nice rundown of the recent work in the Nerves project. Some old faces spoke, some new faces spoke. All good talks. We had a talk from pojiro of the Nerves JP delegation (three people!) and I guess I brought in the European touch. We had Powell Kinney talking about the autonomous boat he&rsquo;d worked on. I shared my learnings and explorations around security from recent client work on the Raspberry Pi CM4 and the challenges therein. I think it was well received and I had a good time giving it. Alex Mclain gave a talk that should serve as a foundation for anyone who wants to build products with Nerves. There should be recordings for all of these that will come out eventually.</p>
<p>I can&rsquo;t imagine a friendlier and more chill group to present to. Darlings all of them and just a good vibe of people caring in a similar direction.</p>
<p>It was such a good time. I got some custom hardware (thanks Alex) and am now very keen to try and make some Nerves event happen over this side of the pond.</p>
<p>The only weirdness of the day was the storm preventing a bunch of people from arriving when they had planned. Many were re-routed, delayed or otherwise bothered by tornados, lightning and other fun flight-unfriendly phenomena.</p>
<p>A first late evening with drinks and friends.</p>
<h2 id="day-2-gig-city-elixir-starts">Day 2: Gig City Elixir starts</h2>
<p>I slept as long as I could without entirely missing breakfast.</p>
<p>A lot more people. I think NervesConf was 30-50 people. Gig City was up towards 150 I think. And a lot of friendly faces. Some I only knew the nickname, some I didn&rsquo;t recognize in 3D. Some were very familiar and nice to meet again.</p>
<p>Randall Thomas started us off well of course. His talks are always good energy, possibly bad feels, but good energy.</p>
<p>Many lovely conversations. Suprisingly tasty catering (Maggie again) with really good vegan options.</p>
<p>And we did my second appearance in the conference schedule. A <a href="https://www.beamrad.io/74">live episode of BEAM Radio</a>. We tapped some guests and people we knew to prepare hot takes. Then me and Bruce just invited people up on stage. One of them was Quinn. She blew the roof off of the conversation in terms of where it could go and then it went. And we brought in people from the audience, whoever wanted to bring something to the table. I hope the recording comes out really well because I am very happy with how it all came together and the discussions that were had. People went all kinds of ways and did it well. I hope we also make a video of it.</p>
<p>Bruce and I didn&rsquo;t have to do much, just keep shifting in new people. Thank you all who participated, I&rsquo;m proud of y&rsquo;all.</p>
<p>Another good evening. I restrained myself somewhat in preparation for my closing keynote the next day but still ended up going to bed a decent bit after midnight.</p>
<h2 id="day-3-gig-city-elixir-wraps">Day 3: Gig City Elixir wraps</h2>
<p>Another morning even more tightly optimized for sleep. I wanted to be in sane shape for speaking and have a chance of lasting all day. Some nerves in me for sure but I also felt very comfortable with the vibe of the whole event.</p>
<p>I didn&rsquo;t see a ton of talks, I picked some musts but also updated some slides that I&rsquo;d left open for what the conference would bring. I went off and rehearsed. And as I felt myself slipping into a deep tiredness I scampered away the 5 minutes to the hotel, took a long shower, a short nap and recovered a lot of energy. I headed back in time to see Chris Keathley speak. I&rsquo;ve followed the guy for so long and I find his thinking incredibly useful, informative and challenging, in the best way.</p>
<p>I went right after.</p>
<h2 id="preparing-for-my-talk">Preparing for my talk</h2>
<p>This was the hardest talk I&rsquo;ve given so far. In a keynote it didn&rsquo;t seem right to hide behind technical curiosities or trixy, trixy code. I wanted to talk about idea-level stuff, I wanted to touch on community and as per usual I wanted to produce something somewhat novel. I think I pulled it off. I would not be doing this stuff if I wasn&rsquo;t somewhat self-critical so there are plenty of things I could have done better. Better delivery, more practice, tighter slides, a bunch of things. But I&rsquo;m satisfied with how it went and I don&rsquo;t regret anything in particular.</p>
<p>I had an interesting constraint as I wanted the talk to touch on my sponsors field. It was not a constraint they placed but something that made sense to me. And I lined that up with things I&rsquo;ve been thinking about anyway with Fly and Elixir vs Heroku and Ruby. I will try to gather those ideas in a blog post as well, people did say they felt they clarified something.</p>
<p>You&rsquo;ll have the recording of the talk fairly soon.</p>
<h2 id="ending-it">Ending it</h2>
<p>I ended the conference. My keynote was the closer. Bruce and Maggie were brave enough to let that be the final note.</p>
<p>That&rsquo;s an experience. I recommend it. Go to some conferences and end them. It is quite fun :)
Get permission first. There is a whole schedule. Ending them willy-nilly will make people upset.</p>
<p>Talk done I felt empty and drained for a good 15-20 minutes and just lumbered around and got encouraging words from people. This often happens after I&rsquo;ve done something with a decent bit of build-up. A vacuum forms where nothing happens, intensely.</p>
<p>Then Flora (of Nerves puppetry fame) suggested shots so I tagged along out of the ending of the conference. Flora hustled a bunch of us off for tequila shots way too early in the day and then peaced out on an airplane. Boss move if you can pull it off I guess :)</p>
<p>Me and Josh (Price of Alembic) then went to Rock City which is a very cool mountain area tourist thing which the Tates and some other folks had planned a trip to. Josh accidentally acquired a Smith &amp; Wesson trucker cap. Andrea Leopardi compelled me to promise to start using <code>dbg</code> instead of <code>IO.inspect</code>.</p>
<p>The rest of my evening was good conversation at the Moxy with various people and sniping Frank and Jon of the Nerves core team into trying to get ChatGPT to make flails which it does notoriously poorly at.</p>
<h2 id="the-next-day">The next day</h2>
<p>Most people had left. Most people were from the states. Me and Josh Price went for a sunday hike with the Tates. An &ldquo;easy&rdquo; &ldquo;mile&rdquo; according to the initial announcement. Tennessee Easy? It was decidedly at least mediumly strenuous and significantly more than a mile. And very sunny compared to what I&rsquo;m used to. But we got to see just about all of Chattanooga from above and a long stretch of the Tenessee River. A lovely time.</p>
<p>Then we kept doing nice social things for the rest of the day. What lovely hosts and what lovely hospitality. Josh is also a really good travel-buddy.</p>
<h2 id="reflections">Reflections</h2>
<p>I&rsquo;m writing this on the flight home and my head is a bit tired, a bit worn. But there is a lot of idea-matter, a lot of inspirations and a lot stuff bubbling from this conference.</p>
<p>I like software development, Elixir and all that. But I am also quite interested in how people make things. How do you make a community? How do you make a conference? One of the best parts about engaging as a speaker or similar is that you have one foot behind the curtain. You get to see people&rsquo;s design thinking, people&rsquo;s people-skills and you get to engage with people who make things happen.</p>
<p>Much love to all I spoke to and interacted with, anyone and everyone who clapped after talks, anyone who was too shy or respectful to say hi, all that did say hi, those who mentioned something I&rsquo;ve done. Invigorating. All of it. And as much love again to everyone I hung out with, days and evenings. You all made the trip very worthwhile and I think many more good things will sprout from it.</p>
<p>I had the best time.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Unpacking Elixir - IoT &amp; Embedded with Nerves</title>
      <link>https://underjord.io/unpacking-elixir-iot-embedded-nerves.html</link>
      <pubDate>Fri, 26 Apr 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/unpacking-elixir-iot-embedded-nerves.html</guid>
      <description>In this part of the series we dive into one of the frameworks and toolsets that really got me going with both Elixir as a hobby and the community as a space full of helpful people. This will be all about The Nerves Project, oh, and touch on some related stuff.
Before we dig in I should shout out some other BEAM, Elixir &amp;amp; Erlang-related things in the embedded space. AtomVM seems super cool and lets you do Erlang and Elixir on microcontrollers.</description>
      <content:encoded><![CDATA[ <p>In this part of the series we dive into one of the frameworks and toolsets that really got me going with both Elixir as a hobby and the community as a space full of helpful people. This will be all about <a href="https://nerves-project.org/">The Nerves Project</a>, oh, and touch on some related stuff.</p>
<p>Before we dig in I should shout out some other BEAM, Elixir &amp; Erlang-related things in the embedded space. <a href="https://www.atomvm.net/">AtomVM</a> seems super cool and lets you do Erlang and Elixir on microcontrollers. ESP32s and such. I haven&rsquo;t tried it. I really should. There is also <a href="https://www.grisp.org/specs/">the GRiSP boards</a> which does interesting stuff to push Erlang&rsquo;s soft real-time performance closer and closer to hard real-time. It also works with Nerves if you prefer some Linux in the picture.</p>
<p>Erlang has been used for embedded Linux for a long time. The language started in the telco world which blurs with networking which blurs with industrial hardware. It all blurs with &ldquo;embedded linux&rdquo; which blurs with IoT and connected devices overall. It should not be surprising to hear that there are a decent number of people who have done custom linux + Erlang systems in the realm of embedded linux. I don&rsquo;t know of any that have established themselves as the tool of choice or built much of a following though.</p>
<p>I guess Frank Hunleth is one of those people. He did a bunch of embedded, mostly C and C++ before he started veering off into Erlang. Since Frank is a sweetheart we&rsquo;ve <a href="https://www.beamrad.io/9">had him on</a> the pod <a href="https://www.beamrad.io/64">twice</a>. I think the first one should give most detail on his professional history. When Elixir showed up there was more interest and more engagement there and Frank pivoted his IoT tooling plans towards where the collaboration and community felt most active. So Nerves was born into Elixir.</p>
<p>What is Nerves then?</p>
<p>It is Linux as mostly a hardware abstraction layer, source of drivers and OS fundamentals. Then it starts the BEAM and you take care of the rest from Elixir or Erlang. The linux variant is a custom Buildroot system. Buildroot is the less complicated of the major embedded Linux toolsets. Nerves offers a bunch of prebuilt systems adapter to particular hardware. So the Raspberry Pi 4 will have different drivers and configuration than the Beaglebone Black. It supports all the Raspberry Pi computers (not the Pi Pico microcontroller) which means that if you have one in a drawer, time to bring it out. You can also roll your own system if you know enough or are very willing to learn hardware and Linux.</p>
<p>The development process is just an Elixir project. Depending on how hardware-reliant your application is this can vary a bit. Nerves makes it all easy. Trivial SSH into the device. Pushing firmware over the network so you don&rsquo;t need to swap SD cards for every change. A/B partition updates to ensure it is very hard to bork your device in production. It really does have great tooling and a clean process for what is an involved type of work. And of course you can just paste code over SSH into iex. The best deployment process ;)</p>
<p>I got into Nerves by porting an eInk display driver from Python (which I knew) to Elixir (which I was learning). The Elixir Slack channel for #nerves was super helpful and friendly. At the time of writing I have been paid for Nerves work several times and am currently enjoying ongoing Nerves projects with clients. I really recommend trying some Nerves if you either enjoy tinkering with hardware OR have been looking for a reason to try Elixir.</p>
<p>Okay. So it boots the BEAM. And that&rsquo;s your systemd-equivalent or init system. Most of your system should be Elixir code with all the expressiveness, effortless performance, consistently low latencies and reliability that entails. Compared to anything I&rsquo;ve set up on a Raspberry Pi before it is also a wonder of determinism. Your deps get locked. It becomes a real software project, not just a bunch of random tutorials you run through.</p>
<p>There is also NervesHub which is an open source, self-hostable tool for managing fleets of Nerves devices. Firmware delivery, binary diffing for minimal updates, reverse consoles for troubleshooting. Authenticating devices based on hardware device keys, aka. a secure element, hardware root of trust (see <a href="https://hexdocs.pm/nerves_key/readme.html">NervesKey</a>).</p>
<p>Nerves helped me get into the Elixir ecosystem and community and it keeps giving me wonderful new opportunities to learn and explore. It really holds a special place for me. Part of that has been the immense generosity of Frank, Connor and the rest of the Nerves core team folks with both their time and knowledge. I&rsquo;ve learned so much and scratched many itches thanks to them. I hope to pay it forward both in software, time and effort.</p>
<p>There are already many examples of Nerves:</p>
<ul>
<li>SmartRent</li>
<li><a href="https://elixir-lang.org/blog/2023/03/09/embedded-and-cloud-elixir-at-sparkmeter/">SparkMeter</a></li>
<li>Rose Point (navigation)</li>
<li><a href="https://www.hashicorp.com/resources/connecting-farms-and-growing-plants-at-bowery-farming-with-nomad-and-consul">Bowery</a></li>
<li><a href="https://elixirmerge.com/p/gridpoints-use-of-elixir-and-nerves-for-energy-management">GridPoint</a></li>
</ul>
<p>Most of the companies using Nerves do so quietly. This seems to be a thing, especially in industrial automation.</p>
<p>The Nerves ecosystem covers a pretty reasonable amount of hardware with good Elixir-based drivers or libraries. In cases where they are missing the Circuits libraries provide all the necessary primitives with I2C, GPIO, SPI, UART. Elixir offers some really exceptional binary pattern matching and binary construction patterns that make working with hardware incredibly smooth.</p>
<p>I think Nerves stacks an incredibly reasonable set of practical solutions on top of each other and gives shape to what might other be hard to approach. If you are curious to learn about embedded linux I think just the abstractions and structure itself is helpful and if you experiment with it you will come away wiser.</p>
<p>Easiest ways to get started is with Nerves Livebook: <a href="https://github.com/nerves-livebook/nerves_livebook">https://github.com/nerves-livebook/nerves_livebook</a></p>
<p>I made a demo video for it a while back.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
<p>So go touch some circuitboards. Enjoy yourself.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Fundamentals of Object Storage</title>
      <link>https://underjord.io/fundamentals-of-object-storage.html</link>
      <pubDate>Tue, 19 Mar 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/fundamentals-of-object-storage.html</guid>
      <description>I did a livestream where I talked about Object Storage. The how and the why. The Bad Old Days. And also the neat and interesting stuff just beyond the basics. I figured I&amp;rsquo;d cover that in text as well.
You can watch the stream here.
Transparency notice: The livestream and this blog post have been work supported by Tigris. We collaborate on the topics, they provide resources and pay for some of my time so I can do this stuff instead of writing software for clients.</description>
      <content:encoded><![CDATA[ <p>I did a livestream where I talked about Object Storage. The how and the why. The Bad Old Days. And also the neat and interesting stuff just beyond the basics. I figured I&rsquo;d cover that in text as well.</p>
<p>You can <a href="https://youtube.com/live/Sg0Afr_4BMY">watch the stream here</a>.</p>
<p><em>Transparency notice: The livestream and this blog post have been work supported by <a href="https://www.tigrisdata.com/">Tigris</a>. We collaborate on the topics, they provide resources and pay for some of my time so I can do this stuff instead of writing software for clients. I am very glad and thankful that I have someone funding my publishing and creative ideas. Expect more output from me near term, and give them a try!</em></p>
<h2 id="background">Background</h2>
<p>When I cut my teeth architecting systems we didn&rsquo;t have Objects outside of Object-Oriented programming languages and we had precious little Storage. And if we did have Storage it was either on the same physical server or some kind of NFS-based abomination.</p>
<p>Imagine you set up your n-tier architecture. Application scales horizontally in front of the database. You can cache heavy loads with some Redis or Memcache that were the rad things then. For file storage we had to set up a shared store of some sort. And they were finicky bastards.</p>
<p>The mounts would come loose and files got written in a local folder. The performance would randomly degrade. I remember installing a filesystem cache thing for my servers and with NFS it would suddenly just hard-lock the system at the kernel level. Because NFS lives or at least lived then, in the kernel.</p>
<p>When it works well it can be very practical to have a networked filesystem pretend to be a real one. But it is a pretty little lie that you are telling yourself and the system you are building. And if you lean into the file-ish nature of the NFS lifestyle &hellip; well, there are risks. And scars you can get.</p>
<p>I found AWS annoying when it arrived. Unreliable VPS:es that you weren&rsquo;t supposed to put files on. Bah. Humbug. So it took a while before I got into Object Storage (all object storage is essentially S3-compatible these days). But much like the dumbness of EC2-instances enforced good scalability practices the simplicity of S3 made it tremendously effective.</p>
<p>What people want from a network file store is usually reliability and space. The data stays where you put it and does not get lost. And your data must fit. And as data grows it must still fit. So essentially infinite unknown unbounded storage growth. And ideally you don&rsquo;t want to pay more than disk utilization you currently need.</p>
<p>Simplicity, restraint and constraints are good for starting most things. But especially to delivering on ambitious things. Like infinite* file storage.</p>
<p>* not actually infinite, but for most purposes close enough</p>
<p>Object Storage is much more clearly a service. It is not a file system. And when you scratch the surface the name clarifies itself a bit. It is not necessarily about &ldquo;files&rdquo; either. It is just the most successful NoSQL DB of all time probably. Keys, values and it doesn&rsquo;t sweat the rest so much.</p>
<p>The fundamental operations are:</p>
<ul>
<li>Put object</li>
<li>Get object</li>
<li>List objects</li>
<li>Delete object</li>
</ul>
<p>The full list is <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Operations_Amazon_Simple_Storage_Service.html">much, much longer</a>.</p>
<h2 id="with-elixir">With Elixir</h2>
<p>To show how we can work with Object Storage fundamentals in Elixir I set up a mix project: <code>mix new bla</code>. Then I added the following deps in <code>mix.exs</code>:</p>

  <div class="code  elixir "  data-file="mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#66d9ef">defp</span> deps <span style="color:#66d9ef">do</span>
    [
      {<span style="color:#e6db74">:ex_aws</span>, <span style="color:#e6db74">&#34;~&gt; 2.5&#34;</span>},
      {<span style="color:#e6db74">:ex_aws_s3</span>, <span style="color:#e6db74">&#34;~&gt; 2.5&#34;</span>},
      {<span style="color:#e6db74">:hackney</span>, <span style="color:#e6db74">&#34;~&gt; 1.9&#34;</span>},
      {<span style="color:#e6db74">:sweet_xml</span>, <span style="color:#e6db74">&#34;~&gt; 0.7&#34;</span>},
      {<span style="color:#e6db74">:jason</span>, <span style="color:#e6db74">&#34;~&gt; 1.4&#34;</span>}
    ]
  <span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>I then created a <code>config/config.exs</code> with the following contents:</p>

  <div class="code  elixir "  data-file="config/config.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">config/config.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">import</span> <span style="color:#a6e22e">Config</span>

config <span style="color:#e6db74">:ex_aws</span>, <span style="color:#e6db74">:s3</span>,
  <span style="color:#e6db74">scheme</span>: <span style="color:#e6db74">&#34;https://&#34;</span>,
  <span style="color:#e6db74">host</span>: <span style="color:#e6db74">&#34;fly.storage.tigris.dev&#34;</span>,
  <span style="color:#e6db74">port</span>: <span style="color:#ae81ff">443</span></code></pre></div>
  </div>

<p>To get your deps: <code>mix deps.get</code></p>
<p>To create a bucket, if you have the <code>fly</code> command-line tool ready to go it is very simple to do <code>fly tigris create</code>. It is also free until your <strong>really</strong> use it, 5Gb and 10k+ requests/month. Of course take whatever bucket you prefer. But that will spit out credentials.</p>
<p><code>:ex_aws_s3</code> will automatically slurp up your credentials if you set them as environment variables but not the bucket name. If you need multiple buckets you can wrangle them explicitly. These examples assume one set of credentials.</p>
<p>I started the module I called <code>Tigris</code> like this:</p>

  <div class="code  elixir "  data-file="lib/tigris.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/tigris.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Tigris</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">ExAws.S3</span>

  <span style="color:#66d9ef">defp</span> bucket!, <span style="color:#e6db74">do</span>: <span style="color:#a6e22e">System</span><span style="color:#f92672">.</span>fetch_env!(<span style="color:#e6db74">&#34;BUCKET_NAME&#34;</span>)
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>Then I tackled the listing of objects:</p>

  <div class="code  elixir "  data-file="lib/tigris.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/tigris.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e">#..</span>
  <span style="color:#66d9ef">def</span> list!(prefix \\ <span style="color:#e6db74">&#34;&#34;</span>) <span style="color:#66d9ef">do</span>
    bucket!()
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">S3</span><span style="color:#f92672">.</span>list_objects(<span style="color:#e6db74">prefix</span>: prefix)
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">ExAws</span><span style="color:#f92672">.</span>request!()
    <span style="color:#f92672">|&gt;</span> then(<span style="color:#66d9ef">fn</span> %{<span style="color:#e6db74">body</span>: %{<span style="color:#e6db74">contents</span>: contents}} <span style="color:#f92672">-&gt;</span>
      contents
    <span style="color:#66d9ef">end</span>)
  <span style="color:#66d9ef">end</span>
<span style="color:#75715e">#..</span></code></pre></div>
  </div>

<p>You can try things in <code>iex -S mix</code> to get everything we have in this project compiled and ready to go in the prompt:</p>

  <div class="code  elixir "  data-file="iex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">iex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">iex(<span style="color:#ae81ff">1</span>)<span style="color:#f92672">&gt;</span> <span style="color:#a6e22e">Tigris</span><span style="color:#f92672">.</span>list!()
[]</code></pre></div>
  </div>

<p>Next some putting and getting. I am not suggesting you do error handling like this. This is for the purposes of simplicity and the stream, not a best practice and definitely not financial or legal advice.</p>

  <div class="code  elixir "  data-file="lib/tigris.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/tigris.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e"># ..</span>
  <span style="color:#66d9ef">def</span> put!(key, data) <span style="color:#66d9ef">do</span>
    bucket!()
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">S3</span><span style="color:#f92672">.</span>put_object(key, data)
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">ExAws</span><span style="color:#f92672">.</span>request!()

    <span style="color:#e6db74">:ok</span>
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> get(key) <span style="color:#66d9ef">do</span>
    result <span style="color:#f92672">=</span>
      bucket!()
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">S3</span><span style="color:#f92672">.</span>get_object(key)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">ExAws</span><span style="color:#f92672">.</span>request()

    <span style="color:#66d9ef">case</span> result <span style="color:#66d9ef">do</span>
      {<span style="color:#e6db74">:ok</span>, %{<span style="color:#e6db74">body</span>: body}} <span style="color:#f92672">-&gt;</span> body
      {<span style="color:#e6db74">:error</span>, {<span style="color:#e6db74">:http_error</span>, <span style="color:#ae81ff">404</span>, _}} <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">nil</span>
      {<span style="color:#e6db74">:error</span>, error} <span style="color:#f92672">-&gt;</span> {<span style="color:#e6db74">:error</span>, error}
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span>
<span style="color:#75715e"># ..</span></code></pre></div>
  </div>

<p>I tried those out in <code>iex</code> as well to show the fundamentals. Then I wanted to hint at scale I guess. <code>Task.async_stream/2</code> ensures things are run in plenty parallel according to your machine.</p>

  <div class="code  elixir "  data-file="lib/tigris.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/tigris.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e"># ..</span>
  <span style="color:#66d9ef">def</span> put_tons!(kv) <span style="color:#66d9ef">do</span>
    kv
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Task</span><span style="color:#f92672">.</span>async_stream(<span style="color:#66d9ef">fn</span> {key, value} <span style="color:#f92672">-&gt;</span>
      <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>puts(key)
      put!(key, value)
    <span style="color:#66d9ef">end</span>)
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Stream</span><span style="color:#f92672">.</span>run()
  <span style="color:#66d9ef">end</span>
<span style="color:#75715e"># ..</span></code></pre></div>
  </div>

<p>And then:</p>

  <div class="code  elixir "  data-file="iex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">iex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">iex(<span style="color:#ae81ff">1</span>)<span style="color:#f92672">&gt;</span> kv <span style="color:#f92672">=</span> <span style="color:#66d9ef">for</span> d <span style="color:#f92672">&lt;-</span> <span style="color:#ae81ff">1</span><span style="color:#f92672">..</span><span style="color:#ae81ff">100</span>, f <span style="color:#f92672">&lt;-</span> <span style="color:#ae81ff">1</span><span style="color:#f92672">..</span><span style="color:#ae81ff">100</span>, <span style="color:#e6db74">into</span>: %{}, <span style="color:#e6db74">do</span>: {<span style="color:#e6db74">&#34;dir-</span><span style="color:#e6db74">#{</span>d<span style="color:#e6db74">}</span><span style="color:#e6db74">/file-</span><span style="color:#e6db74">#{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">.txt&#34;</span>, <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">#{</span>d<span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">#{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>}
<span style="color:#75715e"># snip</span>
iex(<span style="color:#ae81ff">2</span>)<span style="color:#f92672">&gt;</span> <span style="color:#a6e22e">Tigris</span><span style="color:#f92672">.</span>put_tons!(kv)</code></pre></div>
  </div>

<p>And then to tidy up. Don&rsquo;t run this on buckets where you keep data you like or need:</p>

  <div class="code  elixir "  data-file="lib/tigris.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/tigris.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e"># ..</span>
  <span style="color:#66d9ef">def</span> exterminate! <span style="color:#66d9ef">do</span>
    stream <span style="color:#f92672">=</span>
      bucket!()
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">S3</span><span style="color:#f92672">.</span>list_objects()
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">ExAws</span><span style="color:#f92672">.</span>stream!()
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Stream</span><span style="color:#f92672">.</span>map(<span style="color:#f92672">&amp;</span> &amp;1<span style="color:#f92672">.</span>key)

    <span style="color:#a6e22e">S3</span><span style="color:#f92672">.</span>delete_all_objects(bucket!(), stream) <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">ExAws</span><span style="color:#f92672">.</span>request()

    <span style="color:#e6db74">:exterminated</span>
  <span style="color:#66d9ef">end</span>
<span style="color:#75715e"># ..</span></code></pre></div>
  </div>

<h2 id="presigned-urls">Presigned URLs</h2>
<p>No we are getting a bit fancy. So an enormously beneficial thing with Object Storage is that it can have features that spare your application server from a bunch of horrible work. Have you ever let your app receive a file upload only to save it somewhere else? Ludicrous! We have a service for that! There are protocols!</p>

  <div class="code  elixir "  data-file="lib/tigris.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/tigris.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e"># ..</span>
  <span style="color:#66d9ef">def</span> presign_get(key) <span style="color:#66d9ef">do</span>
    <span style="color:#e6db74">:s3</span>
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">ExAws.Config</span><span style="color:#f92672">.</span>new([])
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">S3</span><span style="color:#f92672">.</span>presigned_url(<span style="color:#e6db74">:get</span>, bucket!(), key, [])
  <span style="color:#66d9ef">end</span>
<span style="color:#75715e"># ..</span></code></pre></div>
  </div>

<p>Use that. Get a presigned URL (you can configure expiration and such) for either downloading or uploading which you can hand off to the client and let them deal with the upload without you. Just between them and your object storage. Fun thing about doing that with Tigris btw. Tigris will place the data globally close to the uploader. It will then replicate as a cache if needed to other regions but this means your customers in Australia get local latencies.</p>
<p>This is equally nice for uploads and downloads. Your application server does not have to go between. At worst a download is:</p>
<ul>
<li>Client asks your application server for a file.</li>
<li>Your application checks if the request makes sense and should be signed for.</li>
<li>Presign URL.</li>
<li>Client receives a response with a redirect to the presigned URL.</li>
<li>Transparent download but your application doesn&rsquo;t serve the bytes.</li>
</ul>
<h2 id="multipart-upload-streaming-up">Multipart upload (streaming up!)</h2>
<p>Up to 5Gb can be a single upload according to AWS. But typically that gets unwieldy. We can chunk uploads at 5Mb chunks.</p>

  <div class="code  elixir "  data-file="lib/tigris.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/tigris.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e"># ..</span>
  <span style="color:#66d9ef">def</span> put_file!(key, from_filepath) <span style="color:#66d9ef">do</span>
    from_filepath
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">S3.Upload</span><span style="color:#f92672">.</span>stream_file()
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Stream</span><span style="color:#f92672">.</span>map(<span style="color:#66d9ef">fn</span> chunk <span style="color:#f92672">-&gt;</span>
      <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>puts(<span style="color:#e6db74">&#34;uploading...&#34;</span>)
      chunk
    <span style="color:#66d9ef">end</span>)
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">S3</span><span style="color:#f92672">.</span>upload(bucket!(), key)
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">ExAws</span><span style="color:#f92672">.</span>request!()
  <span style="color:#66d9ef">end</span>
<span style="color:#75715e"># ..</span></code></pre></div>
  </div>

<p>This is a form of streaming upload. Which means if you need to do processing and then want to offload the result immediately you can reduce your memory usage to about 5Mb (it depends) by streaming the upload instead of holding on to the whole beastly thing. This specifically builds a Stream from a file for upload but there are many variants you can do using the Elixir Stream and IO tools.</p>
<h2 id="range-requests-streaming-down">Range requests (streaming down!)</h2>
<p>Sometimes we just want a few parts of a file. Sometimes we want to build a hell-beast that does read-only SQLite VFS over S3 API. Sometimes we want to do a graceful streaming download. For this we have straight up HTTP range requests.</p>

  <div class="code  elixir "  data-file="lib/tigris.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/tigris.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e"># ..</span>
  <span style="color:#66d9ef">def</span> get(key, range \\ <span style="color:#66d9ef">nil</span>) <span style="color:#66d9ef">do</span>
    opts <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">if</span> range <span style="color:#66d9ef">do</span>
        [<span style="color:#e6db74">range</span>: <span style="color:#e6db74">&#34;bytes=</span><span style="color:#e6db74">#{</span>range<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>]
      <span style="color:#66d9ef">else</span>
        []
      <span style="color:#66d9ef">end</span>

    result <span style="color:#f92672">=</span>
      bucket!()
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">S3</span><span style="color:#f92672">.</span>get_object(key, opts)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">ExAws</span><span style="color:#f92672">.</span>request()

    <span style="color:#66d9ef">case</span> result <span style="color:#66d9ef">do</span>
      {<span style="color:#e6db74">:ok</span>, %{<span style="color:#e6db74">body</span>: body}} <span style="color:#f92672">-&gt;</span> body
      {<span style="color:#e6db74">:error</span>, {<span style="color:#e6db74">:http_error</span>, <span style="color:#ae81ff">404</span>, _}} <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">nil</span>
      {<span style="color:#e6db74">:error</span>, error} <span style="color:#f92672">-&gt;</span> {<span style="color:#e6db74">:error</span>, error}
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span>
<span style="color:#75715e"># ..</span></code></pre></div>
  </div>

<p>This is incredibly useful and powerful. Sure. You might mostly be dealing with files. Until you don&rsquo;t. Or until you realize how many file formats expose useful information with just the right bytes.</p>
<p>Zip archives are streamable with <a href="https://github.com/evadne/packmatic">Packmatic</a> because they allow you to keep the index of files to decompress at the end of the archive. That&rsquo;s a known location. We can search the last part of the file and only get the index, and so, the file listing. And that means we can pick out files. One can do similar things with ID3 tags on MP3s and get metadata without getting the whole file.</p>
<p>If you use the Elixir library <a href="https://hex.pm/packages/explorer">Explorer</a> the underlying Rust library supports the S3 API and will let you lazily read data from .parquet files and such that are stored remotely. Same thing.</p>
<p>I hope this clarifies why range requests are rather useful for a file storage service.</p>
<h2 id="why-the-s3-api-became-standard">Why the S3 API became standard</h2>
<p>All Object Storage services I&rsquo;ve used so far have exposed an S3-compatible API. It makes sense. It helps you support the expected featureset and you get a trillion clients and SDKs compatible with your thing for free.</p>
<p>Is the S3 API just that good?</p>
<p>I don&rsquo;t think it is. And I had a whole spiel here about how it is simply good enough and that the value proposition of reliable &ldquo;infinite&rdquo; storage with a common protocol makes it worth it. It clearly has been. In discussing this post with Ovais Tariq, CEO of Tigris, he actually shared a much more interesting viewpoint. Contrarian to my lukewarm take. It makes sense for Tigris to ship as S3 API compatible to make switching and getting started simple. The S3 API turns out to be a significant constraint however. Tigris has a metadata system and infrastructure that is different from S3. There are improvements, innovations and some really great features that are not easy to implement nicely in the fairly stagnant S3 API. Sure you can finagle features into headers and other clever stuff but there is a downside to it as any new ground you break will be missing in client implementations. From my conversations with Ovais and his team we will still see innovations on Object Storage from them. I guess we&rsquo;ll have to stay tuned to see if the S3 API can handle it.</p>
<hr>
<p>If you want to share some of the more interesting usages you&rsquo;ve seen of Object Storage or just want to tell me about whether you found this helpful, feel free to reach out. I am on the Fediverse as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> and you can just email me as well at <a href="mailto:lars@underjord.io">lars@underjord.io</a>. Thanks for reading.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Challenges of local-first LLMs</title>
      <link>https://underjord.io/challenges-of-local-first-llms.html</link>
      <pubDate>Fri, 08 Mar 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/challenges-of-local-first-llms.html</guid>
      <description>TLDR: the tricky part is &amp;ldquo;Large&amp;rdquo;. &amp;ldquo;Language&amp;rdquo; and &amp;ldquo;Model&amp;rdquo; seems manageable. But largeness has all sorts of trouble associated with it for mobile devices. Not insurmountable, but challenging. Also makes for finicky development.
To start off, I am not an AI expert. I am not an ML engineer. I&amp;rsquo;m a web and systems dev that mostly works in Elixir these days. Through the Elixir community I got connected with ElectricSQL and have done some collaboration with them.</description>
      <content:encoded><![CDATA[ <p><em>TLDR: the tricky part is &ldquo;Large&rdquo;. &ldquo;Language&rdquo; and &ldquo;Model&rdquo; seems manageable. But largeness has all sorts of trouble associated with it for mobile devices. Not insurmountable, but challenging. Also makes for finicky development.</em></p>
<p>To start off, I am not an AI expert. I am not an ML engineer. I&rsquo;m a web and systems dev that mostly works in Elixir these days. Through the Elixir community I got connected with <a href="https://youtu.be/_U5Z8AQy0hc?si=_0PPJmDwV91CMVLN">ElectricSQL</a> and have done some collaboration with them. <em>This post is done as part of my work with them and is paid work. I let them read it before publishing but this is all me and my perspectives. They have kindly paid me to endure JavaScript and experiment with their tools a bit :)</em></p>
<p>ElectricSQL are firmly in the local first ecosystem. Their open source project/product gives you a way to get active-active replication with eventual consistency between a server-side Postgres and a wherever-you-want SQLite. You handle the data model from the Postgres side with your normal tooling. Whenever your clients have a chance to reach the sync service/postgres proxy you&rsquo;ll get schema updates and whatever new data the server might have to your local thing. Anyway. Cool stuff. On to the local first inference!</p>
<p>I really am not enjoying React Native here. Part of that is almost definitely that I&rsquo;m poking around the immature ragged edge of what people have launched, like <a href="https://github.com/mybigday/llama.rn">llama.rn</a> (based on <a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a>) and <a href="https://github.com/hans00/react-native-transformers-example">the outdated example for transformers.js</a>. There are no real paths and I&rsquo;m not deep enough on mobile dev to patch up the holes well myself. I also don&rsquo;t know enough C/C++ to ship custom sqlite&rsquo;s with vector extensions and whatnot. Might get there but am not there currently.</p>
<p>I know there are people who have made models run locally. I am not blazing a trail. But it is also not particularly mature as a space.</p>
<p>So the current approaches I see for getting LLMs to work on phones is mostly quantization which to my understanding is the process of reducing the precision of the numbers and calculations to save heavily on space and memory usage. Taking <a href="https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF">Llama2 7B</a> you have this 13.5Gb model at 16bit floating point. Quantizing it with llama.cpp down to &ldquo;Q2 K&rdquo; I believe uses 2bit integers. Pretty lossy. But it brings it down to 2.6Gb and it can actually be run on a modern iPhone (mine is a 13 Pro). With infinite time I would explore Mistral 7B as an option along with Orca variants and whatnot. There is a lot out there.</p>
<p>Now there are also efforts to rework models to be smaller fundamentally and then you have something like <a href="https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF">TinyLlama-1.1B</a> (<a href="https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v0.3-GGUF">GGUF</a>). This model is miniscule (relatively speaking) at 747Mb. I tried the 0.3 version at one point which was generating more nonsense than sense, wrong language, pure gibberish and more. The 1.x ones seems much more coherent though it certainly makes mistakes frequently. It is plenty snappy.</p>
<p>The crunched up Llama has produced semi-relevant responses for me but it has also been much harder to run. A 731Mb asset is perhaps on the larger side. But whether it goes over wifi, or the lightning cable at USB 2.x speeds, <code>npx react-native@latest run-ios</code> is not impressively fast when slinging a 2.6Gb Llama over the wire (Nullsoft would have loved this era of computing. WinAmp should return as an LLM I guess. It would really whip ..).</p>
<p>And I&rsquo;ve had the phone go very warm using Llama2 models. And then pause all audio playback as background tasks stop getting priority. The CPU, GPU and probably some Neural Engine gets busy doing the hard work of guessing the next word and your phone becomes warm and inert. Not a great experience. But also kind of fun to actually see what this device can do.</p>
<p>Overall, my sense is that the quantized models are ahead of the built-to-be-small models. I think those approaches can converge and lead to some useful stuff. With enough persistence you can probably already wrangle some good usage out of them.</p>
<p>A challenge with these large models is of course that every app that wants to use one ends up becoming essentially a full-on mobile game size. And they aren&rsquo;t very fast. I think there might be a case for an app that manages models for people and provides practical on-device APIs to interact with. To my knowledge this can be done in a few ways under iOS specifically. You can do a bunch of hacking around with App URL schemes. Your model service app can expose Shortcuts that the user can then base integrations on, a real power user thing. And it seems you could build an <a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Action.html">Action Extension</a>.</p>
<p>Essentially this is the revival of the shared library. Assuming people do want LLM-sized models doing things on their phones it will quickly become unpleasant that every app ships their own Llama or Mistral. This might be resolved if Apple (and Google on Android) exposes their own on-device models that cover the desirable use-cases. We will find out.</p>
<p>I haven&rsquo;t gotten into it but I want to try some <a href="https://www.tensorflow.org/lite">TensorFlow Lite/TFLite</a> models of various types as that seems well suited for phone-level devices. I just recently ran into <a href="https://github.com/mrousavy/react-native-fast-tflite">this library for React Native</a>. Like any React Native library I assume it will be torture to get going but has the potential to solve all my problems. Qualcomm recently released 80-or-so edge/mobile-friendly model variants <a href="https://huggingface.co/qualcomm">on HuggingFace</a> so that&rsquo;s a good place to look if you want some TFLite to toy with.</p>
<p>The work on this is paused for the foreseeable future but high on my list was to try more things with vector embeddings. Those seem perfectly feasible to generate on a phone. The <a href="https://huggingface.co/qualcomm/OpenAI-Clip">TFLite version of OpenAI CLIP</a> is 304MB.</p>
<p>I think there are many things that can be done and if you have some pain-tolerance you can already run a local model. It is kind of fun and a good challenge to get real use out of them. I certainly like the idea of not relying on cloud to achieve these things.</p>
<p>Now one reason to pause this is that the ElectricSQL folks have already made <a href="https://electric-sql.com/blog/2024/02/05/local-first-ai-with-tauri-postgres-pgvector-llama">some good progress</a> with other parts of local-first ML/AI experiments. Postgres, pg_vector, Tauri and Llama2. Who needs React Native, really? They also shortly after launched a very exciting collab with the Neon Postgres folks: <a href="https://github.com/electric-sql/pglite">pglite</a></p>
<p>You might have seen pglite if you frequent Hacker News and such. This would take the pg_vector opportunities further. An embeddable Postgres, eh? Not bad :)</p>
<p>There is a repo from my experiments on my github if you go digging, it may be incomplete and not build. It may have junk in it. It is not a place of honor. Overall I think I explored some good stuff but most paths definitely hit a &ldquo;oof, this is too rough&rdquo; end. Trying to get SQLite with VSS was hairy. Trying to get Shortcuts working of all things proved incredibly frustrating. Realizing Transformers.js is missing stuff to make it work on React Native.</p>
<p>Overall, if you want to get into this. I think I&rsquo;d recommend doing it from Swift/Kotlin. The natives will save you from one layer of fragile indirection while you explore the bleeding edge.</p>
<p>Thanks for reading. If you have questions or concerns, hit me up <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> for the Fediverse/Mastodon or via <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Unpacking Elixir: Phoenix</title>
      <link>https://underjord.io/unpacking-elixir-phoenix.html</link>
      <pubDate>Mon, 22 Jan 2024 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/unpacking-elixir-phoenix.html</guid>
      <description>In this series I&amp;rsquo;ve been unpacking various facets of Elixir. Mostly this has meant trying to explain Erlang and the BEAM through the lens of Elixir. Now we are moving into the domain of the web framework. This is where I dare say that Elixir has much more to say than Erlang. Erlang has to my understanding never landed fully on a canonical preferred web framework. Elixir has Phoenix and this post will be unpacking Phoenix.</description>
      <content:encoded><![CDATA[ <p>In this series I&rsquo;ve been unpacking various facets of <a href="https://elixir-lang.org/">Elixir</a>. Mostly this has meant trying to explain Erlang and the BEAM through the lens of Elixir. Now we are moving into the domain of the web framework. This is where I dare say that Elixir has much more to say than Erlang. Erlang has to my understanding never landed fully on a canonical preferred web framework. Elixir has <a href="https://www.phoenixframework.org/">Phoenix</a> and this post will be unpacking Phoenix. The Elixir web framework.</p>
<p>As for Erlang <a href="https://github.com/uhub/awesome-erlang">this Awesome Erlang list</a> has a ton of web frameworks. There have been many but I have never detected a consensus on what one &ldquo;should&rdquo; use. Actually, when I spoke to <a href="https://www.wikidata.org/wiki/Q107596747">Robert Virding</a> over beers at a conference I asked something about this and he more or less said that Elixir and Phoenix should be the preferred web framework for the BEAM. The exact question and the exact answer are muddled by time and memory. My understanding is that he much prefers Erlang for everything else but really wishes that people would just use LfE ;)</p>
<p>This should not be taken as a criticism of Erlang, rather a kudos to Elixir for establishing and maintaining this useful cohesion. The Elixir ecosystem has always had a fair bit of focus on the web in a way which Erlang has not.</p>
<p>People have built other web frameworks in Elixir. Phoenix remains the major player. It is the Rails/Django/Laravel of the Elixir ecosystem though I would argue it is more lightweight. In good ways and possibly bad.</p>
<h2 id="plug">Plug</h2>
<p>We shouldn&rsquo;t start on Phoenix. That&rsquo;s the high-level framework. The fundamentals of dealing with web requests and creating responses are handed to <a href="https://hexdocs.pm/plug/readme.html">Plug</a>. Plug deals with headers, bodies, query params, URLs, paths and all of that by providing the concept of composable plugs. A very simple plug:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.NotFoundPlug</span> <span style="color:#66d9ef">do</span>
	<span style="color:#f92672">alias</span> <span style="color:#a6e22e">Plug.Conn</span>

	<span style="color:#66d9ef">def</span> init(opts) <span style="color:#66d9ef">do</span>
		opts
	<span style="color:#66d9ef">end</span>

	<span style="color:#66d9ef">def</span> call(conn, opts) <span style="color:#66d9ef">do</span>
		conn
		<span style="color:#75715e"># Send a 404 to the client</span>
		<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Conn</span><span style="color:#f92672">.</span>send_response(<span style="color:#ae81ff">404</span>, <span style="color:#e6db74">&#34;Not found&#34;</span>)
		<span style="color:#75715e"># Do no further processing of this plug pipeline</span>
		<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Conn</span><span style="color:#f92672">.</span>halt()
	<span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>Plug also provides a module for routing and building up routes based on path and method. This example is from the docs.</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyRouter</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">Plug.Router</span>

  plug <span style="color:#e6db74">:match</span>
  plug <span style="color:#e6db74">:dispatch</span>

  get <span style="color:#e6db74">&#34;/hello&#34;</span> <span style="color:#66d9ef">do</span>
    send_resp(conn, <span style="color:#ae81ff">200</span>, <span style="color:#e6db74">&#34;world&#34;</span>)
  <span style="color:#66d9ef">end</span>

  forward <span style="color:#e6db74">&#34;/users&#34;</span>, <span style="color:#e6db74">to</span>: <span style="color:#a6e22e">UsersRouter</span>

  match _ <span style="color:#66d9ef">do</span>
    send_resp(conn, <span style="color:#ae81ff">404</span>, <span style="color:#e6db74">&#34;oops&#34;</span>)
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>A plug which does not halt continues to the next plug specified. This means every step of authentication, authorization, parsing and so on can be layered. Similar to a middleware approach. They all work with the <code>Plug.Conn</code> struct.</p>
<p>Plug is a rather elegant way of getting web stuff done. Good building blocks. I stole some code from Frank Hunleth and Connor Rigby of the Nerves project when I wanted to do <a href="https://underjord.io/live-server-push-without-js.html">my live visitor counter in an image</a> (also <a href="https://github.com/lawik/mjpeg/blob/master/lib/mjpeg.ex">the Plug</a> and <a href="https://github.com/lawik/mjpeg_example">the project</a>) to do chunking and produce an MJPEG. It didn&rsquo;t need the rest of Phoenix. A good case for straight Plug. But also, a Phoenix Controller is just a Plug and could do it just as well.</p>
<h2 id="web-servers">Web servers</h2>
<p>Historically Phoenix has leaned on a web server called <a href="https://ninenines.eu/docs/en/cowboy/2.10/guide/">Cowboy</a> written in Erlang. It has been a very reliable workhorse for a long time and has done well in that role. It connects to Plug via the <code>plug_cowboy</code> library. Increasingly I see projects pick up <a href="https://hexdocs.pm/bandit/Bandit.html">Bandit</a> which is intended to be a replacement written in Elixir. This allows the community a lower barrier to contribution as more people in the Elixir space know Elixir than Erlang. There is more to it as well. We covered some of that in <a href="https://www.beamrad.io/53">an episode of BEAM Radio</a> if you are curious. Supposedly Bandit also benchmarks as a bit faster than Cowboy which is of course a nice perk.</p>
<p>Something these web servers have in common is that they are not your Ruby or Python application web servers. No reverse proxy required unless you want one. They can actually be trusted to do real frontline work. Erlang was built for it.</p>
<h2 id="phoenix">Phoenix</h2>
<p>Now I think we can tackle Phoenix. Ah yes, the &ldquo;Rails of Elixir&rdquo;. But not nearly so similar. Now I don&rsquo;t have any significant experience with Rails but I talk to people who do. I have experience in Django. Both Django and Rails work hard to help the developer be productive for the common case. The Pareto Principle, 80/20 rule, all that. And beyond.</p>
<p>In Django this is achieved through &ldquo;magic&rdquo;. Mostly inheritance of classes that introspect what you put in them and do things based off of that. They also take metaprogramming very literally and you end up putting a bunch of things in a class-within-a-class called Meta as I recall.</p>
<p>These are super-dynamic languages where monkey-patching and other fun stuff is incredibly available. This of course means you should limit how much you use this fun stuff as much as possible. A code-base without discipline can get very messy on top of these languages and frameworks.</p>
<p>Phoenix tries not to rely on &ldquo;magic&rdquo;. We call them <a href="https://hexdocs.pm/elixir/macros.html">macros</a> instead.</p>
<p>I kid. Macros are among the more confusing parts of Phoenix as well as Plug but they are generally there to manage some inherent complexity for you and they are much of the time still in &ldquo;your&rdquo; code.</p>
<p>Typically you start a Phoenix project using <code>mix phx.new my_app</code> which generates a project that you then own. Sure, you have dependencies, the code of which you don&rsquo;t own, but your MyAppWeb module has macros for bringing in the necessary functions for Controllers or LiveView and you can adapt that to your way.</p>
<p>I&rsquo;ve heard multiple people go &ldquo;that&rsquo;s a lot of files&rdquo; when generating a Phoenix project and I agree, that&rsquo;s the impression you&rsquo;ll get. But most of the files have fairly clear purpose once you get to know them and they are there to make things explicit and hand you the reins instead of mysteriously and magically inheriting things at you. There are also hygiene things like gettext that you might not use in your first few projects that are there because they just ought to include it. And you&rsquo;d be pissed if it was not there when you needed it.</p>
<h3 id="opinionated-design-or-a-lack-thereof">Opinionated design, or a lack thereof</h3>
<p>We often talk about opinionated design in web frameworks. The reason is generally that an opinionated design makes significant trade-offs for some cases in order to support the common case. Pareto principle, 80/20 rule, all that, again. By providing an opinionated design you eliminate the need for many decisions and ideally provide well-proven good-enough solutions or at least helpful simplifications.</p>
<p>Phoenix is not deeply opinionated. I think comes with the developers driving Phoenix had more experience and wanted to focus on good primitives. Also, I Functional Programming achieves abstraction quite differently from the OOP style of Python and Ruby. FP tends to achieve complex things with simple parts.</p>
<p>Erlang is incredibly opinionated at a fundamental level. It makes a ton of choices in the service of building services and Elixir inherited those opinions. We&rsquo;ve traded off a number of things we don&rsquo;t care about to get a fantastic foundation for a web framework. We get trivial concurrency and parallelism but have traded off small binaries and number crunching. We have consistent latencies but don&rsquo;t get the speed of mutable state.</p>
<p>I started out considering Phoenix as an opinionated framework in the vein of Django. I don&rsquo;t know what gave me that idea aside from them both being web frameworks. Sure, it brings in some opinions such as &ldquo;specifying routers in a central place is nice&rdquo; and &ldquo;this is how you should bring in your helper functions for doing controllers&rdquo;. It also abstracts away connection pools and supervision trees and there are opinions enshrined there but that&rsquo;s usually not what people mean when talking about opinionated framework designs.</p>
<p>If you come from Django or Rails you will likely miss the extremely convenient derive-everything-from-the-model typ of schema-driven development. If you want more of that the currently most interesting option is <a href="https://ash-hq.org/">Ash Framework</a> which is very opinionated and quite fascinating.</p>
<p>There are some opinionated parts outside the core of Phoenix&rsquo;s web capabilities. Channels is an opinion on top of WebSockets, Presence as well. Phoenix LiveView is heavily opinionated and makes fairly aggressive trade-offs for great wins in productivity.</p>
<p>Phoenix also brings in <a href="https://hexdocs.pm/ecto/Ecto.html">Ecto</a> by default and Ecto is a fairly opinionated approach to relational databases. <a href="https://hexdocs.pm/ecto/Ecto.Changeset.html">Changesets</a> and the <a href="https://hexdocs.pm/ecto/Ecto.Query.html">Query DSL</a> are both quite flexible but they push you towards a particular way of working that the library believes is best.</p>
<p>The most contentious part of Phoenix seems to be <a href="https://hexdocs.pm/phoenix/contexts.html">Contexts</a>. Which is the connective tissue between a Controller, Channel or LiveView and the Ecto-driven data layer. There is an approach generated by default but it is much discussed and will generally require you to apply your own designs and ideas. It it very open ended. There is no special Context code. There is an opinion coming from the Phoenix generators but it seems very softly held and it is not really in the bones of the framework.</p>
<p>Overall I think Phoenix has been layered well and I find this restraint of opinion in the base framework means that there isn&rsquo;t a big desire for a lightweight microframework alternative to Phoenix (see Flask, FastAPI in Python) because if you just ask it to not add a database or skip the HTML bits or whatever configuration you need you will get your minimal API service or you full-trim HTML-spitting machine.</p>
<h2 id="the-phoenix-feature-set">The Phoenix feature set</h2>
<p>I will try to capture the things that make up Phoenix. Major features as it were.</p>
<h3 id="project-generation">Project generation</h3>
<p>Phoenix does a bit of inversion of ownership and as I&rsquo;ve mentioned, produces a bunch of files that you can then own and evolve. I run <code>mix phx.new</code> on something like a weekly basis as I try a new hack of some sort. The generator has a bunch of options, choose database (postgres, mysql, sqlite) or skip Ecto and database details entirely. HTML or not. LiveView or not. Assets or not. This is the normal starting point.</p>
<p>There are other starter templates. Legendary, Petal Pro and some others. I can&rsquo;t vouch for them. I&rsquo;ve worked on a Petal Pro project, it was fine, it certainly brought more opinion around templating and layouts.</p>
<h3 id="ecto">Ecto</h3>
<p>The database layer. Not locked to Phoenix in any particular way but it does ship by default.</p>
<p>Ecto provides relational database functionality to your app and stops you from making a bunch of mistakes that could lead to SQL injection attacks and the like. This informs Ecto&rsquo;s design in unusual ways. It relies a decent bit on compile-time macros for building queries. There are also escape hatches pretty much wherever you might need them.</p>
<h4 id="repos-ectorepohttpshexdocspmectoectorepohtml">Repos (<a href="https://hexdocs.pm/ecto/Ecto.Repo.html">Ecto.Repo</a>)</h4>
<p>A repo is an abstraction over a supervision tree that manages database connection pooling. A typical app deals with one Repo named <code>MyApp.Repo</code> and it provides all the query functions and such.</p>
<p>If you are dealing with two separate databases you can easily set up and additional repo. And if you are dealing with multi-tenancy or some other multi-database situation you have &ldquo;dynamic repos&rdquo; functionality which will let you work that way as well.</p>
<h4 id="schemas-ectoschemahttpshexdocspmectoectoschemahtml">Schemas (<a href="https://hexdocs.pm/ecto/Ecto.Schema.html">Ecto.Schema</a>)</h4>
<p>The Schema module exposes a DSL for specifying database tables, mostly. It can also be used to specify schemas that are not backed by a database for various purposes. But mainly, this correlates with Django or Rails models while being significantly less magical in nature. They boil down to Elixir Structs.</p>
<h3 id="changesets-ectochangesethttpshexdocspmectoectochangesethtml">Changesets (<a href="https://hexdocs.pm/ecto/Ecto.Changeset.html">Ecto.Changeset</a>)</h3>
<p>A way to define validation rules for schemas (and other data). This will help you produce good errors, integrate actual errors returned from the database gracefully and many other things. Changesets are used in and around inserts and updates. They are a large topic and well worth reading up on because they are quite and interesting and useful design.</p>
<h3 id="web-stuff">Web stuff</h3>
<h4 id="the-endpoint-myappwebendpointhttpshexdocspmphoenixphoenixendpointhtml">The Endpoint (<a href="https://hexdocs.pm/phoenix/Phoenix.Endpoint.html">MyAppWeb.Endpoint</a>)</h4>
<p>This module is generated for you but when added to your supervision tree it starts a Phoenix Endpoint which contains a supervision tree for starting any Phoenix-owned process. Whether you use the Plug integration for Cowboy or Bandit it will set up your web server to listen for inbound requests and route them to Plug and the Endpoint. The Endpoint defines a bunch of fundamental plugs and config. It then typically defers to the Router for further handling of requests.</p>
<h4 id="router-myappwebrouterhttpshexdocspmphoenixphoenixrouterhtml">Router (<a href="https://hexdocs.pm/phoenix/Phoenix.Router.html">MyAppWeb.Router</a>)</h4>
<p>The router module is where you use a combination of Phoenix and Plug plugs to handle requests and delegate them to Controllers and LiveViews. It is a nice central place for structuring this and also allows you to define pipelines for other plugs that need to be applied, such as checking authentication and authorization.</p>
<h4 id="myappweb">MyAppWeb</h4>
<p>This holds your macros for controllers, channels and liveviews. These macros mostly bring in other Phoenix functionality but they are in your file and you can and should use them as an extension point for bringing in your own tools.</p>
<h4 id="controller-myappwebmypagecontrollerhttpshexdocspmphoenixphoenixcontrollerhtml">Controller (<a href="https://hexdocs.pm/phoenix/Phoenix.Controller.html">MyAppWeb.MyPageController</a>)</h4>
<p>A controller handles a request. The Controller might produce JSON API responses, HTML, file chunks or whatever else. Doesn&rsquo;t matter. If you are rendering HTML you get into templating and components. A controller is actually also just a Plug.</p>
<h4 id="templating-heexhttpshexdocspmphoenixcomponentshtml">Templating (<a href="https://hexdocs.pm/phoenix/components.html">Heex</a>)</h4>
<p>Heex is an evolution of the regular Eex templating that ships with Elixir. Heex is HTML-aware and will tell you when you screw up your closing tags. It also has nice syntax for arguments beyond basic string interpolation and such.</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">def</span> render(assigns) <span style="color:#66d9ef">do</span>
	<span style="color:#e6db74">~H</span><span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">		&lt;div :for={thing &lt;- @items} :if={thing.great?} class={@myclass}&gt;
</span><span style="color:#e6db74">			&lt;.custom_component thingie={thing} /&gt;
</span><span style="color:#e6db74">		&lt;/div&gt;
</span><span style="color:#e6db74">	&#34;&#34;&#34;</span></code></pre></div>
  </div>

<h3 id="innovations">Innovations</h3>
<p>I want to shine some light on the bits that I think offer capabilities beyond what we typically see in web frameworks or that do things better than most web frameworks.</p>
<h4 id="pubsub-phoenixpubsubhttpshexdocspmphoenix_pubsubphoenixpubsubhtml">PubSub (<a href="https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html">Phoenix.Pubsub</a>)</h4>
<p>Functionality used as an underpinning for Phoenix Channels. PubSub is a publish/subscribe mechanism for loose coupling of communication across your application. It uses Erlang&rsquo;s process groups and Erlang distribution. Unless you are on Heroku in which case you need the Redis adapter. You should be clustering or Chris McCord will be upset with you.</p>
<p>Phoenix PubSub is incredibly practical for letting processes track a topic and receive messages when things happen on that. And as GenServers, Channels and LiveViews are processes that can handle messages you can use these for many niceties. A common one is informing a LiveView that content it is showing has in fact changed. It can then do whatever it considers appropriate to inform the user.</p>
<h4 id="presence-phoenixpresencehttpshexdocspmphoenixphoenixpresencehtml">Presence (<a href="https://hexdocs.pm/phoenix/Phoenix.Presence.html">Phoenix.Presence</a>)</h4>
<p>Built to support usage in Channels but usable as a more general tool. Phoenix Presence lets you track the ephemeral presence of things (usually users). Are they online? Busy? Away with a small message? Are they on mobile only? It uses a CRDT approach to minimize how much data it needs to keep around while creating an eventually consistent model of the world without requiring a separate storage backend.</p>
<h4 id="channels-phoenix-channelshttpshexdocspmphoenixchannelshtml">Channels (<a href="https://hexdocs.pm/phoenix/channels.html">Phoenix Channels</a>)</h4>
<p>An abstraction, intended to go on top of WebSockets though it will do long polling if necessary, it provides an abstraction for connecting to channels and joining rooms within them. Each WebSocket is backed by a GenServer on the server side and will let you keep state about the connected user. Typically you connect to it with a JavaScript client which handles failures, reconnects and provides some API niceties.</p>
<p>Fundamentally the most important bits are WebSockets and server-side actors. But the rest is nice too.</p>
<h4 id="liveview-phoenixliveviewhttpshexdocspmphoenix_live_viewphoenixliveviewhtml">LiveView (<a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html">Phoenix.LiveView</a>)</h4>
<p>The belle of the ball for most of us. The point of LiveView is to eliminate the need to write JavaScript for most web development tasks. It allows driving highly interactive web UI based on server state. This typically happens over a Channels-style WebSocket. You can annotate your Heex templates and components with attributes such as <code>phx-click</code> and similar to allow sending events to the server. The event is processed to update server state and a minimal diff is calculated and passed back to the browser which can then patch the DOM using morphdom.</p>
<p>You would have a very hard time getting payloads this lean using a SPA with your own API implementation. It also leans hard on an Erlangism: consistently low latencies.</p>
<p>There is also support for driving certain simpler JS/CSS updates without a server round-trip, animation support, streamed listings, functional stateless component, stateful components and more that I&rsquo;m forgetting.</p>
<p>It is also trivial to push server-side changes to relevant LiveViews, thanks to the BEAM and Phoenix PubSub.</p>
<p>Here we have a very opinionated design. It sacrifices offline support for not needing to write as much code or maintain as many interfaces. It is an immense time-saver. There are many more posts about LiveView out there. We can leave it at this point.</p>
<h3 id="asset-pipeline">Asset pipeline</h3>
<p>Once upon a time Phoenix shipped brunch I think. Then they switched to the industry standard: Webpack. Then they grew sick of the massive support burden it was to help people keep their node and npm setups working. Phoenix switched to <a href="https://esbuild.github.io/">Esbuild</a> delivered through an Elixir library via Hex.pm.</p>
<p>I think this was a very good move. I have so many fewer asset problems now.</p>
<h3 id="mailer-swooshhttpshexdocspmswooshswooshhtml">Mailer (<a href="https://hexdocs.pm/swoosh/Swoosh.html">Swoosh</a>)</h3>
<p>Phoenix ships with Swoosh which is an abstraction for email. It has plug-ins for many popular transactional email providers. So once you go into production you have an easy time doing password resets or magic links.</p>
<p>It also has a very nice little dev tool that lets you check a mailbox for the mail that has been &ldquo;sent&rdquo;.</p>
<h3 id="live-dashboard-phoenixlivedashboardhttpshexdocspmphoenix_live_dashboardphoenixlivedashboardhtml">Live Dashboard (<a href="https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.html">Phoenix.LiveDashboard</a>)</h3>
<p>Under-promoted cool thing. It is an observability dashboard web UI that you can ship by default in your admin and do simple things like:</p>
<ul>
<li>Investigate your running processes</li>
<li>See breakdowns of memory usage</li>
<li>See OS metrics</li>
<li>See ETS table usage</li>
<li>Capture request logs</li>
</ul>
<p>And more. For no effort you get a practical first-look tool for investigating a misbehaving system. This is very Elixir. All the primitives and possibilities come from the BEAM and have been possible in Erlang since forever. But Elixir made it nice, simple and for most of us it is there by default (because Phoenix).</p>
<h2 id="your-app-is-not-just-web">Your app is not just &ldquo;web&rdquo;</h2>
<p>Most systems have more duties than just serving web traffic. Often this is delegated to queues/brokers, workers, other services, Redis, databases, cloud functions or whatever else. A Phoenix app is a BEAM application first. You can run many other workloads in it. You are building a system and it doesn&rsquo;t have to be several applications under this runtime.</p>
<p>Importantly Phoenix does not infect your system in any particular weird way. It doesn&rsquo;t warp the application away from normal Elixir application conventions.</p>
<h2 id="eliminating-infrastructure">Eliminating infrastructure</h2>
<p>I have touched on this throughout. But fundamentally a Phoenix application is only really expected to need a backing relational database. Typically Postgres.</p>
<p>No Redis. No mail workers. No separate task-runners. It can all occur from your application.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>Phoenix is very much the canonical web framework for Elixir and it has massive gravity in the community and ecosystem. I think we benefit a lot from that. There is a lot of common, directed effort centering on Phoenix and Ecto.</p>
<p>It is a very solid web framework, on top a legendary runtime. And you build inside it with a very approachable language.</p>
<p>We get the system design and development possibilities afforded by our runtime where we have very few things limiting us in what we want to do.</p>
<p>And then we get the innovations that have been built in a way that is essentially unique. Other ecosystems have copied LiveView, as they should. But their variants can&rsquo;t do everything that LiveView can and they will have challenges getting there if they try. And that disregards all the practicalities of making an application be live.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Unpacking Elixir: The Actor Model</title>
      <link>https://underjord.io/unpacking-elixir-the-actor-model.html</link>
      <pubDate>Thu, 16 Nov 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/unpacking-elixir-the-actor-model.html</guid>
      <description>This series covers a lot of fundamentals about the underlying BEAM VM and Erlang as consequence of covering Elixir fundamentals. A lot has been said about The Actor Model when it comes to Erlang. That&amp;rsquo;s kind of funny. Because the only thing that as a matter of terminology should definitely be called actors in the Erlang world are Bjarne Däcker, Joe Armstrong, Mike Williams and Robert Virding as featured in Erlang: The Movie.</description>
      <content:encoded><![CDATA[ <p>This series covers a lot of fundamentals about the underlying BEAM VM and Erlang as consequence of covering Elixir fundamentals. A lot has been said about The Actor Model when it comes to Erlang. That&rsquo;s kind of funny. Because the only thing that as a matter of terminology should definitely be called actors in the Erlang world are Bjarne Däcker, Joe Armstrong, Mike Williams and Robert Virding as featured in <a href="https://www.youtube.com/watch?v=xrIjfIjssLE">Erlang: The Movie</a>. And while technically correct, it is quite charitable to call them actors. I suspect they would agree.</p>
<p><a href="https://en.wikipedia.org/wiki/Actor_model">The Actor Model</a> is generally a model for concurrent computation. And Erlang was <a href="https://erlang.org/pipermail/erlang-questions/2014-June/079794.html">not built to implement Actors</a>. I&rsquo;m sure we could be debate about whether it does or not. I have not read Carl Hewitt and I&rsquo;m not going to offer an opinion. It does something similar enough in terms of message passing and spawning. It might be the case that Erlang processes has shifted what people expect an Actor to be and rather than be a realization of the theory it has become an implementation that overrides the theory. I will reference The Actor Model as something Erlang and Elixir does with regards to processes, messaging and concurrency for the purposes of this post. Please argue about this further in all comment sections and social media for traction and engagement ;)</p>
<p>Regardless. The terminology of an &ldquo;actor&rdquo; does not exist* in Erlang or Elixir. There is spawning processes, sending messages and receiving them. The typical representative of a high-level, full-featured actor in Erlang is the gen_server (there are also gen_event, gen_statem and other abstractions with different features) and in Elixir we have the GenServer module, etc. A generic server. They are almost exclusively started with a link to the spawning process which should ideally be a Supervisor. I cover more of this under <a href="/unpacking-elixir-resilience.html">the resilience part of the series</a>.</p>
<p><em>*  I actually recently found a use of &ldquo;actor&rdquo; in the ecosystem. The Ash Framework uses the term actor for the concept of &ldquo;the acting party&rdquo;. Typically a user, team or organisation taking an action on a resource. It is unrelated and not in Elixir itself. Amusing though. Ash brought Actors to Elixir.</em></p>
<p>A GenServer is a bunch of logic started as the core loop of an Erlang process. It conforms to a number of useful protocols for debuggability, introspection and as mentioned they tend to be started with <a href="https://www.erlang.org/doc/man/erlang#link-1">a link</a>. Meaning the processes will be aware if the other end of the link exits. The fundamental loop is to wait until it receives a message, execute a relevant code path based on the message, update held state if necessary, back to receive a message and repeat.</p>
<p>There are three main ways of interacting with a started GenServer. Calls are a request/response approach with a timeout. These are synchronous from the calling side but can be handled in an asynchronous fashion by the GenServer (see returning  a <code>:noreply</code> on a call <a href="https://hexdocs.pm/elixir/1.12/GenServer.html#c:handle_call/3">in the docs</a>). Then we have a cast which is a fire-and-forget message sent to a GenServer. And then there is regular message passing with <code>send/2</code> and related fundamentals. A GenServer is an Erlang process and it can receive messages that are not calls and casts. Similarly it can send messages that are not replies to calls.</p>
<p>For people coming from OOP to Elixir there is often an attempt to map a class to a GenServer module and object instantiation to <code>start_link</code> and <code>init</code>. This is generally an anti-pattern. A GenServer sets the runtime behavior of your application and is typically relevant for holding a connection, processing work from a queue, polling an API or other runtime concerns. It is a system design and architectural choice. It is not really about code structure and encapsulation. If what you want is to group data and logic, what you want for this is usually a module, probably with an Elixir struct and a bunch of functions. Pure immutable operations that can be performed in any process at any time without creating a bottleneck. Your actors can benefit a lot from modules like this to manage their internal operations.</p>
<p>Whenever you need serial ordering or fanning out to multiple parallel pieces of execution you should reach for GenServers, Tasks and such. Sometimes you absolutely want a bottleneck, usually when you need control of how things interact. Rate limiting, serializing writes, managing a unique resources such as a GPU or tracking information about an ongoing background process.</p>
<p>One example of a typical actor usage is also one of the more agressive uses of the paradigm. The Phoenix LiveView. The LiveView holds a Phoenix Channels WebSocket connection in state. It handles events received from the WebSocket, messages from the system and through these handler functions it can update the state of the LiveView. Every change to the state produces diffs that can be passed to UI over the WebSocket. Every Erlang-style actor exposes an API surface to the surrounding system, the LiveView exposes a subset of it over the WebSocket.</p>
<p>All Erlang processes including GenServers have a process ID, succinctly known as a PID. There are numerous ways of registering, aliasing, grouping and finally addressing them. This can be used to implement messaging patterns such as queue/broker and workers or publish/subscribe (pubsub) internally for the application. Messaging can also traverse an Erlang cluster transparently which is incredibly convenient and powerful. During messaging you don&rsquo;t have to be concerned about where your processes are in the cluster. If you have the PID you can reach them.</p>
<p>Concretely, the Erlang <code>:pg</code> module handles Process groups and can register named groups across the cluster in an incredibly convenient way. This is the foundation of Phoenix PubSub. Unless you use the Redis adapter in which case you should* probably move on from Heroku to get the best use out of Elixir.</p>
<p><em>* I snark.</em></p>
<p>While all* code in Erlang and Elixir runs in processes and most libraries and tools you work with are built on top of gen_server and friends. The Actor Model as implemented in Erlang is not the dominant paradigm that determines what your code looks like. Not in the way Object-Oriented Programming does. It is not like in Python where &ldquo;everything is an object&rdquo; kind of dominates the language. Most of Elixir is a dynamic flavor of Functional Programming. You don&rsquo;t spend all your time doing Actor-stuff. Processes and messaging are underpinnings for the code you write and when you need to deal with state or work needs to be distributed across more units of concurrency. Certainly. However I find most of what I do is basic Functional Programming.</p>
<p>**  There are several exceptions. It is true enough but not actually absolutely true. Ports and NIFs are ways of doing things outside of regular Erlang processes.</p>
<p>And as anyone who has done Elixir for a bit can attest. If you isolate the places where you perform messaging a bit from other, pure logic, that makes it a lot easier to test. Some have argued that Erlang is like Kubernetes and that GenServers are like microservices. This is very incorrect. But it may be helpful for understanding the nature of an Erlang/Elixir application. It has many things going on. It is very much as system in and of itself, it is not constrained to being a single web app or a single worker. It was designed to do many things.</p>
<p>Where this analogy holds well is probably testing. Writing tests across Kubernetes cluster is probably even worse than writing tests across your Elixir application but I think they are similarly instructive. To test multiple moving parts like this you need a lot of control over them from the testing side. The worst case is if they are heavily reliant on time and timing. That is really painful to work with in any system and it is quite difficult to control well.</p>
<p>Trying to write an integration test that crosses multiple GenServer and Supervisor boundaries will tell you how well you did with abstractions. You will know where you took shortcuts or simplified things too much. And this is the reason why the pure tests are much easier. Testing a single GenServer that can be started with a non-global name is a reasonable process. Testing a few of them that are all essentially singletons probably prevents your tests from running in parallel, will make cleanup and reset a pain and will make you want to refactor almost immediately. This has influenced the <a href="https://hexdocs.pm/elixir/1.15.7/library-guidelines.html#anti-patterns">library guidelines</a> for Elixir. Under Anti-Patterns you&rsquo;ll find:</p>
<p><a href="https://hexdocs.pm/elixir/1.15.7/library-guidelines.html#avoid-application-configuration">Avoid application configuration</a></p>
<p>Why? Because it is global. Your user may use it and your library might offer it as a convenience. If you require it your are placing massive limitations on how useful your library can be.</p>
<p><a href="https://hexdocs.pm/elixir/1.15.7/library-guidelines.html#avoid-using-processes-for-code-organization">Avoid using processes for code organization</a></p>
<p>Which is what I was saying above. Processes and consequently GenServers are not a tool for code organization.</p>
<p>You will find that Phoenix generally generates modules for you that you then own. MyAppWeb.Endpoint, MyApp.Repo (from Ecto, really), MyApp.PubSub and so on. Then these are started in your application. You own them. If they pull config they pull them based on the name they have in your application so that if you want multiple Repos you absolutely can (dynamic repos also exist, different purpose). The Endpoint gives your the web server supervision tree, the Repo gives you the database connection pool supervision tree, the PubSub manages your process groups and whatnot for the PubSub mechanism.</p>
<p>Phoenix needs processes for what it does, concurrent requests, queries, pubsub. But it hands you that stuff. You can run many Phoenixes in your application if you need or want. The framework hands you the reigns.</p>
<p>Wrapping up. The Actor Model that Erlang actually has is a mechanism mostly intended to provide a reasonable API for concurrency. And distribution. And also a way to model a system design with a highly dynamic functional language. It does shared-nothing and message passing. This is where many (perhaps most?) high-level concurrency APIs end up. The alternatives are to my understanding always more painful with much more nuanced performance trade-offs.</p>
<p>If you have a need to argue about the nuances of Actors I am available explicitly for that practice (also business inquiries) over email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> and out there on the fedi <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Unpacking Elixir: Observability</title>
      <link>https://underjord.io/unpacking-elixir-observability.html</link>
      <pubDate>Mon, 02 Oct 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/unpacking-elixir-observability.html</guid>
      <description>Elixir supports the usual supects of observability. Open Telemetry (OTel), log handlers, capturing metrics. And it does it well. This post will mostly focus on the observability you have on the BEAM that is either incredibly rare to see elsewhere or possibly entirely unique.
The previous posts on concurrency and resilience might give useful context around how processes work and how supervision trees are structured. I will try not to lean too heavily on them but if you feel the need to understand more, consider reading them.</description>
      <content:encoded><![CDATA[ <p>Elixir supports the usual supects of observability. Open Telemetry (OTel), log handlers, capturing metrics. And it does it well. This post will mostly focus on the observability you have on the BEAM that is either incredibly rare to see elsewhere or possibly entirely unique.</p>
<p>The previous posts on <a href="/unpacking-elixir-concurrency.html">concurrency</a> and <a href="/unpacking-elixir-resilience.html">resilience</a> might give useful context around how processes work and how supervision trees are structured. I will try not to lean too heavily on them but if you feel the need to understand more, consider reading them.</p>
<p>I don&rsquo;t know if my understanding is accurate to the truth of the system, I don&rsquo;t read BEAM bytecode, but I&rsquo;ll take a stab. Elixir and Erlang applications do not change their shape very much in compilation. I imagine the VM-level code still has an understanding of the fundamental parts of the language we see. Modules. Functions. It certainly knows about processes, messages, mailboxes and all that.</p>
<p>Why would someone build it this way? It has to be fundamentally inefficient compared to say .. C++ or Rust. Your compiled C or C++ program will be very different from the code you wrote. Who knows what the compiler will feel like doing. It will be semantically equivalent, the meaning of how the code executes will be retained. There is a ton of additional debugging information required if you want to retain the human-readable meaning and ideas of the original code during execution of the end product. And that might be enough to give you a nice stack trace.</p>
<p>Elixir aside, other high-level dynamic languages also seem to keep more of their general structure. If the program can mutate itself significantly during runtime it limits how much the compiler can be allowed to mangle things.</p>
<p>Erlang was built to provide hot code updates. I think this is the fundamental reason it is so introspectable at runtime. Hot code updates, while they can be done rigorously and should be treated seriously for production systems, is essentially the biggest monkey patch facility imaginable. I guess any language that can patch itself at runtime could start implementing hot code updates. But Erlang is designed to allow it and do it gracefully. This significantly limits how much the compiler can be allowed to boil things away.</p>
<p>Let&rsquo;s start with the tool that Elixir devs tend to use daily. The REPL, the shell, iEx. It is a great part of the developer workflow as is. More importantly though, if you package an Erlang Release of your project you also end up with <code>my_app remote</code>. This command pops an iex shell, connects to your app over Erlang distribution and lets you poke your application. It lets you operate your application. All your modules and functions are intact and you can just make function calls, send messages and generally poke and prod around in your cluster. Whatever you need, anything that&rsquo;s available in Erlang and Elixir plus any new code your a willing to type or paste into the interpreter.</p>
<p>What facilities do we have to actually pull information from the system? Well. The Erlang <code>sys.get_state/1</code> function lets you pull the state of an &ldquo;Actor&rdquo;-style process (GenServer, GenStage, gen_event, gen_statem, etc) which is usually all of them. If they are part of how Erlang runs supervision trees by implementing those protocols it should also be able to give you the state it is holding. So you can inspect the running state of your application down to the studs.</p>
<p>Elixir offers <code>Process.list/0</code> to get a plain list of all the process IDs (PIDs) on the local node. With this you can do <code>Process.info/1</code> to find out what is going on and investigate things like memory usage, reduction count (number of function calls), initial function call and much more. All things that are part of the Erlang protocol for a well-behaved process.</p>
<p>This is all underpinnings for higher-level tools such as Erlang&rsquo;s fun desktop UI app <code>observer</code> which can show you a graph of your supervision tree. It also has an activity monitor for your processes (order by memory used descending, oh there&rsquo;s the memory leak). You can kill processes, you can get system-level stats. It has a bit of everything.</p>
<p>Of course with LiveView coming to Elixir this was pushed a bit further. A default Phoenix app has a few lines to uncomment to enable LiveDashboard which is a view that gives you much of the same information as observer. Plus it collects some telemetry for Phoenix and Ecto. You also get system-level information (disk, RAM, CPU), BEAM-level information (memory allocation types, resource usage, schedulers running) and a bit more. You also get a Process listing here in a nice and neat web view. If you are new in Elixir and want to make some waves I think pushing LiveDashboard further would be a good place to poke around. It is already a very cool start.</p>
<p>I believe LiveDashboard established this pattern for libraries with web UI for Phoenix. It provides a plug. Meaning you can shove it in any part of your router that you like. Usually behind an admin access check. I&rsquo;ve since seen this done with Oban (job processing library) web UI and I believe the same thing is done with Orion.</p>
<p>Let&rsquo;s talk about Orion because it feeds right into this story. It is a recent development by Thomas Depierre. Dubbed as a Dynamic Distributed Profiler it does something that requires a lot of instrumentation to do in any ecosystem and which is probably impossible to do fully in some. It was entirely achieved with existing Erlang facilities. You can enter a module name, a function name and an arity, hit Run. It will start to capture the performance of that function being called across your entire cluster. It then graphs that and gives you data and statistics on how that function is performing. There are many cool directions to extend this tool, let&rsquo;s get into what it was built on. Tracing.</p>
<p>Tracing in Erlang is a mechanism for capturing information around the execution of a function. It is not limited to performance numbers. I&rsquo;ve used both raw Erlang <code>dbg</code>, the convenience library <code>recon</code> and a little bit of <code>recon_ex</code> to do tracing on production systems when I needed to figure something out. I don&rsquo;t know of any other runtime or language that makes this possible. Maybe I&rsquo;m missing a world of tools in different ecosystems. Let me know. But essentially you formulate a type of pattern match for which invocations of the function you want to capture and in what way. Typically I want the inputs and the outputs along with execution time.</p>
<p>Think about this for a moment. If you have a function that seems to end up getting called with the wrong value, probably nil, for some unclear reason. And of course only in production. You can, in a reasonable manner, set up a trace and either wait for the thing to happen or trigger the behavior. Then you just watch the answers come in.</p>
<p>Oh, you need to know what happens one function deeper? Set another trace. There are limits and considerations for how much tracing you should do at once but you have a lot of room to play. Most of the tools built on top of the Erlang primitives try to protect you a bit from overloading your system with trace messages.</p>
<p>There are so many tools that haven&rsquo;t even been built on top of this yet. I don&rsquo;t think most Elixir developers are even aware that it is possible. There is no particular reason you couldn&rsquo;t trigger an automatic trace after a new type of error surfaces and try to capture more info on the next run, ship that to your devs. Or build a UI for picking modules, functions and args to match so that you can capture these traces. If you want you could probably adapt the results to go into your Open Telemetry trace storage. There is a world to explore here.</p>
<p>And to some it won&rsquo;t be available at all. If you run your stuff on Heroku and don&rsquo;t add some tool for accessing a shell through the web or something you might have no way of reaching your server to pop the shell.</p>
<p>I should have asked for a <a href="https://fly.io">Fly</a> sponsorship here. This stuff is why I&rsquo;m excited about their platform. Wireguard private networking by default makes clustering much easier. It also makes connecting to servers simpler which means getting at an iex shell straightforward.</p>
<p>You could replicate most of it with Tailscale or if you really want to work for it, custom wireguard stuff. Either way, Fly is a very good match for Elixir. It makes sense that they anchor their presence in the Elixir ecosystem by funding Chris McCord&rsquo;s work on LiveView. Their infra offering just fits so well. A bunch of asterisks on the maturity of the offering still remain and they&rsquo;ve owned up to that. Feature-wise I really like it.</p>
<p>A regular VPS or dedicated server is also perfectly convenient to shell into.</p>
<p>This capability, especially with Wireguard networking, also allows you to connect a Livebook (collaborative code notebook for Elixir) to a running system. This lets you build a recipe-book of things you might want to do in your system. Some would call them playbooks or runbooks. Rather than writing ad-hoc code that will get lost in your terminal history you build up a toolset. I haven&rsquo;t put this into practice but it should be perfectly feasible.</p>
<p>To revisit. The BEAM retains the shape of your application. You modules, your functions, they still exist. Your processes are not just abstractions that are flattened out by the compiler. They are real and exist, comparatively speaking. Erlang and OTP have protocols and systems in place that make it possible to observe the running system at a high level for an overview as well as dive in and inspect any particular part.</p>
<p>The unique nature of these incredibly dynamic systems warranted unusual solutions and that gave us the Erlang tracing facilities. It is not a wildly monkey-patching library from some APM provider. It is not a hack to interject into the operation of the software, it is a fundamental facility of the runtime.</p>
<p>Playing nice with existing ecosystems and standard practices such as Open Telemetry is important. Elixir and Erlang are used for serious stuff and in mixed environments. It can&rsquo;t all be special, unique and quirky. And I think the telemetry handling, metrics libraries like PromEx and the OTel implementation make great use of the BEAM and do not require external tools to operate aside from where to store the data.</p>
<p>Then when you look at the stuff that really is special, unique and quirky there is immense potential. We can go beyond what is feasible in other runtimes and languages. I think this is a big space for innovation on top of Elixir, Erlang and the runtime. The tools I&rsquo;ve seen in this area are still fairly simple and there is so much potential.</p>
<p>This is what you get working with a higher level of abstraction that had a deeper purpose. Here the abstraction is not just about convenience and syntax but a deeply worked design that serves larger objectives. Python is a high level of abstraction primarily to make a sleek and convenient language. I am not convinced that the design of the language and runtime had clearer objectives more elevated than nice and convenient syntax. That&rsquo;s fine but it has long-term consequences.</p>
<p>I think observability and introspectability, of arbitrary parts of the system, at runtime, is one of those things that people don&rsquo;t know or really think about with Erlang and Elixir. But the magic of the BEAM is in the runtime and at runtime. It always was.</p>
<hr>
<p>What would you want to see in observability? Am I missing fantastic contenders in other ecosystems? Let me know via email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on the fedi <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. I really enjoy exchanges with my readers.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Unpacking Elixir: Resilience</title>
      <link>https://underjord.io/unpacking-elixir-resilience.html</link>
      <pubDate>Sun, 24 Sep 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/unpacking-elixir-resilience.html</guid>
      <description>The nine nines. 99.9999999% of uptime. Whether the AXD301 actually deserves to be held up as a system of nine nines seems debatable. I am not particularly interested in that debate. Erlang has a strong record for reliability and a design intended to help you as a developer and operator achieve your nines. Maybe just five of them. Up to you really.
Previous episodes of this article series has unpacked Concurrency, Real-time &amp;amp; Latency and the syntax.</description>
      <content:encoded><![CDATA[ <p>The nine nines. 99.9999999% of uptime. Whether the AXD301 actually deserves to be held up as a system of nine nines seems debatable. I am not particularly interested in that debate. Erlang has a strong record for reliability and a design intended to help you as a developer and operator achieve your nines. Maybe just five of them. Up to you really.</p>
<p>Previous episodes of this article series has unpacked <a href="/unpacking-elixir-concurrency.html">Concurrency</a>, <a href="/unpacking-elixir-realtime-latency.html">Real-time &amp; Latency</a> and <a href="/unpacking-elixir-syntax.html">the syntax</a>. In this one we talk about how Elixir and Erlang achieve reliability.</p>
<p>There are agressive, defensive and rigorous practices you can adopt in a variety of ways to achieve reliability and resilience. Testing, profiling, specifications, design, observability, culture, operations and so on. This is not about most of those. This is about software abstractions that serve you in making your system more resilient and allow you to construct resilient designs.</p>
<p>Erlang implements the Actor model, the typical actor in Erlang and Elixir terminology is the GenServer. The generic server. It is a process that has some nice facilities for receiving calls (&ldquo;synchronous&rdquo; messages, expecting a response within a timeout) and casts (asynchronous messages, with no expectation of reply). A GenServer is a bunch of API wrapped around the basic Erlang process. A process can receive and send messages. A GenServer (also other related types, gen_event, gen_statem) implements capturing errors that occur and the subsequent exit and passing information about the problems to the parental process as a message.</p>
<p>So what is the parental process. Well it is usually a special type of Erlang process called a Supervisor. A Supervisor can run multiple child processes that implement the necessary API for being a child. The Supervisor then handle problems with the children falling over by applying a strategy.</p>
<p>The strategies are normally <code>one_for_one</code>, <code>one_for_all</code> and <code>one_for_rest</code>. One for one indicates that if a child crashes it, it and only it should be considered when restarting. Leave the others alone. One for all means that the supervisor should restart all children if one child fails. This is often useful if the children are sharing a workload in some important way where restarting one would cause trouble with the others. One for rest is kind of nuanced in that the Supervisor will restart any child defined in the starting order after the crashing one. This can be used to kill a supporting process if the main one dies for example.</p>
<p>So a Supervisor supervises a set of child processes. This set can be dynamic, growing and shrinking as the system operates. In Elixir that would be a DynamicSupervisor. If a single crash happens within a child it will apply the strategy. If the new child crashes immediately it will try again. If there are enough failures within a given threshold the Supervisor will consider itself failing at the job and crash.</p>
<p>This crashing is a feature, not a bug. By crashing it will propagate a signal to whatever Supervisor started this Supervisor that there is an issue. This Supervisor can in turn apply strategies to try and recover the Supervisor through the tried and true practice of turning it off and on again. Or via custom logic if you are feeling spicy.</p>
<p>How many restarts, how often, how intense it is allowed to get. That&rsquo;s all configurable. You might be fine with the defaults but if you have something particular in mind you have a pretty solid set of knobs to twiddle.</p>
<p>By having Supervisors supervise Supervisors all the way back to the root Supervisor which sits on top of your particular Erlang application we have defined a supervision tree. The idea being that any issue out in a nice leaf node of a tree is probably addressable there and should not affect the rest of your nice stateful actor-packed system. But if it isn&rsquo;t, it is probably addressable at a branch closer to the tree.</p>
<p>A typical use-case for a Supervisor is a connection pool. And it might be the case that if any of the connections crash that indicates a problem with the server and you know from experience that you might as well re-establish every connection in the pool. Cool. Your Supervisor should do <code>one_for_all</code>. While if a phone-call crashes you probably don&rsquo;t want to dump out every call in the system just because one had a weird issue. <code>one_for_one</code> should do it.</p>
<p>This is where the Erlang &ldquo;Let it Crash&rdquo; idea comes from. It is a bit of a meme (often repeated, poorly examined, often misunderstood, sometimes with funny pictures). Fundamentally, if there is nothing useful your code can do on a failure, no mitigation, no meaningful fallback, then you might as well have it blow up and rely on the underlying tree to make whatever is most useful out of it. You should probably handle the usual suspects &ldquo;oh, data is missing, let&rsquo;s 404&rdquo;. That is an expected failure mode. &ldquo;oh, hard drive has imploded and disk can&rsquo;t be read&rdquo; is a very different error and a 500 Internal Server Error is probably fine.</p>
<p>I don&rsquo;t think I&rsquo;ve seen an up-and-running Elixir application go down due to exhausting the Supervision tree with erroring out. I have seen it many times as a failure mode for a misconfigured deploy, typically missing an env variable, where starting the initial supervision tree fails.</p>
<p>Supervision is the fundamental building block in Erlang&rsquo;s resilience story.</p>
<p>How is it used in Elixir? Not much. As in, we don&rsquo;t typically need to think about it. Phoenix ships a practical and helpful Supervision tree. Ecto ships a good Supervision tree for your database connections and all that. These Supervision trees live a peaceful secretive life under the guise of library code and you get all the advantage with none of the work. And the moment you start coloring outside the lines and building your own GenServers the facilities are there for you.</p>
<p>With your regular generated Phoenix web application all you see is that your <code>application.ex</code> file contains a few entries for different purposes. These are the first branches of your supervision tree and all that you really see.</p>
<p>You get <code>MyApp.Repo</code> for your Ecto Repo which is the abstraction for all your database connectivity, hiding your connection pools and whatnot. You get <code>MyApp.Endpoint</code> which wraps up your HTTP server (Cowboy or Bandit) in Plug and Phoenix abstractions to respond. Under this you will build up a subtree with a variety of jobs. For HTTP requests you have processes that respond. I believe Cowboy uses a pool and Bandit spawns more ad-hoc. There are also the WebSockets and the specific Phoenix Channels or LiveView processes that belong to the state of your application. This all branches out from your Endpoint as it is all related to your web serving.</p>
<p>You can hear more about <a href="https://www.beamrad.io/53">how Bandit operates</a> on the BEAM Radio episode we did with Mat Trudel. We got into the process usage a decent bit there.</p>
<p>Separately you&rsquo;ll have <code>MyApp.PubSub</code> which starts the Process Groups (pg2) or Redis adapter for doing cool PubSub stuff. This is not strictly web-related, so it has a separate sub-tree. Someone did a thinking.</p>
<p>I appreciate that Phoenix doesn&rsquo;t try to hide this stuff from you any more than abstracting away the details. If you want to run your app without the web part you can quickly get to the idea of dropping the Endpoint from the list. You simply don&rsquo;t start it. Same with the DB, just cut out the Repo. Likewise if you need multiple DBs or multiple web services. You can make more Repos and Endpoints. It is explicit. The code is right there. It uses the standard Erlang and Elixir way of starting things.</p>
<p>Anyway, that&rsquo;s a detour from resilience and reliability. Fundamentally you have these supervisors, strategies as a structure to contain the blast radius of errors. The places this approach falls down is if you build a native implemented function (NIF) in C or similar. A crash in that brings down the whole VM. This is why doing work in properly managed BEAM code is preferred in most cases and why you&rsquo;ll often see attempts to re-implement things in Elixir/Erlang rather than just binding to an existing C library. Rust and Rustler, Zig and Zigler both offer NIF implementations with some additional safety. Of course they don&rsquo;t entirely remove the risks of native code though.</p>
<p>Rustler uses <a href="https://medium.com/@jlouis666/erlang-dirty-scheduler-overhead-6e1219dcc7">dirty schedulers</a> and I expect Zigler does something similar or at least supports the old and the new options for building NIFs. So they should play nice-ish with Erlang scheduling. But fundamentally native code doesn&rsquo;t quite offer the same safety or same behavior.</p>
<p>Beyond Supervision trees there are more aspects of Erlang that are resilient. Scheduling and pre-empting was intended to produce <a href="/unpacking-elixir-realtime-latency.html">consistently low latencies</a> but it also protects you from heavy workloads bogging things down, it prevents infinitely looping bugs from slowing the system to a crawl and in general makes things resilient to things not being ideal all the time.</p>
<p>Another thing is the Erlang heart. That&rsquo;s a module that can be used to detect the Erlang application itself going down and being able to start it again. An almost-supervisor for your entire application. The IoT <a href="https://nerves-project.org/">Nerves project</a> also uses a variant of this to help ensure a resilient device.</p>
<p>Having built in other web frameworks and languages this is all a bit foreign. It is more reminiscent of building a microservice architecture than building a single application. But it does occur at the application level. It gives you tools to think about bringing up your system, bringing down your system, handling failure in your system. Those concerns all exist in other languages but the control-mechanisms tend to be quite coarse. Erlang and Elixir helps you think about how you design your application.</p>
<hr>
<p>Do you have lessons learned that you&rsquo;d like to share about building for resiliency? Does the Erlang way of doing it resonate with you? Let me know over email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or via <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Unpacking Elixir: Real-time &amp; Latency</title>
      <link>https://underjord.io/unpacking-elixir-realtime-latency.html</link>
      <pubDate>Fri, 08 Sep 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/unpacking-elixir-realtime-latency.html</guid>
      <description>Elixir was built on Erlang. Erlang was built to provide &amp;ldquo;consistently low latency&amp;rdquo; and a few other audacious goals. Note, this is not a hard realtime constraint. It is soft, squishy and yet, important and real. It makes Erlang unusually suitable to systems where latency matters and where a near-realtime experience is necessary.
Soft realtime. This part of the Unpacking Elixir series will really benefit from having read Unpacking Elixir - Concurrency as that post covers a lot about how concurrency works in Erlang and with that it covers Processes and Schedulers.</description>
      <content:encoded><![CDATA[ <p>Elixir was built on Erlang. Erlang was built to provide &ldquo;consistently low latency&rdquo; and a few other audacious goals. Note, this is not a hard realtime constraint. It is soft, squishy and yet, important and real. It makes Erlang unusually suitable to systems where latency matters and where a near-realtime experience is necessary.</p>
<p>Soft realtime. This part of the Unpacking Elixir series will really benefit from having read <a href="/unpacking-elixir-concurrency.html">Unpacking Elixir - Concurrency</a> as that post covers a lot about how concurrency works in Erlang and with that it covers Processes and Schedulers. This is mostly about Erlang and the historic choices made in developing the BEAM virtual machine. In the end we will get into how Elixir leverages it in the current landscape but Elixir didn&rsquo;t create this capability.</p>
<p>Erlang&rsquo;s claim for consistently low latency predates the multi-core concurrency approach a fair bit. From my understanding it ran a single scheduler in the olden times and the pre-empting was there to ensure this fair distribution of computational resources. It would prevent single pieces of CPU-intensive work from holding up concurrent, faster, pieces of work. It ensures progress is made on all work in short order. No piece of work should be waiting long for processing. A heavy piece of work will also eventually resolve.</p>
<p>Another interesting advantage of this design is that an accidentally infinite piece of work will not necessarily disrupt the system significantly. It will waste resources but will not block all progress.</p>
<p>Erlang was built for telecom and the work was, again from my understanding, about routing phone-calls without introducing noticeable delay preventing any particular misbehaving process from impacting other processes too heavily. In a way it was always about the quality of user experience. Performance often is.</p>
<p>It is a very high-level and dynamic system which is unusual when talking about performance. Why? By being a higher abstraction level, being very dynamic and working with immutable data structures as a Functional Programming style language Erlang leaves a lot of performance on the table in the service of other objectives. We won&rsquo;t go into those other objectives now but Wikipedia lists distributed, fault-tolerant, highly available and hot swapping as additional traits beyond the soft real-time. I did say Erlang had audacious goals.</p>
<p>So soft real-time or &ldquo;consistently low latency&rdquo; is one of the objectives. And it is a performance objective. This is an interesting quirk of design. Designing for the absolutely lowest latency would require a very different approach. It would complicate or even sacrifice other objectives. It might be more costly, complicated and rigid or it might be significantly limited in capabilities. Erlang is not a one-trick pony.</p>
<p>Rather than the absolute lowest latency they went for consistently low latency, with tolerance for deviations and acceptance of imperfect results. They created a system that has been spoken of with some reverence in Computer Science ever since. It is not because it has the lowest latency. I am quite certain it does not. It was only one of the objectives after all.</p>
<p>So we have a cool high-level language, with nice abstractions, that offers low latency. What did people do with it? We mostly hear about chat and messaging because it does do that very well. The popular open source messaging queue RabbitMQ is built on Erlang. The Jabber/XMPP ejabberd software was a popular FOSS chat server. Seemingly that ended up in use at Facebook for their chat. Actually, you will find Erlang or Elixir driving chat in a ton of computer and video games. League of Legends by Riot Games comes to mind. Also beyond messaging but still in games is the <a href="http://www.erlang-factory.com/upload/presentations/395/ErlangandFirst-PersonShooters.pdf">heavy use of Erlang</a> in <a href="https://www.demonware.net/">Demonware</a> which powers some pretty high-profile games. Like CoDBlops.</p>
<p>Even more famously Whatsapp built on Erlang. And Discord built on Elixir. These are two massive players in instant messaging that are deeply invested in the BEAM virtual machine. They are applications where latency matters. Not as life-and-death, not to hit the deadline for correctly driving an electron beam or anything quite so sensitive. Rather for providing a good experience to users. It doesn&rsquo;t matter if there is small variance in latency. It matters that you typically avoid a large variance.</p>
<p>Onwards to what we can use it for. Elixir got the Phoenix web framework. Initially it was just a very responsive web framework. Especially as it pre-compiled templates in a nice way so they render really fast and it was already quick to respond due to Erlang. Even under pressure, latencies would be good. Great stuff but nothing shocking.</p>
<p>One of the early bigger things from that was Phoenix Channels which is a bit of abstraction on top of Web Sockets. This allowed a Single-Page Application or similar to talk to a server that could keep state, that could wrangle messaging, database communication and also was good at broadcasting to other connected clients in a really simple way. Importantly, it didn&rsquo;t suck. A lot of them did back then. The latency was great, it scaled well. See <a href="https://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections">The Road to 2 Million Websocket Connections in Phoenix</a> for more from that era. This was in 2015.</p>
<p>The next big thing has been LiveView. Which came out with a v0.1.0 in 2019. This is again where that latency being a user experience thing comes in to play. Phoenix LiveView allows you to write Elixir instead of JavaScript frontend code to a very large extent. Components, interactivity and all you really want from a frontend framework. You only write Elixir and your state lives on one side of the connection. The server. This obviates the need for writing API and contract code.</p>
<p>It does require that connection and to be a great experience it requires that latency stays low. You can draw a straight line between this need for low latency and the fact that the creator of LiveView, Chris McCord, works at Fly.io who famously want to move your application closer to your user. That aside. LiveView lets you do a lot with a little. It has been copied by most major web framework ecosystems at this point. What they can&rsquo;t copy is the BEAM runtime and as such they can&rsquo;t quite get the same deal. Python and Ruby are both fighting to sidestep their GIL problems. Node.js is very sensitive to blocking the event loop and tanking latency. Livewire for PHP decided it didn&rsquo;t want to bother holding state and consequently can&rsquo;t do quite the same things as well.</p>
<p>LiveView is not a panacea. Not a silver bullet. Not the solution for every problem.</p>
<p>In that way LiveView is very much Erlang. It solves a solid set of problems people actually have with abstractions that make those problems trivial to work with. It seems unconcerned with a theoretical ideal and gets on with doing the work.</p>
<p>And it is quite quick about it.</p>
<hr>
<p>Have any latency horror stories to share? Notes, questions or concerns about this thing I wrote? Feel free to reach out through <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> or on email <a href="mailto:lars@underjord.io">lars@underjord.io</a>. Have a good one.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Unpacking Elixir: Syntax</title>
      <link>https://underjord.io/unpacking-elixir-syntax.html</link>
      <pubDate>Fri, 01 Sep 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/unpacking-elixir-syntax.html</guid>
      <description>Elixir is a language with syntactical roots in Ruby. It also carries the Erlang legacy. Legacy used here as in &amp;ldquo;a great legacy&amp;rdquo; and not as in &amp;ldquo;system you don&amp;rsquo;t like anymore&amp;rdquo;. Ruby is an object-oriented language. Elixir is functional language. The Erlang part has an impact as Elixir was designed to provide strong interoperability with Erlang. Like Ruby and Erlang, Elixir offers a high-level of abstraction and is a very dynamic language.</description>
      <content:encoded><![CDATA[ <p>Elixir is a language with syntactical roots in Ruby. It also carries the Erlang legacy. Legacy used here as in &ldquo;a great legacy&rdquo; and not as in &ldquo;system you don&rsquo;t like anymore&rdquo;. Ruby is an object-oriented language. Elixir is functional language. The Erlang part has an impact as Elixir was designed to provide strong interoperability with Erlang. Like Ruby and Erlang, Elixir offers a high-level of abstraction and is a very dynamic language. Overall I would say the Elixir syntax is pretty approachable and reasonable to learn. Let&rsquo;s unpack it.</p>
<p>This is another piece of my series on &ldquo;Unpacking Elixir&rdquo;. The previously published part was on <a href="/unpacking-elixir-concurrency.html">concurrency</a>.</p>
<p>My background is Python so I wasn&rsquo;t familiar with Ruby before-hand and ran into all these Ruby-isms with some confusion. Overall it was still a pretty smooth ride for me. I had done PHP, Javascript, Python, some C/C++, C#. It is a slightly new style but it didn&rsquo;t scare me.</p>
<p>The thing that many people will probably find unnecessary coming from C-style languages or find very verbose coming from Python is how a block is defined:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">do</span>
  <span style="color:#75715e"># block contents go here</span>
<span style="color:#66d9ef">end</span>

<span style="color:#66d9ef">if</span> foo? <span style="color:#66d9ef">do</span>
	<span style="color:#75715e"># true</span>
<span style="color:#66d9ef">else</span>
    <span style="color:#75715e"># false</span>
<span style="color:#66d9ef">end</span>

<span style="color:#66d9ef">def</span> my_function(arg1) <span style="color:#66d9ef">do</span>
  <span style="color:#75715e"># function body</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>It is fair to note that <code>end</code>  has a lot more characters than <code>}</code> but I do think it comes across as more human. A less dense and symbol-filled syntax could be argued to be more approachable and potentially less noisy. I don&rsquo;t particularly care either way.</p>
<p>Let&rsquo;s write a basic module:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyModule.SampleThing</span> <span style="color:#66d9ef">do</span>
	<span style="color:#a6e22e">@moduledoc</span> <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">	This is module documentation. Typically written as Markdown.
</span><span style="color:#e6db74">	&#34;&#34;&#34;</span>
	
	<span style="color:#75715e"># compile-time attribute, define-style</span>
	<span style="color:#a6e22e">@my_attribute</span> <span style="color:#ae81ff">1000</span>
	
	<span style="color:#a6e22e">@doc</span> <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">	This is function documentation.
</span><span style="color:#e6db74">	&#34;&#34;&#34;</span>
	<span style="color:#66d9ef">def</span> public_function(arg1, arg2) <span style="color:#66d9ef">do</span>
	  <span style="color:#75715e"># do thing</span>
	  new_arg <span style="color:#f92672">=</span> arg1 <span style="color:#f92672">+</span> arg2
	  <span style="color:#75715e"># return the last thing done</span>
	  new_arg <span style="color:#f92672">+</span> <span style="color:#ae81ff">2</span>
	<span style="color:#66d9ef">end</span>

    <span style="color:#75715e"># private function with an optional default argument</span>
	<span style="color:#66d9ef">defp</span> private_function(arg1, arg2 \\ <span style="color:#ae81ff">5</span>) <span style="color:#66d9ef">do</span>
	  <span style="color:#75715e"># do something</span>
	<span style="color:#66d9ef">end</span>

	<span style="color:#66d9ef">defp</span> short_function(x), <span style="color:#e6db74">do</span>: x <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>
	<span style="color:#66d9ef">defp</span> no_args, <span style="color:#e6db74">do</span>: <span style="color:#ae81ff">1</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>Modules are Pascal case. Functions, module attributes and bindings of values (variable names) are snake_case. Docstrings are multi-line text strings and also compile-time attributes. ExDoc can make very nice documentation with these. Both types of docs can also contain doctests which are a nice way of making simple tests, testing your sample code in the docs and encouraging code samples in the documentation.</p>
<p>Any do/end block can be replaced with <code>, do:</code>  for one-liners. If a function takes no args you can skip the parentheses. Functions return the value of the last expression. There is no early return without branching and you do not make the return explicit.</p>
<p>Quickly some values and types:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">integer <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>
float <span style="color:#f92672">=</span> <span style="color:#ae81ff">1.0</span>
boolean <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span> <span style="color:#75715e"># true and false are both atoms</span>
null_value <span style="color:#f92672">=</span> <span style="color:#66d9ef">nil</span> <span style="color:#75715e"># nil is also an atom</span>
atom <span style="color:#f92672">=</span> <span style="color:#e6db74">:foo</span> <span style="color:#75715e"># aside from true/false/nil you reference atoms with a colon :</span>
string <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;lawik&#34;</span>
binary <span style="color:#f92672">=</span> &lt;&lt;<span style="color:#ae81ff">108</span>, <span style="color:#ae81ff">97</span>, <span style="color:#ae81ff">119</span>, <span style="color:#ae81ff">105</span>, <span style="color:#ae81ff">107</span>&gt;&gt; <span style="color:#75715e"># equivalent to string above</span>

tuple <span style="color:#f92672">=</span> {<span style="color:#e6db74">:ok</span>, <span style="color:#ae81ff">5</span>} <span style="color:#75715e"># multiple values grouped, not limited to two</span>
list <span style="color:#f92672">=</span> [<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">4</span>, <span style="color:#ae81ff">5</span>]
map_of_strings <span style="color:#f92672">=</span> %{<span style="color:#e6db74">&#34;username&#34;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#34;lawik&#34;</span>, <span style="color:#e6db74">&#34;site&#34;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#34;underjord.io&#34;</span>}
map_of_atoms <span style="color:#f92672">=</span> %{<span style="color:#e6db74">username</span>: <span style="color:#e6db74">&#34;lawik&#34;</span>, <span style="color:#e6db74">site</span>: <span style="color:#e6db74">&#34;underjord.io&#34;</span>}
map_of_atoms_without_sugar <span style="color:#f92672">=</span> %{<span style="color:#e6db74">:username</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#34;lawik&#34;</span>, <span style="color:#e6db74">:site</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#34;underjord.io&#34;</span>} <span style="color:#75715e"># Removing the syntactic sugar for colons</span>
struct <span style="color:#f92672">=</span> %<span style="color:#a6e22e">MyStruct</span>{<span style="color:#e6db74">username</span>: <span style="color:#e6db74">&#34;lawik&#34;</span>, <span style="color:#e6db74">site</span>: <span style="color:#e6db74">&#34;underjord.io&#34;</span>} <span style="color:#75715e"># very special map</span>

<span style="color:#75715e"># erlang compatibility</span>
keyword_list <span style="color:#f92672">=</span> [<span style="color:#e6db74">username</span>: <span style="color:#e6db74">&#34;lawik&#34;</span>, <span style="color:#e6db74">site</span>: <span style="color:#e6db74">&#34;underjord.io&#34;</span>] <span style="color:#75715e"># the old-school Erlang map, but syntatically sugared</span>
same_but_in_lists_and_tuples <span style="color:#f92672">=</span> [{<span style="color:#e6db74">:username</span>, <span style="color:#e6db74">&#34;lawik&#34;</span>}, {<span style="color:#e6db74">:site</span>, <span style="color:#e6db74">&#34;underjord.io&#34;</span>}] <span style="color:#75715e"># free from syntactic sugar</span>
charlist <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;lawik&#39;</span> <span style="color:#75715e"># the old-school Erlang string</span>
charlist_in_lists_and_numbers <span style="color:#f92672">=</span> [<span style="color:#ae81ff">108</span>, <span style="color:#ae81ff">97</span>, <span style="color:#ae81ff">119</span>, <span style="color:#ae81ff">105</span>, <span style="color:#ae81ff">107</span>]</code></pre></div>
  </div>

<p>Interesting sugar around these types.</p>
<p>Strings are binaries that fit within the constraints of a UTF-8-encoded binary. String and binary concatenation is <code>&quot;foo&quot; &lt;&gt; &quot;bar&quot;</code>. String interpolation is <code>&quot;Hello #{name} and welcome.&quot;</code> where <code>name</code> is a value or expression that converts to a string through a particular Protocol.</p>
<p>Lists have a syntax convenience for prepending. <code>new_list = [ new_value | old_list ]</code> will do it. It will return when we talk pattern matching. Lists are implemented as linked lists (not ideal, it is known) so appending is much less performant. List concatenation is <code>a ++ b</code> . Most operations are found in either the <code>List</code> or <code>Enum</code> modules.</p>
<p>Maps have a syntax convenience for updating an existing field. <code>new_map = %{ old_map | atom_key: new_value}</code> makes for a pretty simple process. Also works for structs. Overall map operations live in the <code>Map</code>  and <code>Enum</code> modules.</p>
<p>Keyword lists are very common in Erlang for options. Elixir added some nice sugar for that purpose. A list of tuples can be created with <code>[key1: 5, key2: 6]</code> and when calling a function which takes options as a Keyword list as the last argument you can drop into what feels like a python kwargs situation: <code>my_function(&quot;regular value&quot;, force: true, timeout: 553)</code> No double splat to speak of though.</p>
<p>Pipes are kind of neat:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyModule.Pipage</span> <span style="color:#66d9ef">do</span>
  <span style="color:#66d9ef">def</span> new <span style="color:#66d9ef">do</span>
    %{}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> add_defaults(thing) <span style="color:#66d9ef">do</span>
    thing
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(<span style="color:#e6db74">:timeout</span>, <span style="color:#ae81ff">5000</span>)
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(<span style="color:#e6db74">:weather</span>, <span style="color:#e6db74">&#34;rainy&#34;</span>)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> set_name(thing, name) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(thing, <span style="color:#e6db74">:name</span>, name)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> do_all_of_it <span style="color:#66d9ef">do</span>
    new()
    <span style="color:#f92672">|&gt;</span> add_defaults()
    <span style="color:#f92672">|&gt;</span> set_name(<span style="color:#e6db74">&#34;lawik&#34;</span>)
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>So pipes allow passing a value through and changing it. They don&rsquo;t deal with failure or anything like that but in functional life there are many cases where this just becomes much more readable. Not that there is only one type of pipe <code>|&gt;</code> and it feeds the output into the first argument of the next function. This worked nicely for my brain. It will upset many established FP folks that want all kind of pipes and arrows. I also know there is some history with piping to the last position and I know Elm does it that way. I think this is more approachable but it might be the Python <code>self</code> OOP bleeding through rather than what humans expect as a general rule.</p>
<p>Let&rsquo;s make an example that is more scripting-oriented and uses the Elixir standard library.</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#e6db74">&#34;~&#34;</span>
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Path</span><span style="color:#f92672">.</span>expand()
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Path</span><span style="color:#f92672">.</span>join(<span style="color:#e6db74">&#34;.config/my_app&#34;</span>)
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>mkdir_p!()
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>ls!()</code></pre></div>
  </div>

<p>This shows one of two interesting conventions for Elixir function names. The <code>!</code> at the end of a function indicates a function that will raise an error on failure. Usually they have a sibling function without the bang that will return a tuple of <code>{:ok, result}</code> or <code>{:error, reason}</code>. The other one is <code>?</code> at the end of a function to indicate it typically provides a boolean result. This is not enforced, it is only a convention.</p>
<p>Why these result tuples? We don&rsquo;t have static typing and Result types, monads or what have you in Elixir. The ok/error tuple is a convention from Erlang and is essentially an informal Result type. Elixir didn&rsquo;t upend the convention which means interop with Erlang code feels normal.</p>
<p>The way they are generally used is with pattern matching. To extract the value and throw a MatchError if it does not you can use a bare pattern match inline in a function.</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">{<span style="color:#e6db74">:ok</span>, status} <span style="color:#f92672">=</span> <span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>stat(my_path)</code></pre></div>
  </div>

<p>Or you can do a case statement:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>stat(my_path) <span style="color:#66d9ef">do</span>
  {<span style="color:#e6db74">:ok</span>, %{<span style="color:#e6db74">type</span>: <span style="color:#e6db74">:directory</span>}} <span style="color:#f92672">-&gt;</span> <span style="color:#75715e"># do thing</span>
  {<span style="color:#e6db74">:ok</span>, _unused} <span style="color:#f92672">-&gt;</span> <span style="color:#75715e"># do other thing</span>
  {<span style="color:#e6db74">:error</span>, <span style="color:#e6db74">:enoent</span>} <span style="color:#f92672">-&gt;</span> <span style="color:#75715e"># specific error</span>
  _ <span style="color:#f92672">-&gt;</span> <span style="color:#75715e"># any other result</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>You can also do nice things using the <code>if</code> macro, the <code>with</code> statement and a bunch of other things. I won&rsquo;t attempt to cover all the syntax. The <code>for</code> macro comprehension thing is wildly feature-filled. One very nice place to use pattern matching is in function heads. Elixir supports overloading of a sort.</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.MyModule</span> <span style="color:#66d9ef">do</span>
	<span style="color:#66d9ef">def</span> eat_value(<span style="color:#66d9ef">nil</span>) <span style="color:#66d9ef">do</span>
		{<span style="color:#e6db74">:error</span>, <span style="color:#e6db74">:hates_nil</span>}
	<span style="color:#66d9ef">end</span>

	<span style="color:#66d9ef">def</span> eat_value(%{<span style="color:#e6db74">style</span>: <span style="color:#e6db74">:tasty</span>}), <span style="color:#e6db74">do</span>: {<span style="color:#e6db74">:ok</span>, <span style="color:#e6db74">:tasty</span>}
	<span style="color:#66d9ef">def</span> eat_value(%{<span style="color:#e6db74">flavor</span>: any_flavor}), <span style="color:#e6db74">do</span>: {<span style="color:#e6db74">:ok</span>, any_flavor}

	<span style="color:#66d9ef">def</span> eat_value(%{} <span style="color:#f92672">=</span> any_map_or_struct) <span style="color:#66d9ef">do</span>
		{<span style="color:#e6db74">:ok</span>, <span style="color:#e6db74">:tasted_fine</span>}
	<span style="color:#66d9ef">end</span>

	<span style="color:#66d9ef">def</span> eat_value([first_value, <span style="color:#f92672">|</span> _rest_of_list]) <span style="color:#66d9ef">do</span>
		{<span style="color:#e6db74">:ok</span>, first_value}
	<span style="color:#66d9ef">end</span>

	<span style="color:#66d9ef">def</span> eat_value(num) <span style="color:#f92672">when</span> is_integer(num) <span style="color:#f92672">or</span> is_float(num) <span style="color:#66d9ef">do</span>
		{<span style="color:#e6db74">:ok</span>, <span style="color:#e6db74">&#34;I will consider it a </span><span style="color:#e6db74">#{</span>num<span style="color:#e6db74">}</span><span style="color:#e6db74">.&#34;</span>}
	<span style="color:#66d9ef">end</span>

    <span style="color:#75715e"># Convention dictates _ to ignore a value in a match</span>
	<span style="color:#66d9ef">def</span> eat_value(_), <span style="color:#e6db74">do</span>: {<span style="color:#e6db74">:ok</span>, <span style="color:#e6db74">:nothing_special</span>}
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>This fundamentally compiles down to a case statement in a single function and if none of the cases match it will raise a specific error. You can also apply guards for numeric ranges and such. This example has matching for; Specific value <code>nil</code>. Value of a key in a map. Value of a key in a map to a binding and using it later. Head of a list.</p>
<p>The <code>with is_integer(..</code> stuff is a guard clause. It also shows the convention of <code>_</code> marking unused bindings, either with a name like <code>_bla</code> or just the plain <code>_</code>.</p>
<p>Anonymous/lambda functions are an important thing in most functional programming and we certainly have those.</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e"># We can bind an existing function to a value with this syntax</span>
referenced_function <span style="color:#f92672">=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>stat<span style="color:#f92672">/</span><span style="color:#ae81ff">1</span>

<span style="color:#75715e"># Anonymous function, full syntax</span>
anon <span style="color:#f92672">=</span> <span style="color:#66d9ef">fn</span> arg1, arg2 <span style="color:#f92672">-&gt;</span>
	arg1 <span style="color:#f92672">+</span> arg2
<span style="color:#66d9ef">end</span>

<span style="color:#75715e"># Anonymous function, short syntax</span>
anon <span style="color:#f92672">=</span> <span style="color:#f92672">&amp;</span> &amp;1 <span style="color:#f92672">+</span> &amp;2

<span style="color:#75715e"># Anonymous function, multiple clauses</span>
anon <span style="color:#f92672">=</span> <span style="color:#66d9ef">fn</span>
  {<span style="color:#e6db74">:ok</span>, result} <span style="color:#f92672">-&gt;</span> result <span style="color:#f92672">+</span> <span style="color:#ae81ff">5</span>
  {<span style="color:#e6db74">:error</span>, reason} <span style="color:#f92672">-&gt;</span> {<span style="color:#e6db74">:error</span>, <span style="color:#e6db74">:bad</span>, reason}
  other <span style="color:#f92672">-&gt;</span> other
<span style="color:#66d9ef">end</span>

<span style="color:#75715e"># Calling an anonymous function</span>
anon<span style="color:#f92672">.</span>(my_first_arg)</code></pre></div>
  </div>

<p>These are commonly used in pipelines for things like the very important Enum module:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>ls!()
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>map(<span style="color:#66d9ef">fn</span> filename <span style="color:#f92672">-&gt;</span>
  <span style="color:#a6e22e">Path</span><span style="color:#f92672">.</span>join(my_path, filename)
<span style="color:#66d9ef">end</span>)</code></pre></div>
  </div>

<p>Something that is not Elixir syntax so much as something you run into using libraries within Elixir are macros and DSLs (domain-specific-languages) built from macros. Elixir does not allow a ton of hiding things in normal code. The code is explicit and mostly goes in a straight line. To avoid some things becoming very unwieldy, such as the Phoenix web framework&rsquo;s routing and the Ecto database library&rsquo;s schema/migration definitions they have built small sets of macros that generate the relevant functions for you. This is one of few places where I find things often feel a bit woo-woo and magical. But it is almost exlusively where the alternative would be worse, either for a technical reason or for a human reason.</p>
<p>So when you run into things that don&rsquo;t feel like normal Elixir code. Odds are you are dealing with macros. They can define custom blocks and they can define custom keywords that are mostly like functions but actually expanded at compile-time.</p>
<p>Example of an Ecto database schema:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.MyUser</span> <span style="color:#66d9ef">do</span>
	<span style="color:#f92672">use</span> <span style="color:#a6e22e">Ecto.Schema</span>

	schema <span style="color:#e6db74">&#34;users&#34;</span> <span style="color:#66d9ef">do</span>
		field <span style="color:#e6db74">:username</span>, <span style="color:#e6db74">:string</span>
		field <span style="color:#e6db74">:password</span>, <span style="color:#e6db74">:string</span>, <span style="color:#e6db74">redact</span>: <span style="color:#66d9ef">true</span>
		timestamps()
	<span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>This generates a model with everything the database layer needs to know about the schema. Your <code>MyApp.MyUser</code> module will have a <code>__schema__()</code> function. <code>use Ecto.Schema</code> is what brings that magic into your module and your subsequent calls to the macro <code>schema</code>, <code>field</code> and <code>timestamps</code> do the rest.</p>
<p>This brings us to one thing that is a bit messy but learnable. <code>alias</code>, <code>import</code>, <code>require</code> and <code>use</code>. This is <a href="https://elixir-lang.org/getting-started/alias-require-and-import.html">also covered in the official guide</a> which is where I learned Elixir mostly.</p>
<ul>
<li><code>alias</code> shortens the name of a module or lets you change the name you use for a module.</li>
<li><code>require</code> makes macros available from a particular function.</li>
<li><code>import</code> brings in functions AND macros.</li>
<li><code>use</code> lets the specific module inject any code functions through macro madness.</li>
</ul>
<p>Erlang interop deserves a mention. Both Elixir and Erlang module and function names are atoms. <code>MyApp.MyUser</code> is actually syntactic sugar for <code>:Elixir.MyApp.MyUser</code> which is just an atom. So Erlang interop looks like this:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#e6db74">:erlang_module_name</span><span style="color:#f92672">.</span>function_name(arg1, arg2)
<span style="color:#75715e"># OR actual example</span>
im <span style="color:#f92672">=</span> <span style="color:#e6db74">:egd</span><span style="color:#f92672">.</span>create(<span style="color:#ae81ff">200</span>, <span style="color:#ae81ff">200</span>)</code></pre></div>
  </div>

<p>This is already a bunch so I will end it. I have not covered all syntax in the language but I think I&rsquo;ve given it a fair shake. I hope this is beneficial to you if you are curious to try the language or figure out if it is something you might enjoy.</p>
<hr>
<p>If you have questions, comments or concerns you can reach me by my email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on the socials <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Unpacking Elixir: Concurrency</title>
      <link>https://underjord.io/unpacking-elixir-concurrency.html</link>
      <pubDate>Fri, 25 Aug 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/unpacking-elixir-concurrency.html</guid>
      <description>Elixir is the thing I do most of my public writing and speaking about. It is my default programming language for the last 5-6 years. It suits my brain. Performs well for the kind of work I typically do. And using it I have experienced very few drawbacks. Rather than writing yet another post trying to widely summarize what I think is beneficial about the language I want to try and go a bit deeper on one particular aspect I like.</description>
      <content:encoded><![CDATA[ <p>Elixir is the thing I do most of my public writing and speaking about. It is my default programming language for the last 5-6 years. It suits my brain. Performs well for the kind of work I typically do. And using it I have experienced very few drawbacks. Rather than writing yet another post trying to widely summarize what I think is beneficial about the language I want to try and go a bit deeper on one particular aspect I like. Not incredibly technically deep, but unpack the concepts more thoroughly. I hope this will be a series of stand-alone posts. We will see. This time we unpack Elixir concurrency (and parallelism).</p>
<p>My origin story is steeped in PHP. In PHP the concurrency model is pretty much whatever your web server (mod_php) or app server (php-fpm?) decides it should be. PHP generally assumes a shared-nothing model and each request is clear of whatever work is done in the others. In this regard work can run concurrently but they can also not share anything without reaching for external files, databases, queues or similar.</p>
<p>Then I went to Python. Over in Python .. it depends. It is really bad at concurrency. Ordinary threading is available but enormously constrained by the GIL (Global Interpreter Lock). The best solution seems to be just running more interpreters or using Python multiprocess which I believe forks new processes. I liked Python. I didn&rsquo;t enjoy trying to do performant concurrency in Python though.</p>
<p>Elixir builds on Erlang. Fundamentally everything I&rsquo;m writing about here is thanks to the Erlang virtual machine, aka. the BEAM, which has this stuff at its core. I will write in the context of Elixir. The things that are great here are also great about Erlang. Except possibly the Task module, which is kind of neat. That could also be done with Erlang but I don&rsquo;t know if anyone has.</p>
<p>The BEAM is made to run a ton of lightweight processes. A type of green thread if you will. They take very little time to spawn, they take very little memory. A system can have millions of them. And if you need many millions of them you can bump the default limit. They are fundamentally a different thing from OS processes and threads. The BEAM itself is scheduled by the OS but on startup the BEAM creates threads for its schedulers. One per available CPU core by default. These in turn take care of scheduling processes. This is the source of concurrency and parallelism.</p>
<p>We won&rsquo;t go into detail on the Actor model and how that works here but that is built on top of the fundamental primitive of a process. A process runs a function, the function only has access to its own local variables and private copies of whatever arguments were passed in. This is Functional Programming so everything is immutable. A process cannot change the values in another function. This function running in a process has two cool special bits of API it can use. It can receive messages and it can send messages. If the function runs to completion the Process goes bye-bye. If the process runs in an eternal loop checking for messages and only acts if a message comes in? Then we end up talking about the Actor model again which I said we won&rsquo;t do. So lets move on.</p>
<p>A process runs on one of the schedulers. If many processes taking a long time creates a queue on one scheduler the other schedulers can start stealing work to balance it out. If a particular process goes through a lot of function calls (internally called reductions), say 4000 for example, without completing then the scheduler will pre-empt it. That means freeze it, mid-run, and send it back to the end of the work queue. This is called pre-emptive multitasking and it is what Linux does to the OS processes. In the BEAM nothing gets to hold up the line for very long. This is how it ensures low latency which is another topic I hope to unpack at a different time.</p>
<p>The immutable FP nature of Elixir and Erlang are pragmatic choices that were made to achieve a bunch of goals. It predates multi-core machines by a fair bit and they were targeting a different kind of concurrency, distributed systems made up of multiple machines, a cluster. The end result was accidentally convenient for achieving incredibly concurrency and parallelism on multi-core machines. Processes do not share state, they can only communicate asynchronously through message passing. This ensures it is safe to run them at the same time. Same time as in concurrent, executing the tasks during the same period of time. Also parallel, executing the tasks at the same time. All APIs that touch files or other constrained resources, stdin/stdout and such, have mechanisms for ensuring that contention is under control. The Erlang folks have spent a lot of time getting this right.</p>
<p>When I write my code I generally don&rsquo;t need to care about this very much. Most web requests take place inside a single process. They might communicate on through to a database connection pool which involves more processes but overall I don&rsquo;t need to care about the underlying primitives. I also don&rsquo;t need to think about the Node.js way of racing to IO. Node.js can be highly concurrent. CPU-bound work is fine on the BEAM. It won&rsquo;t disrupt the ability of other processes to get work done, both because multiple cores can do the work but also because if I spend many CPU cycles I will be pre-empted and other work can slip by. This is all handled by the web server I use, more than by me.</p>
<p>If I do want to do concurrent work there is the usual async/await mechanisms, implemented in the Task module. Not as weird as in JS. They just return a Task reference which is a helpful abstraction on top of the Process ID or PID. Then you can await on it to get a result or await on multiple to get multiple results. There is also a wicked function called Task.async_stream which will take an enumerable (list, map, stream or similar) and run a Task for each entry as a lazy stream. By default it has a concurrency matching the number of available cores (just like the schedulers). This is essentially a fancy shortcut for using all your machine has to offer in the service of getting the work done for tasks that are embarrassingly concurrent. It is very fun to use when bodging together scripts that you want to go fast.</p>
<p>Compared to PHP I have many more tools for interacting across the work that happens in my system. Process registries and groups, PubSub mechanisms for message passing, memory-based shared state accessed through safe mechanisms (ETS for example).</p>
<p>Compared to Python I can do multiple things at once in simple and useful ways. In a language that is as expressive and as dynamic (in the places I care about).</p>
<p>Compared to Javascript I can do multiple things at once easily without worrying about CPU-bound work holding up other requests and such. I get actual immutability instead of working really hard to pretend I do.</p>
<p>These comparisons are of course based on my understandings and experiences with the languages. I&rsquo;ve stuck with languages I have some experience with but I don&rsquo;t hang out with them on a regular basis currently. If I have misrepresented any of them, do let me know.</p>
<p>In summary; great options for concurrency and parallelism are not an afterthought in Elixir and Erlang. They are built into the very foundation of the runtime and the languages surface it to us. This matters for performance and practicality. Doing concurrency on the BEAM is not punishing, it is not risky. It is just what you end up doing by using it.</p>
<hr>
<p>If you have questions, comments or concerns you can reach me by my email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on the socials <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>The many states of Elixir</title>
      <link>https://underjord.io/the-many-states-of-elixir.html</link>
      <pubDate>Tue, 13 Jun 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/the-many-states-of-elixir.html</guid>
      <description>We of the blessed church of Functional Programming pride ourselves on our immutability, purity and lack of noxious side-effects. We do not mutate the state. We only produce new and better state. Deterministic state. Correct state. The best state.
Except that I work in Elixir. Elixir is built on Erlang. I like to think Erlang being FP is mostly an accidental side-effect of trying to solve a bunch of complex requirements around distributed systems, fault tolerance and consistently low latency (this predates the concurrency story, another kind-of-accident).</description>
      <content:encoded><![CDATA[ <p>We of the blessed church of Functional Programming pride ourselves on our immutability, purity and lack of noxious side-effects. We do not mutate the state. We only produce new and better state. Deterministic state. Correct state. The best state.</p>
<p>Except that I work in Elixir. Elixir is built on Erlang. I like to think Erlang being FP is mostly an accidental side-effect of trying to solve a bunch of complex requirements around distributed systems, fault tolerance and consistently low latency (this predates the concurrency story, another kind-of-accident). Elixir and Erlang are decidedly loose in structure.</p>
<p>I get the sense that they appeal to some specific flavors of FP enthusiasts only because they are FP and have undeniable practical applications. Accepted due to being (relatively, practically) successful and interesting. In spite of not being quite strict, rigorous or firm enough to give a true hard FP lover the rigidity they truly desire. Moving on from that imagery. Suffice to say Elixir is not pure.</p>
<p>You can write parts of your application that are generally pure and that has great advantages. However the language and plattform also offers a ton of tools to work differently with regards to state and mutability.</p>
<p>There are wild side effects for logging, IO, processes, message passing and many other things. It is a very practical language. Often we also need mutable state.</p>
<p>Surely not!?</p>
<p>Yes. Quite.</p>
<h2 id="ets">ETS</h2>
<p>I would say that this is the blessed path to mutability in Erlang-land. If you want to store something in memory and you have to be able to read and write to it kind of ad-hoc. <a href="https://www.erlang.org/doc/man/ets.html">ETS</a> can help you out. It is a bit of swiss army knife all told and has a bunch of different types of tables (bags, duplicate bags, sets and probably more, I’m going from memory).</p>
<p>A common misstep in Elixir and Erlang is to implement an in-memory store as a GenServer. This can be the right move if serial access is important, if what you need is a bottle-neck where you can do something important. But in many cases what you would probably rather have is an ETS table and you might enable options for improved read concurrency or improved write concurrency.</p>
<p>ETS is a super useful tool. It is also very much a way to get mutable local or global state.</p>
<p>I will gloss over DETS because that’s IO (ETS on file) and I will skip Mnesia because it is a whole-ass database.</p>
<h2 id="persistent-term">Persistent Term</h2>
<p>Have some global data that rarely, but occasionally, changes? <a href="https://www.erlang.org/doc/man/persistent_term.html">Persistent term</a>. You get really great read performance. Updating or deleting a term gets really expensive.</p>
<p>A very pragmatic solution for an in-memory store.</p>
<h2 id="process-dictionary">Process dictionary</h2>
<p>I guess this is the Erlang process equivalent of something being thread-local. It is a key value mechanism that lives and dies with your process. Actually we had Mat Trudel of <a href="https://github.com/mtrudel/bandit">Bandit</a> fame on <a href="https://beamrad.io">BEAM Radio</a> just now so there will be an episode soon where he outs his dirty, filthy use of this local mutable data in his fabulous web server.</p>
<p>These are also used for dynamic repos in Ecto if I’m not mistaken which can be kind of surprising. I believe they are also used for the Ecto sandbox which means it can be kind of funny crossing process boundaries. Depends on your options and what you need.</p>
<p>Very impure, very practical. I find myself reaching for it when I need to be able to switch some slight behavior for testability or similar and don’t necessarily have a good way to pass config around. Crimes.</p>
<h2 id="application-environment">Application environment</h2>
<p>Now we are getting worse. An <a href="https://hexdocs.pm/elixir/main/Application.html">Erlang application</a> is something like a library or particular app project. Each app can store configuration and assorted dodgy bogus in their own ”environment”.</p>
<p>It is intended for reading but of course you can write to it. I couldn’t tell you what the performance characteristics are because the act is a crime and we shouldn’t benchmark crimes. If you do benchmark the crime, let me know.</p>
<p>I have done this in Livebook where it is a viable way of setting up configuration during the setup block. I have also done it in relation to particular tests at times.</p>
<h2 id="system-environment">System environment</h2>
<p>Environment variables are great. But we usually treat them like constants. No more! You can trivially change environment variables and suddenly you have an awesome KV store that you should absolutely not use in this way. Rad.</p>
<h2 id="update">Update!</h2>
<p>Karl-Johan Nilsson immediately mentioned <a href="https://www.erlang.org/doc/man/counters.html">counters</a> and <a href="https://www.erlang.org/doc/man/atomics.html">atomics</a> after I posted this. Shows what I know.</p>
<p>—-</p>
<p>I think that covers the mutable states I know of that makes Elixir and Erlang practically capable but ever so impure. If I have horribly misrepresented something or smeared your favorite FP language, no worries. You can complain at <a href="mailto:lars@underjord.io">lars@underjord.io</a>, berate me on Mastodon via @lawik@fosstodon.org or even like, and subscribe <a href="https://youtube.com/c/underjord">my latest video</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Scripting with Elixir</title>
      <link>https://underjord.io/scripting-with-elixir.html</link>
      <pubDate>Mon, 12 Jun 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/scripting-with-elixir.html</guid>
      <description>I was a Python developer for some time and one great joy of Python is that you have an expressive language that you can use for your serious apps as well as for your hacky little one-off script or bespoke pieces of automation. By expressive I mean that typing very little can give you a lot of progress towards your result. I&amp;rsquo;ve scripted a fair bit in Python. Now I do it with Elixir.</description>
      <content:encoded><![CDATA[ <p>I was a Python developer for some time and one great joy of Python is that you have an expressive language that you can use for your serious apps as well as for your hacky little one-off script or bespoke pieces of automation. By expressive I mean that typing very little can give you a lot of progress towards your result. I&rsquo;ve scripted a fair bit in Python. Now I do it with Elixir.</p>
<p>Overall I would recommend scripting in whatever language you are most comfortable in that is at least reasonably comfortable for scripting. A quick script benefits from a low barrier between thought and execution. Use what makes sense for you. I find Elixir surprisingly good as a scripting language ever since the introduction of <code>Mix.install</code>.</p>
<p>Elixir actually answers a question that Python, to my knowledge does not, in this regard. While you do need the language and its runtime installed, how about dependencies? I usually globally installed <code>requests</code> in Python, maybe an AWS SDK library, because I&rsquo;d need them eventually.</p>
<p>In Elixir each script can briefly define dependencies.</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e">#!/usr/bin/env elixir</span>

<span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span>install([<span style="color:#e6db74">:req</span>, <span style="color:#e6db74">:jason</span>])

<span style="color:#e6db74">&#34;https://api.github.com/users/lawik&#34;</span>
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Req</span><span style="color:#f92672">.</span>get!()
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>inspect()</code></pre></div>
  </div>

<p>The <code>Mix.install</code> part installs the top two things I need. An easy-to-use HTTP client (Erlang <code>httpc</code> works but is kinda clunky) and a JSON library. I could similarly pull in CSV-parsers or what-have-you. In Python you have JSON and CSV built in but you generally do want a better HTTP client.</p>
<p>I have occasionally found Elixir to be a bit less convenient for doing nice sloppy dictionaries as mutable state is less conveniently available and if I&rsquo;m parsing a bunch of files or larger structures and want to build up convenient key/values for them I often want to exfiltrate some key and value out of multiple nested loops. This usually leads to Enum.reduce in Elixir and it is less convenient. It totally works though. And if you want to be really gnarly you can do <code>Process.put/2</code> and <code>Process.get/2</code>. I bet you could do it reasonably comfortable with an ETS table but I haven&rsquo;t really used those while scripting. Yet.</p>
<p>Let&rsquo;s compare.</p>

  <div class="code  python " >
    <div class="meta">
      <span class="language">python</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e">#/usr/bin/env python3</span>
index <span style="color:#f92672">=</span> {}

<span style="color:#66d9ef">with</span> f <span style="color:#f92672">in</span> open(<span style="color:#e6db74">&#34;file.json&#34;</span>):
	<span style="color:#66d9ef">for</span> item <span style="color:#f92672">in</span> json<span style="color:#f92672">.</span>loads(f):
		<span style="color:#66d9ef">for</span> subitem <span style="color:#f92672">in</span> item[<span style="color:#e6db74">&#34;children&#34;</span>]:
			index[subitem[<span style="color:#e6db74">&#34;id&#34;</span>]] <span style="color:#f92672">=</span> subitem</code></pre></div>
  </div>

<p>Versus</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e">#/usr/bin/env elixir</span>

<span style="color:#e6db74">&#34;file.json&#34;</span>
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>read!()
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Jason</span><span style="color:#f92672">.</span>decode!()
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>reduce(%{}, <span style="color:#66d9ef">fn</span> item, index <span style="color:#f92672">-&gt;</span>
    <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>reduce(item[<span style="color:#e6db74">&#34;children&#34;</span>], index, <span style="color:#66d9ef">fn</span> subitem, index <span style="color:#f92672">-&gt;</span>
		<span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(index, subitem[<span style="color:#e6db74">&#34;id&#34;</span>], subitem)
    <span style="color:#66d9ef">end</span>)
<span style="color:#66d9ef">end</span>)</code></pre></div>
  </div>

<p>It can get a bit involved in nested cases. Especially if you need to operate on multiple data structures and such. Lacking mutability means paying more attention to data structures and how they flow through functions.</p>
<p>Overall though I&rsquo;ve also found that whenever I do something slightly time-consuming I can optimize it significantly by using a single <code>Task.async_stream/3</code> which will effectively utilize all my cores as it works through whatever list I&rsquo;m processing. Getting this equivalent thing going in Python is significantly more painful.</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e">#!/usr/bin/env elixir  </span>
  
<span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span>install([<span style="color:#e6db74">:req</span>, <span style="color:#e6db74">:jason</span>])  
  
<span style="color:#e6db74">&#34;https://api.github.com/users/lawik/repos&#34;</span>  
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Req</span><span style="color:#f92672">.</span>get!()  
<span style="color:#f92672">|&gt;</span> then(<span style="color:#f92672">&amp;</span> &amp;1<span style="color:#f92672">.</span>body)
<span style="color:#75715e"># And now we get very parallel</span>
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Task</span><span style="color:#f92672">.</span>async_stream(<span style="color:#66d9ef">fn</span> %{<span style="color:#e6db74">&#34;stargazers_url&#34;</span> <span style="color:#f92672">=&gt;</span> url} <span style="color:#f92672">-&gt;</span>  
 url  
 <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Req</span><span style="color:#f92672">.</span>get!()  
 <span style="color:#f92672">|&gt;</span> then(<span style="color:#f92672">&amp;</span> &amp;1<span style="color:#f92672">.</span>body)  
<span style="color:#66d9ef">end</span>)  
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>flat_map(<span style="color:#66d9ef">fn</span> result <span style="color:#f92672">-&gt;</span>  
 <span style="color:#66d9ef">case</span> result <span style="color:#66d9ef">do</span>  
   {<span style="color:#e6db74">:ok</span>, stargazers} <span style="color:#f92672">-&gt;</span>  
     stargazers  
   err <span style="color:#f92672">-&gt;</span>  
     <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>inspect(err, <span style="color:#e6db74">label</span>: <span style="color:#e6db74">&#34;Error fetching stargazers&#34;</span>)  
     []  
 <span style="color:#66d9ef">end</span>  
<span style="color:#66d9ef">end</span>)  
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>uniq()  
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>count()  
<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>inspect()</code></pre></div>
  </div>

<p>When writing out this script and trying it I hit the Github API limit for unauthenticated calls on like my third or forth run :)</p>
<p>I also like that most of the Elixir syntax ends up being basic function calls. Instead of <code>with f in open</code> to open a file I&rsquo;ll call the <code>File</code> module and the <code>read!</code> function. The exclamation point (or bang) is a special convention. In most cases there are matching functions without that symbol and those generally return an <code>{:ok, content}</code> tuple or an error tuple on failure. The bang functions will raise an error on failure. These bang functions make for very convenient pipelines. If you need to operate on the failure case it is generally better to use the regular one and a case statement.</p>
<p>This is very Erlang &ldquo;let it crash&rdquo; but applied in a different context. By using them in a script we are saying that any failure to exeute the function has no reasonable mitigation or that we don&rsquo;t care to spend time defending against small deviations from expectation. Handling all conceivable possible outcomes of attempting to read a file is generally a waste of time and effort in a script that requires the file to exist. Just error out if it doesn&rsquo;t.</p>
<p>First run can be a bit slow if the Mix.install needs to pull down and compile the packages (just as the initial pip install would) but subsequent runs will be reasonably snappy.</p>
<p>If you want more examples of doing scripting in Elixir I suggest looking at <a href="https://github.com/wojtekmach/mix_install_examples/tree/main">Wojtek Mach&rsquo;s Mix Install Examples repo</a>. It has a lot of things. Wallaby for driving a browser. A single-file web server with Phoenix LiveView. Or why not write a NIF in C. Lots of fun stuff in there.</p>
<p>This is intentionally a shorter post. Hope you enjoyed it and it made you a bit curious. If you have thoughts, questions or anything of the sort you can reach me at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or as @lawik@fosstodon.org on the Fediverse.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Why do ML on the Erlang VM?</title>
      <link>https://underjord.io/why-ml-on-erlang.html</link>
      <pubDate>Fri, 09 Jun 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/why-ml-on-erlang.html</guid>
      <description>Another question might be; why do machine learning at all? I&amp;rsquo;m not big into ML/AI though I&amp;rsquo;ve been poking it more recently as the open models, such as Stable Diffusion and the more practical Whisper, caught my curiosity. I have a very decent GPU (3090 Ti) and I&amp;rsquo;ve poked around a bit. I don&amp;rsquo;t consider ML a super exciting solution to all the problems or a harbinger of general artificial intelligence.</description>
      <content:encoded><![CDATA[ <p>Another question might be; why do machine learning at all? I&rsquo;m not big into ML/AI though I&rsquo;ve been poking it more recently as the open models, such as <a href="https://github.com/CompVis/stable-diffusion">Stable Diffusion</a> and the more practical <a href="https://github.com/openai/whisper">Whisper</a>, caught my curiosity. I have a very decent GPU (3090 Ti) and I&rsquo;ve poked around a bit. I don&rsquo;t consider ML a super exciting solution to all the problems or a harbinger of general artificial intelligence. There have been some impressive things done recently for sure. There have also been some honestly grotesque overreach with regards to rights and consent. I&rsquo;m not your AI hype man.</p>
<p>Where I do appreciate it is in very utilitarian things. Whisper is a good example. Live captions and audio-to-text-transcription are both quite labor-intensive. Considering how much audio is being produced in an ongoing fashion there is no chance to make that more accessible and searchable, by hand. Here this model can clearly be a good thing. It can also be used for ill but that&rsquo;s mostly the nature of practical tools. Similarly I&rsquo;ve worked with <a href="https://vic.ai">a startup</a> that processes invoices, receipts and such with ML models to turn the structured but very inconsistent human de facto standard of emailing PDFs to each other into structured data. Understanding and more recently producing data in messy formats, that&rsquo;s where ML seems to beat writing code.</p>
<p>I am genuinely excited that with the Nx project and it&rsquo;s descendants this capability is available in Elixir. The <a href="https://github.com/elixir-nx/bumblebee">Bumblebee project</a> really drives home that you can use this now. I like Elixir and want to see it become more and more capable. For many simpler models it may now be easy enough to just load the model and do inference in your Elixir application instead of needing to offload it elsewhere. And also, without needing to rely on some other party offering an API.</p>
<p>Brief aside, I like it when I can run things myself. It is a good reassuring option to have even when you do rely on a managed provider. It can also be quite the requirement here in the EU with the GDPR and Schrems I-III tearing through what can and can&rsquo;t be done with data. You can often get more polished results with proprietary services but they are not a cure-all and if you rely heavily on them your are taking on a lot of risk.</p>
<h2 id="is-ml-in-elixir-really-a-good-idea">Is ML in Elixir really a good idea?</h2>
<p>Not in Elixir itself. No. Probably not. But Nx really doesn&rsquo;t just execute models in Elixir. It is a bit more involved, we&rsquo;ll get there. For now, let&rsquo;s talk about Python.</p>
<p>Python is the default for Machine Learning projects. It brings some good stuff to the table.</p>
<ul>
<li>High level language, expressive and productive, few low-level concerns</li>
<li>Approachable language, friendly syntax and not heavy on syntax or tricky concepts</li>
<li>Ubiquitous, it is everywhere</li>
<li>Fun to write expressive libraries in</li>
</ul>
<p>Doing ML in Python also has some drawbacks. Most of these are general to Python rather than specific to ML and also recur in similar languages like Ruby. I rather like Python, wrote it  professionally for a number of years. That is to say, there is some love for the language while I&rsquo;m writing about these problems.</p>
<ul>
<li>Efficiency. It can be dog slow and doing any kind of work in Python is not the most efficient.</li>
<li>Concurrency. It is terrible for utilizing multiple cores.</li>
<li>Raw performance. Crunching numbers in Python is not a good idea.</li>
<li>Distributed computing. No aspect of Python makes it particularly suited to distributed computing. Few languages and runtimes are.</li>
<li>Managing complexity. Python applications can get pretty messy and allow some really poor choices to be made.</li>
</ul>
<p>Some of these are related to the language, most are related to the runtime and how it behaves.
There are established solutions for many of these problems. You&rsquo;ll see libraries such as numpy that do number-crunching and they drop into C/C++ to do this efficiently. There are also efforts to unlock the performance problems of the runtime in various ways.</p>
<p>Both concurrency and distribution are other areas where Python either drops into additional infrastructure tooling and/or C/C++ to achieve good results. <a href="https://www.ray.io/">Ray</a> seems like a popular solution and it actually ends up pretty close to the Actor model offered by Erlang, along with distribution and serving concepts similar to what Nx offers.</p>
<h2 id="what-does-elixir-offer">What does Elixir offer?</h2>
<p>Elixir covers similar ground to Python in general. It is a high-level language, it is dynamically typed, it is expressive and friendly. The runtimes are however, very different.</p>
<p>Due to the Erlang virtual machine (the BEAM) Elixir can offer fantastic concurrency and consistently low latency. Erlang was built for soft realtime, distributed systems, fault tolerance and scale.</p>
<p>Elixir inherits and expands on a fundamentally high level design of a Functional Programming style language with the Actor model built in. This limits and clarifies complexity. If you want or need complexity you generally need to more explicitly opt in to it. You can build some gnarly things. But if you write the code that gives the least resistance you&rsquo;ll generally be writing pure functions operating on immutable data.</p>
<p>It is really bad at number crunching. The VM has recently gotten a JIT compiler which lets it be a fair bit better at certain raw number crunching tasks but overall it is not what it is for. This is where Nx, Axon and friends step in and provide a functional Elixir API for building up your computational graphs in a high-level language that will then be executed using XLA on either CPU or GPU as highly optimized code. Erlang has always offered NIFs and a few other escape hatches to do demanding work in native code. This is a purpose-built abstraction on top of that capability with ML as the focus and goal.</p>
<p>Your application will already be better at concurrency and perform more consistently at runtime under the BEAM VM. When you want to start distributing and orchestrating the workloads you should find that it is trivial compared to Python. You don&rsquo;t necessarily need to bring in any particular tooling for it or add infrastructure like message queues. Elixir is already very good at orchestrating work.</p>
<p>Livebook. A tool for interactive, easily reproducible, real-time collaborative code notebooks. Jupyter Notebooks have all sorts of weird drawbacks and are still a great tool. Livebook is a much cleaner design, has features that are unlikely to happen in Jupyter and allows that nice and easy workflow for prototyping and exploring. You should try it out if you want to poke around with Elixir, with or without ML. Easiest way to try Elixir code.</p>
<h2 id="the-vm-is-built-for-runtime">The VM is built for runtime</h2>
<p>Erlang is incredibly powerful in terms of observability. You can inspect your running system in depth and Elixir fully inherits this capability. You can work with the running system in a manner that is incredibly unusual if not entirely unique. Start and stop processes, find and identify bottlenecks and build your system for high levels of fault tolerance.</p>
<p>This will also translate to the ML work. Parts will be harder to introspect as they happen on GPUs or in native code but overall the way work flows through your application or cluster will be observable.</p>
<h2 id="what-is-not-there-yet">What is not there yet?</h2>
<p>Tons of things are potentially missing depending on what you want to do. It is still early days but there is also a lot of activity.</p>
<p>There are many cool models already implemented and drop-in-ready for use in the Bumblebee project. There are already multiple backends for Nx providing options for edge inference (TFLite), alternate general backends (TorchX I believe), mature XLA support and more is coming. I also look forward to the OpenXLA efforts that seem to increasingly support Apple systems with Metal and all that. Those are in progress for Nx as well.</p>
<p>Importing models straight from Python has been a mixed bag from what I understand but I think recently announced Ortex broadens the possibilities significantly for ONNX-packaged models.</p>
<p>A lot vision models are usable through the OpenCV bindings project <a href="https://github.com/cocoa-xu/evision">evision</a> as well.</p>
<h2 id="again-why">Again, why?</h2>
<p>Python was not chosen for Machine Learning. It was an accident of academia that Python became the tool. Some was its merits as a prototyping tool, certainly. But as ML grew it mostly remained due to inertia. People built libraries to cover the deficiencies and so it grew. While I don&rsquo;t mind Python I don&rsquo;t think it is a particularly good tool for this job. It struggles with a number of performance problems in production environments. Every new need in ML necessitates new libraries because you cannot tackle it inside of regular Python.</p>
<p>Elixir has powerful abstractions for distributing work, performing well concurrently and scaling far and wide. Out of the box. And then the performant ML stuff is built as additional libraries that hook into these abstractions cleanly. The focus on immutable data structures and functional programming reduces complexity and avoids the code becoming a big ball of mud while the Actor model provides the tools to build architecturally sound and scalable systems without necessarily bringing in a ton of additional tools and infrastructure.</p>
<p>It is ridiculous how easily I can swing together a Phoenix web app and add some ML smarts to it. José showed some of this in his <a href="https://www.youtube.com/watch?v=g3oyh3g1AtQ">Bumblebee launch demo</a>. I genuinely think Elixir provides strong advantages for companies that have a reason to do ML. It also puts basic ML within reach of the random web dev builder type who just wants a little special something for an app.</p>
<p>Examples:</p>
<ul>
<li>User profile photo? Crop it to the detected face or remove the background and have a transparent cutout to play with. Or turn it into a sketch, a comic style portrait or whatever. U2net has many applications.</li>
<li>Text being written? Use text sentiment analysis to get some understanding of what is being communicated in your app and adapt UI accordingly.</li>
<li>Audio in use? Transcriptions via Whisper are quite straightforward to achieve.</li>
</ul>
<p>Whether you agree or disagree you can reach me over email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or you can poke me on Mastodon where I am @lawik@fosstodon.org.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>ElixirConf EU 2023, Lisbon report</title>
      <link>https://underjord.io/elixirconf-eu-2023-lisbon.html</link>
      <pubDate>Thu, 27 Apr 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/elixirconf-eu-2023-lisbon.html</guid>
      <description>Last week I attended ElixirConf in Portugal and had a lovely time. I&amp;rsquo;ll try to capture my experience of the conference here in this post. For me and for you.
The last ElixirConf I visited was Prague in 2019. Pre-pandemic. I had just gotten into Elixir and kind of decided that I should actually really engage and try to do the whole community thing with this language as it seemed like it had a community of a comprehendable size.</description>
      <content:encoded><![CDATA[ <p>Last week I attended ElixirConf in Portugal and had a lovely time. I&rsquo;ll try to capture my experience of the conference here in this post. For me and for you.</p>
<p>The last ElixirConf I visited was Prague in 2019. Pre-pandemic. I had just gotten into Elixir and kind of decided that I should actually really engage and try to do the whole community thing with this language as it seemed like it had a community of a comprehendable size. I traveled there solo. I was mostly doing Nerves and playing around. I had a low-effort, high-yield client at that moment and plenty of time to experiment. I came home from that conference with a lot of excitement and <a href="/why-am-i-interested-in-elixir.html">I started this blog</a> with some of the stuff I saw there.</p>
<p>Between now and then I&rsquo;ve done a few Code BEAM conferences. They are cool events. But they are different. The events have distinct characters and overall I find ElixirConfs have so far hewn closer to what I want. That said, I plan on hitting as many Code BEAM events as I can.</p>
<p>I missed ElixirConf in London as it was rescheduled to be right at the time of the birth of my second child. I stayed home. He arrived on time. Worth.</p>
<p>This preamble was for context. I had a lot of built up connection with people in this community and this would be the first time I&rsquo;d revisit this dedicated physical gathering since I really stepped into the community. This would be the first time I met a focused group of the Elixir community since I started my blog, newsletter, YouTube channel and the <a href="https://beamrad.io">BEAM Radio</a> podcast. I was very excited and had no idea what to expect.</p>
<p>I also submitted a talk and got it accepted. My first in-person conference talk. Hnnnngh&hellip;</p>
<p>Going to a conference and leaving your partner behind with children ages 1 and 3 is not a small ask. At least not with our children. So I didn&rsquo;t. We invited the in-laws so my wife had some company and brought the whole family to Portugal. We are fortunate enough that it was feasible to do so as a vacation. While I was at the conference they were at some castle. I got lovely pictures of my daughter being a statue. While I was making poor decisions she had help putting them to bed. This was a good move all in all.</p>
<p>We also planned the trip with days before and after for shared vacation time.</p>
<p>Travel went well, kids were good, hotel was nice, Lisbon was lovely.</p>
<p>Since we were there early I also went to the Elixir Meetup two days before the conference. It was in the Bounce office. I encountered the first person who knew me from my online activities. A reader of the Nerves Newsletter specifically. Kinda cool :)</p>
<p>People gave two neat presentations at the meetup. The first one by David Elias about additional concepts you can apply on top of &ldquo;contexts&rdquo; and the second one about using Doom for visualizing supervision strategies and shooting your processes. Fun stuff I thought. Then we headed out for drinks and pizza. Some of the folks we headed out with apparently knew who I was so I handed out my first few stickers. They knew <a href="/team.html#maqbool">Maqbool</a> which is apparently an incredibly common trait. Everyone knows him.</p>
<p>During pizza I was dubbed to be The Lars by André Albuquerque who gave the Doom talk. Apologies to danish Lars of the Netherlands who also spoke at the conference. André was apparently familiar with what I do as well. More stickers were shared. Then we sat around at a kiosk in a park, talking tech and drinking wine until it was quite late. Not a bad first run-in with both the locals and the Elixir community.</p>
<p>Day before the conference I had been invited to the Speaker Dinner in the evening. A very nice gathering of very enthusiastic Elixir people. This place had an incredible density of &ldquo;oh, we&rsquo;ve spoken, I had no idea you had a face&rdquo;. Apparently I&rsquo;m very tall. Or so people kept telling me (it&rsquo;s fine, I don&rsquo;t mind, interesting that it doesn&rsquo;t translate in video). The food was good but comparatively forgettable when all around you are people you have so much shared mindspace with. Like talking to fish in a barrel.</p>
<p>I particularly ended up next to this figure of the name Benjamin Milde. Oh! <code>lostkobrakai</code>, <a href="https://twitter.com/marcelfahle/status/1649370198460203009">everyone knows that guy</a>! Then I also got proper chances to talk to Jeffrey Mathias (of Elixir testing fame), Matt Trudel (of Bandit fame) and Eric Meadows-Jönsson (who is apparently local to Gothenburg, I&rsquo;d missed that). Perhaps least well known to me and the most novel conversation was with Guillaume Duboc. He is the guy doing the research project that will attempt to bring types to Elixir. Now I don&rsquo;t particularly want types for Elixir, I don&rsquo;t hate the idea but I don&rsquo;t feel the need either. He seems incredibly enthusiastic, thoughtful and sincere. Great impression. I&rsquo;m happy that whoever explores that tricky territory has his mindset.</p>
<p>I also found out before then that his talk. The biggest talk of the conference. The most important and probably contentious talk.. was concurrent with mine. Damnit. And now I knew my arch-nemesis for the conference was nice and friendly. Aagh!</p>
<p>Mornings and evenings I rehearsed my talk so that the few souls that couldn&rsquo;t fit in that room with the types talk would hopefully enjoy it. Tears in my eyes. (no, not really)</p>
<p>My team trickled in throughout the days before the conference. I brought <a href="/team.html">the whole Elixir gang</a>.</p>
<p>For this conference I had a plan to try and record people and do some interview kind of stuff. I capture some and I might do something with it. <em>Yes, I have the organizers permission and the permission of everyone who was a direct subject of my filming.</em> But I learned a few things. It is a pain to try and do socializing, going to talks and filming at the same time. It is two things too many. Also the camera rig is a bit annoying. Also, people think you are crew at the event. Also, my gimbal broke? That&rsquo;s under investigation. Second day I did not bring the big camera.</p>
<p>Socializing went fine. I skipped quite a few talks for the benefit of the hallway track. This was also where it became noticeable that people knew who I was. I am not a person who has had any degree of &ldquo;fame&rdquo; before. I generally deal okay with attention, I&rsquo;m not particularly shy or very reserved usually so I wasn&rsquo;t worried about it. It was all in all just fun. People were really nice.</p>
<p>What I wasn&rsquo;t ready for and I don&rsquo;t think I managed to respond fully to is the people who said what I do helps them. That makes me a little bit too happy and I can&rsquo;t fully absorb it. Small things, big things. People had such nice things to say about what I have shared, made and written that it just makes me blush. They all got stickers, that&rsquo;s all I had. Some asked for advice as well. The stickers are probably better quality than the advice but I tried at least. I hope I didn&rsquo;t forget to give anyone who spoke to me about the stuff I do stickers. I probably did. It was occasionally hectic.</p>
<p>If you came up and talked to me, thank you. If I failed to respond or was busy when you tried. I&rsquo;m sorry, don&rsquo;t hesitate to reach out again.</p>
<p>Meeting people from the community that I know or know of was the big thing I had in mind for this conference. I also hoped that some people would recognize my work. I put things out for people to enjoy and it is nice to get some signal that isn&rsquo;t just a view counter. Being a person people <em>know of</em> was interesting and new. I hope I did it gracefully.</p>
<p>The most memorable talk was certainly Evadne Wu&rsquo;s &ldquo;Not Hot Dog Revisited&rdquo;. It was actually kind of close in content to my talk except very different in details and execution. But Membrane, LiveView and ML were all in there. When the organizers ask what you need for your talk it is a boss move to go: &ldquo;Three <em>different</em> hotdogs.&rdquo;</p>
<p>I gave my talk during this first day. Final talk of the day before lightning talks. I think I may have gotten about a hundred of the six hundred physically attending audience. Damn you Guillaume!</p>
<p>I spoke on LiveView with Membrane and actually mostly on creation and creativity in code. The slides and code is <a href="https://github.com/lawik/lively">all available here</a>. There should eventually be a recording published. It is a heavy revision of the talk I gave to Elixir Community Krakow earlier. The nervousness was under control, the track host was lovely, my friends (big shoutout to hypeman Marcel Fahle!) and team showed up and encouraged me. The ML model behaved kinda well, and it mostly failed in fun ways, I remembered most of the talk, I said most of the things, people laughed when it was appropriate. All in all. I feel like the talk landed. I&rsquo;m happy with how it went.</p>
<p>Because of who I am as a person apparently it wasn&rsquo;t a talk and a distinct demo section. I made the entire talk be a demo and tried to make it kind of a performance throughout.</p>
<p>Afterwards people have also given me feedback about the talk that confirmed that to some people it definitely landed. I&rsquo;m thrilled about that. Actually, you over there, guy with the red beard who grabbed me just when I was leaving on the last day at that craft beer place? As far as you got in what you said absolutely made my evening. Whimsy <em>indeed</em>.</p>
<p>That wrapped up the day for me. I was too twitchy after my talk to watch the lightning talks. The evening ended in ramen, rooftop open bar and night club in that order. I hit the pillow at 03:00 (that&rsquo;s AM for you monsters out there).</p>
<p>My daughter kindly did not wake me before 06:00 and agreed to snuggle and snooze until 07:00. I was not in great shape for day two of the conference. Oof. But as a parent I don&rsquo;t often get to be irresponsible. I can&rsquo;t regret it really. I might not repeat it though.</p>
<p>Second day I&rsquo;ll admit I bailed on a few talks just because my body was rebelling against my past behavior. Hangover combined with chronic stomach problems made me a bit less chipper this particular day. I still had a lot of good conversations. Including leads for work, prospective collaborations and all that good stuff.</p>
<p>I also left my big camera at the hotel for the second day as the gimbal was broken and I wasn&rsquo;t in any shape to do a bunch of recording. I will still see if there is a seed of a video in the material I&rsquo;ve captured. I&rsquo;d love it if there was. But lesson for next time is that I need someone focused on taking video if I&rsquo;m going to do that at another one of these. I might also do more vlog-style stuff but then I should have a lighter gimbal and use the small camera.</p>
<p>The final item of the conference day was Chris McCord talking through the current state of LiveView and the path to 1.0. It was a good, rapid-fire presentation of new cool stuff. <a href="https://twitter.com/josevalim/status/1649692143676530693">Checkboxes man</a>. It started both great and awkwardly. Having ChatGPT or GPT4 generate intros for people is not particularly my jam. Having Jim Freeze recite rap lyrics hit an aggressively awkward spot intersecting cringe and awesome. Definitely warmed up the crowd.</p>
<p>The day and conference wrapped on that presentation. It was a good piece of punctuation. The fact that it was a bunch of web, code and practical usage reiterated to me that ElixirConf is very much a builder community. There are bouts of theory and moments of idea stuff. But mostly people build things and share. That&rsquo;s my jam so it suits me great.</p>
<p>The Electric SQL folks pulled together a bunch of folks at a craft beer place after. It turned into a very chill place to have conversations. I would have stayed all night if we hadn&rsquo;t planned dinner with the team and a small artisanal batch of Elixir friends, old and new. Some significant others joined us. It was an absolutely delightful dinner. After it wrapped up a few of us went on for a beer and then the night wound down.</p>
<p>I stayed a couple more days in Lisbon with my family. No Elixir in sight. And then I went home.</p>
<p>I&rsquo;m still digesting the experience. I&rsquo;m looking forward to ElixirConf US.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Physical knobs &amp; userspace drivers in Elixir</title>
      <link>https://underjord.io/userspace-drivers-in-elixir.html</link>
      <pubDate>Wed, 01 Mar 2023 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/userspace-drivers-in-elixir.html</guid>
      <description>I like production gear. Audio, video and .. miscellaneous. I like things that seem like they&amp;rsquo;ll make me oh-so-very productive. I&amp;rsquo;m a sucker prosumer as a hobby. Let&amp;rsquo;s talk about Elgato, Elixir and why I never actually get anything done. This is unfortunately not sponsored by a prosumer brand, or anyone, that&amp;rsquo;d have been something.
Ad-style pitch for a business-thing: I help companies find competent, capable or promising Elixir developers. If you build in Elixir and want to spend less time finding great candidates, reach out.</description>
      <content:encoded><![CDATA[ <p>I like production gear. Audio, video and .. miscellaneous. I like things that seem like they&rsquo;ll make me oh-so-very productive. I&rsquo;m a sucker prosumer as a hobby. Let&rsquo;s talk about Elgato, Elixir and why I never actually get anything done. This is unfortunately not sponsored by a prosumer brand, or anyone, that&rsquo;d have been something.</p>
<p><em>Ad-style pitch for a business-thing: I help companies find competent, capable or promising Elixir developers. If you build in Elixir and want to spend less time finding great candidates, reach out.</em></p>
<img class="blog-image" src="assets/images/blog/streamdeckplus.jpg" alt="Picture of my Stream Deck Plus showing some text and some custom button icons. It looks proud." />
<p>I was first introduced to Elgato for their lighting panels, the arms that hold them up and the Camlink 4K. I was stepping up my streaming/meeting camera situation by putting my then only good camera, a Sony ZV-1, on a stand and improving the lighting.</p>
<p>At the time the camera didn&rsquo;t do USB streaming, recent firmware allows it to do 720p.. I got the Elgato Camlink 4K instead. It takes the HDMI out from the camera and lets me treat it as a webcam via USB. It has been essentially problem-free for me under Linux continuously. It can hang but I&rsquo;d blame my USB controllers more than the hardware, other hardware has also complained.</p>
<p>The Elgato Keylights have been nice enough as well. Hook them up to wifi via the app, a Mac or Windows. Then they are accessible to be controlled and they provide nice diffused light that does the job. As I moved <a href="/the-mac-is-losing-me.html">from Mac to Linux</a> I started controlling them with a Python script using <a href="https://pypi.org/project/leglight/">leglight</a> and also some <a href="https://github.com/lawik/lightswitch">experiments with Nerves</a> and my own <a href="https://github.com/lawik/keylight">keylight library</a>.</p>
<p>I&rsquo;ve been eyeing the Stream Deck because all the Mac productivity podcasts I have a bad habit of following has been hyping it up. Of course. No Linux support. So I held off. Then I saw the <a href="https://www.elgato.com/en/stream-deck-plus">Stream Deck Plus</a> which has <strong>physical knobs</strong>. And then the <a href="https://www.elgato.com/en/stream-deck-pedal">Stream Deck Pedal</a> which is <strong>a pedal</strong>. I am not hard to impress. Unfortunately.</p>
<p>No Linux support from Elgato but I&rsquo;ve been feeling up for a challenge. I checked and saw existing implementations in Python, Node, etcetera and therefore I considered it doable. I&rsquo;ve <a href="/case-study-inky-an-elixir-library.html">ported from Pyton</a> before with good results. And if I got hard stuck it seemed like <a href="https://timothycrosley.github.io/streamdeck-ui/">streamdeck-ui</a> is a thing. (note: no Stream Deck Plus support at time of writing.).</p>
<p>The Stream Decks are  <code>hidraw</code> (not hi draw, hid raw) devices in Linux terminology which essentially means they can be driven with <a href="https://www.kernel.org/doc/html/next/hid/hidraw.html">a very simplistic interface</a> and all the knowledge about the specific device is enshrined in the userspace code that talks to it. Pretty much the same thing in other OS:es. If you are using the Stream Deck software to set things up, there is literally nothing saved on the device. The software is polling the device for activity, generating and pushing new images whenever the screens need to change and such. Even the Stream Deck Plus with a neat variety of controls and interfaces does very little beside retain an image and provide input signals.</p>
<p>I&rsquo;d like my code to work on both Linux and the occasional Mac (I still run an Apple laptop) and I don&rsquo;t mind accidental Windows support. Rather than work with Elixir libraries for straight <a href="https://github.com/nerves-web-kiosk/hidraw">hidraw</a> I looked for something using the library <a href="https://github.com/libusb/hidapi">hidapi</a> which provides cross-platform abstractions with a simple API. I found <a href="https://hex.pm/packages/hid">hid</a> on Hex.pm, unfortunately the source repo is gone.</p>
<p>This is already much more elaborate than the Elgato Keylight. That&rsquo;s a very straight-forward JSON API. This was more challenging but also a lot more interesting.</p>
<p>The hidapi library provides:</p>
<ul>
<li>hid_enumerate - list devices</li>
<li>hid_open - open a device</li>
<li>hid_write - write an &ldquo;output report&rdquo; to a device</li>
<li>hid_read - read an &ldquo;input report&rdquo; from a device</li>
<li>hid_send_feature_report - this is another type of write</li>
<li>hid_get_feature_report - this is another type of read</li>
<li>hid_close</li>
</ul>
<p>There are some other I don&rsquo;t care about as well providing more info about the device and such.</p>
<p>The NIF library I had didn&rsquo;t have all of those hooked up, I believe I needed to add sending feature reports. I imagine the original author just happened to not need that. I also fixed the makefile to work on recent MacOS and actually fixed a bug or two in the C code. As someone who doesn&rsquo;t write C, essentially ever, that felt like an achievement. You can <a href="https://github.com/lawik/hid/commits/master">see my changes here</a>.</p>
<p>This stuff happened during the process of porting. I worked off of <a href="https://github.com/abcminiuser/python-elgato-streamdeck">python-elgato-streamdeck</a> as a reference implementation. Particular props to <a href="https://github.com/abcminiuser/python-elgato-streamdeck/pull/108">this PR for Plus support</a>. I also checked my work against <a href="https://github.com/julusian/node-elgato-stream-deck">node-elgato-stream-deck</a> a few times as I wasn&rsquo;t sure the Python one had it right. Some weird build issue with dynamic shared libraries and ctypes meant I never actually got the Python one running.</p>
<p>The device offers the following functionality:</p>
<ul>
<li>8 buttons aka. &ldquo;keys&rdquo; with full color LCD screens under them. Down, Up. That&rsquo;s all they do.</li>
<li>An LCD touchscreen strip. 800x100, full color. Can register short tap, long press and a weird kind of drag.</li>
<li>4 knobs, turns in steps, left, right, can also send multiple steps if turned quickly. Can also be pressed as a button, up/down.</li>
<li>Controllable LCD brightness (it is really just one panel).</li>
</ul>
<p>All of this is done over that USB HID interface.</p>
<p>I had very little trouble with the Elixir port overall. My most long-standing wtfs where around sending images to the StreamDeck and making sure they worked. I have <a href="https://youtube.com/live/v_7-2L2TFM4">a stream of me struggling to update the LCD</a> where in the end I had just flipped two fields in the messages I was sending. Buttons and knobs took very little time. Compared to when I was making Inky over 3 years ago I have written Elixir pretty much daily since. It is nice to feel that the language fades away when solving problems.</p>
<p>Building and matching binaries could have been more challenging if it wasn&rsquo;t for all the work I did <a href="https://changelog.com/posts/a-new-chapter-for-changelog-podcasts">with The Changelog for chapter support</a> where I implemented <a href="https://hex.pm/packages/id3vx">an ID3v2 library for Elixir</a>. That made me very familiar with <a href="https://changelog.com/posts/id3vx-a-library-for-parsing-and-encoding-id3-tags">all of the binary pattern matching and constructing binaries</a>.</p>
<p>Let&rsquo;s actually have some code in here:</p>

  <div class="code  elixir "  data-file="lib/streamdex/devices/streamdeck_plus.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/streamdex/devices/streamdeck_plus.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">def</span> set_brightness(d, percent) <span style="color:#f92672">when</span> is_integer(percent) <span style="color:#66d9ef">do</span>
	percent <span style="color:#f92672">=</span> min(max(percent, <span style="color:#ae81ff">0</span>), <span style="color:#ae81ff">100</span>)
	payload <span style="color:#f92672">=</span> rightpad_bytes(&lt;&lt;<span style="color:#ae81ff">0x03</span>, <span style="color:#ae81ff">0x08</span>, percent&gt;&gt;, <span style="color:#ae81ff">32</span>)

	write_feature(d, payload)
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>Not too complicated. How about reading keys?</p>

  <div class="code  elixir "  data-file="lib/streamdex/devices/streamdeck_plus.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/streamdex/devices/streamdeck_plus.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">def</span> read_key_states(d) <span style="color:#66d9ef">do</span>
	{<span style="color:#e6db74">:ok</span>, binary} <span style="color:#f92672">=</span> read(d, <span style="color:#ae81ff">14</span>)
	binary
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>Also simple. Writing a new chunk of image to the LCD is a bit more involved..</p>

  <div class="code  elixir "  data-file="lib/streamdex/devices/streamdeck_plus.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/streamdex/devices/streamdeck_plus.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">def</span> set_lcd_image(d, x, y, width, height, binary) <span style="color:#66d9ef">do</span>
    send_lcd_image_chunk(d, binary, x, y, width, height, <span style="color:#ae81ff">0</span>)
<span style="color:#66d9ef">end</span>

<span style="color:#66d9ef">defp</span> send_lcd_image_chunk(_, &lt;&lt;&gt;&gt;, _, _, _, _, _), <span style="color:#e6db74">do</span>: <span style="color:#e6db74">:ok</span>

<span style="color:#66d9ef">defp</span> send_lcd_image_chunk(d, binary, x, y, width, height, page_number) <span style="color:#66d9ef">do</span>
    <span style="color:#66d9ef">if</span> width <span style="color:#f92672">+</span> x <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">800</span> <span style="color:#66d9ef">do</span>
      <span style="color:#66d9ef">raise</span> <span style="color:#e6db74">&#34;too wide&#34;</span>
    <span style="color:#66d9ef">end</span>

    <span style="color:#66d9ef">if</span> height <span style="color:#f92672">+</span> y <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">100</span> <span style="color:#66d9ef">do</span>
      <span style="color:#66d9ef">raise</span> <span style="color:#e6db74">&#34;too high&#34;</span>
    <span style="color:#66d9ef">end</span>

    bytes_remaining <span style="color:#f92672">=</span> byte_size(binary)
    payload_length <span style="color:#f92672">=</span> <span style="color:#a6e22e">@config</span><span style="color:#f92672">.</span>image<span style="color:#f92672">.</span>report<span style="color:#f92672">.</span>touchlcd_payload_length
    length <span style="color:#f92672">=</span> min(bytes_remaining, payload_length)

    {bytes, remainder, is_last} <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">case</span> binary <span style="color:#66d9ef">do</span>
        &lt;&lt;bytes<span style="color:#f92672">::</span>binary<span style="color:#f92672">-</span>size(payload_length), remainder<span style="color:#f92672">::</span>binary&gt;&gt; <span style="color:#f92672">-&gt;</span>
          {bytes, remainder, <span style="color:#ae81ff">0</span>}

        bytes <span style="color:#f92672">-&gt;</span>
          {bytes, &lt;&lt;&gt;&gt;, <span style="color:#ae81ff">1</span>}
      <span style="color:#66d9ef">end</span>

    header <span style="color:#f92672">=</span>
      [
        <span style="color:#ae81ff">0x02</span>,
        <span style="color:#ae81ff">0x0C</span>,
        &lt;&lt;x<span style="color:#f92672">::</span>size(<span style="color:#ae81ff">16</span>)<span style="color:#f92672">-</span>unsigned<span style="color:#f92672">-</span>integer<span style="color:#f92672">-</span>little&gt;&gt;,
        &lt;&lt;y<span style="color:#f92672">::</span>size(<span style="color:#ae81ff">16</span>)<span style="color:#f92672">-</span>unsigned<span style="color:#f92672">-</span>integer<span style="color:#f92672">-</span>little&gt;&gt;,
        &lt;&lt;width<span style="color:#f92672">::</span>size(<span style="color:#ae81ff">16</span>)<span style="color:#f92672">-</span>unsigned<span style="color:#f92672">-</span>integer<span style="color:#f92672">-</span>little&gt;&gt;,
        &lt;&lt;height<span style="color:#f92672">::</span>size(<span style="color:#ae81ff">16</span>)<span style="color:#f92672">-</span>unsigned<span style="color:#f92672">-</span>integer<span style="color:#f92672">-</span>little&gt;&gt;,
        is_last,
        &lt;&lt;page_number<span style="color:#f92672">::</span>size(<span style="color:#ae81ff">16</span>)<span style="color:#f92672">-</span>unsigned<span style="color:#f92672">-</span>integer<span style="color:#f92672">-</span>little&gt;&gt;,
        &lt;&lt;length<span style="color:#f92672">::</span>size(<span style="color:#ae81ff">16</span>)<span style="color:#f92672">-</span>unsigned<span style="color:#f92672">-</span>integer<span style="color:#f92672">-</span>little&gt;&gt;,
        <span style="color:#ae81ff">0x00</span>
      ]
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>iodata_to_binary()

    <span style="color:#ae81ff">16</span> <span style="color:#f92672">=</span> byte_size(header)

    payload <span style="color:#f92672">=</span> header <span style="color:#f92672">&lt;&gt;</span> bytes

    payload <span style="color:#f92672">=</span> rightpad_bytes(payload, <span style="color:#a6e22e">@config</span><span style="color:#f92672">.</span>image<span style="color:#f92672">.</span>report<span style="color:#f92672">.</span>touchlcd_length)

    <span style="color:#ae81ff">1024</span> <span style="color:#f92672">=</span> byte_size(payload)

    <span style="color:#66d9ef">case</span> write(d, payload, <span style="color:#e6db74">&#34;set lcd image chunk&#34;</span>) <span style="color:#66d9ef">do</span>
      {<span style="color:#e6db74">:ok</span>, _} <span style="color:#f92672">-&gt;</span>
        send_lcd_image_chunk(d, remainder, x, y, width, height, page_number <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>)

      err <span style="color:#f92672">-&gt;</span>
        err
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>Still not unreasonable. Just longer.</p>
<iframe style="display: block; margin-left: auto; margin-right: auto; width: 422px;" width="422" height="751" src="https://www.youtube.com/embed/1c9cwARW2pk" title="StreamDeck Plus in Elixir" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p>I shared some in-progress work both in <a href="https://youtube.com/shorts/1c9cwARW2pk">this Short</a> and <a href="https://youtube.com/live/v_7-2L2TFM4">on the livestream</a>.</p>
<p>The end result is <a href="https://github.com/lawik/streamdex">the streamdex library</a> which supports the Stream Deck Plus right now as well as the Stream Deck Pedal (literally three keys, very simple). It is pretty rough, alpha-level, as I only took it to the point where I could use it. If you have a Stream Deck and interest in Elixir, feel free to contribute your device.</p>
<p>As a self-taught web dev it is wild to me that I&rsquo;m writing something that can be fairly called device drivers right now. I&rsquo;m not claiming they are advanced or impressive. Userspace drivers like this one live on top of many layers of more complex implementation. I just know that teenager me is nodding his head right now. He is pretty proud.</p>
<p>I&rsquo;m also very glad that someone else has done the reverse engineering. I did fire up Wireshark a couple of times, actually how I found the final swapped byte for the LCD, but I don&rsquo;t want any credit for figuring out how these work.</p>
<p>Ever since I got it fully operational I&rsquo;ve been hacking at a crude but effective project for running a bunch of my computer .. stuff. That&rsquo;s <a href="https://github.com/lawik/auto">also public</a>.</p>
<p>So far I&rsquo;ve tried:</p>
<ul>
<li>Lights
<ul>
<li>keys for on/off</li>
<li>a knob for brightness that can be pressed for on/off</li>
<li>a knob for light temperature and pressing the knob resets that to a good default</li>
</ul>
</li>
<li>Calendar
<ul>
<li>Show current and upcoming calendar events on the LCD which I mostly ripped from <a href="https://github.com/lawik/calendar_gadget">my calendar gadget eInk project</a>. Simplistic display still, much more fun to be had.</li>
</ul>
</li>
<li>Play/Pause
<ul>
<li>A key that toggles and uses <a href="https://git.sr.ht/~geb/dotool">dotool</a> to send the media play/pause key event to the OS. Made a quick and nasty <a href="https://github.com/lawik/dotool_elixir">dotool_elixir</a> library that pulls that together and compiles the Go code.</li>
</ul>
</li>
<li>Mic mute
<ul>
<li>Both a key for toggle and a pedal for press. Sends micmute key event to the OS. <em>does not work currently on my KDE :(</em></li>
</ul>
</li>
<li>Key icons
<ul>
<li>Made a small library called <a href="https://github.com/lawik/bs_icons">bs_icons</a> which pulls the Bootstrap Icons repository and provides those for me in my Elixir project. I then mangle into keys.</li>
</ul>
</li>
</ul>
<p>Building on the backs of the community and ecosystem like this I have gotten amazing mileage out of Kip Cole&rsquo;s great <a href="https://hexdocs.pm/image/readme.html">Image</a> library. I&rsquo;ve also used <a href="https://github.com/teamon/resvg">resvg</a> for turning SVG into PNG, which I apparently could have done with Image. On the plus side that also brought in Rust as well which meant achieving basic hipster compliance. Now I just need to change my HID library to use Zig and I&rsquo;ll have aced it. That might happen actually.</p>
<p>Things yet to try:</p>
<ul>
<li>Touchscreen
<ul>
<li>Want to render much more interesting UI on it. Image can do so much fun stuff and I bet I could make it play nice with Brian Cardarella&rsquo;s <a href="https://hexdocs.pm/easing/readme.html">easing library</a>. I&rsquo;ve already tested a bit and Image can update the screen at just shy of 60hz. No idea if that looks reasonable though. We can expect to push 30hz with no issue I expect. Image provides operations like <code>blur</code> and <code>ripple</code> which I&rsquo;d love to animate. I made <a href="https://youtube.com/shorts/ze2ZGMh8hQY">a Short showing the silliness I&rsquo;m after</a>.</li>
<li>Using the touch at all. I&rsquo;m most curious about the drag. I couldn&rsquo;t actually see that Elgato uses that so it might be too crap. But I want to see what I can make it do. Build a whole little touch UI in there.</li>
</ul>
</li>
<li>Update keys while being pressed, trigger events on key up after down instead of down. Those kinds of niceties.</li>
</ul>
<iframe style="display: block; margin-left: auto; margin-right: auto; width: 422px;" width="422" height="751" src="https://www.youtube.com/embed/ze2ZGMh8hQY" title="Elixir image processing" frameborder="0" allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share" allowfullscreen></iframe>
<p>This is a really fun thing to poke around with when I have some moments for myself. Just making it do more and more. Exploring how I can combine more and more sources of data, inputs and outputs. I have a very simplistic GenServer-driven approach right now that I&rsquo;d like to think about abstracting a bit. If I want to do better UI I need to keep the state of the UI differently and I&rsquo;ll need to manage timing for animations.</p>
<p>I have a bunch of ideas around how to wrap libraries that provide new capabilities in a way where they plug into this in a more flexible way. Early thoughts. Might go nowhere.</p>
<p>When they market macro-pads like this for productivity I don&rsquo;t think they usually mean that you&rsquo;ll write tons of lines of code to make them do things. For me, this device has made me very productive. In a very specific way.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Chat bots as UI at ElixirConf Africa</title>
      <link>https://underjord.io/video-elixirconf-africa-chatbots.html</link>
      <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-elixirconf-africa-chatbots.html</guid>
      <description>My presentation from ElixirConf Africa.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>My presentation from ElixirConf Africa.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/elixirconf-africa-chatbots-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/elixirconf-africa-chatbots-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/elixirconf-africa-chatbots-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/elixirconf-africa-chatbots.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/elixirconf-africa-chatbots-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Does terminology matter?</title>
      <link>https://underjord.io/video-does-terminology-matter.html</link>
      <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-does-terminology-matter.html</guid>
      <description>A short video on the importance of terminology in computing.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>A short video on the importance of terminology in computing.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/shorts-3-terminology-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/shorts-3-terminology-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/shorts-3-terminology-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/shorts-3-terminology.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/shorts-3-terminology-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Is it all queues?</title>
      <link>https://underjord.io/video-is-it-all-queues.html</link>
      <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-is-it-all-queues.html</guid>
      <description>Are computers really all about queues?
At some level. Probably.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>Are computers really all about queues?</p>
<p>At some level. Probably.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/short-2-queues-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/short-2-queues-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/short-2-queues-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/short-2-queues.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/short-2-queues-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Reacting to the Phoenix Keynote 2022</title>
      <link>https://underjord.io/video-reacting-to-phoenix-keynote.html</link>
      <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-reacting-to-phoenix-keynote.html</guid>
      <description>I share my reactions to the Phoenix keynote at ElixirConf 2022.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>I share my reactions to the Phoenix keynote at ElixirConf 2022.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-phoenix-keynote-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-phoenix-keynote-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-phoenix-keynote-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-phoenix-keynote.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-phoenix-keynote-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Sonic Pi</title>
      <link>https://underjord.io/video-sonic-pi.html</link>
      <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-sonic-pi.html</guid>
      <description>Code has always been about creative pursuit for me.
This video showcases Sonic Pi. It is an open source project that I really appreciate. Sonic Pi enables live coding of music. If you are already a programmer it can turn your hard-earned, often dry, programming capabilities into musical potential. If you know music, you might just learn programming. If you know neither it is a fantastically fun place to start.</description>
      <content:encoded><![CDATA[ <p>Code has always been about creative pursuit for me.</p>
<p>This video showcases Sonic Pi. It is an open source project that I really appreciate. Sonic Pi enables live coding of music. If you are already a programmer it can turn your hard-earned, often dry, programming capabilities into musical potential. If you know music, you might just learn programming. If you know neither it is a fantastically fun place to start.</p>
<p>The video was entirely funded by Underjord out of love for cool open source software projects. Thanks to Sam Aaron of Sonic Pi for help on music choice and working through the idea. Mad props to DJ_Dave for the track which is in Sonic Pi&rsquo;s examples, slightly modified for this presentation.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/sonic-pi-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/sonic-pi-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/sonic-pi-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/sonic-pi.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/sonic-pi-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
<p>The project is focused on learning and enables any kid or teacher with a computer, even a Raspberry Pi, to get going.</p>
<ul>
<li>Learn more: <a href="https://sonic-pi.net/">https://sonic-pi.net/</a></li>
<li>The Sonic Pi forum: <a href="https://in-thread.sonic-pi.net">https://in-thread.sonic-pi.net</a></li>
<li>Want to get going? The tutorial: sonic-pi.net/tutorial.html</li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Why use a database?</title>
      <link>https://underjord.io/video-why-use-a-database.html</link>
      <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-why-use-a-database.html</guid>
      <description>A short video on databases.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>A short video on databases.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/shorts-1-database-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/shorts-1-database-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/shorts-1-database-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/shorts-1-database.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/shorts-1-database-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Complex Systems All The Way Down</title>
      <link>https://underjord.io/complex-systems-all-the-way-down.html</link>
      <pubDate>Mon, 24 Oct 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/complex-systems-all-the-way-down.html</guid>
      <description>As part of running Underjord I&amp;rsquo;ve done both mentorship and some teaching. Both in the form of a working relationship where I have seniority and am responsible for supporting their learning as well as mentoring entirely external people of varying experience levels. I find it incredibly hard to transmit everything I think people need to get familiar with when they want to do development. Why?
Well, I learned it all incrementally.</description>
      <content:encoded><![CDATA[ <p>As part of running Underjord I&rsquo;ve done both mentorship and some teaching. Both in the form of a working relationship where I have seniority and am responsible for supporting their learning as well as mentoring entirely external people of varying experience levels. I find it incredibly hard to transmit everything I think people need to get familiar with when they want to do development. Why?</p>
<p>Well, I learned it all incrementally. HTML on Geocities, then some CSS, some JS. Oooh, PERL. Some XML. Local web server, exciting! Bam! PHP, MySQL, Linux. Years of Linux desktop use along with running my own server because young and no money. jQuery and AJAX. Onward to first paid work, paid hosting and CMS:es. WordPress, Joomla, Drupal, all of it. Tried to build web applications with those. That sucked. Better servers, Python, web frameworks, SPAs. Onwards to clouds. And on it goes to this day.</p>
<p><strong>Note: This post is a refinement of an essay from my newsletter. Issue 15 to be specific. If you enjoy the read, consider <a href="/newsletter.html">signing up</a> for a weekly hit of opinions and tech.</strong></p>
<p>It has been unending learning. And most of it has built on but also replaced most of what came before. I don&rsquo;t really like traditional CMS:es. I think they make empty promises and sneak up on you with hidden costs. But they are a pretty accessible place to start and learn. I don&rsquo;t like cheap hosting providers, but, again, they are accessible. The choices I make today are choices I can make because of my past experiences. I already know many of the tradeoffs. My choices might not make sense to a newbie. &ldquo;WordPress is a huge and influential project. Surely they know what they are doing?&rdquo; Yeah, but they can&rsquo;t change their design without breaking their golden goose of extensions. I bet they have some thoughts.</p>
<p>When a student asks me &ldquo;What are your thoughts on MongoDB?&rdquo; the answer is only simple if I lie. I know some people like it. I have heard enough to distrust it. The answer &ldquo;Just use Postgres&rdquo; isn&rsquo;t exactly right, &ldquo;It&rsquo;s probably fine&rdquo; feels dishonest and if I add &ldquo;but I&rsquo;d never choose it for anything” that&rsquo;s hardly helpful. There is enormous context and choosing what to bring into a learning person&rsquo;s world is genuinely difficult.</p>
<p>At the time I was learning, I didn&rsquo;t have access to any kind of bigger picture. I had limited resources. Limited documentation. I had so many limitations. I ran into them constantly and eventually found the next thing I could use to do cool stuff. If you try to teach someone to use an HTML form today, you will need to decide if you are talking about the basic idea or reality. Do we need to cover CSRF, captchas and how to do it in React? How about the backend? Starting out, I didn&rsquo;t hear about half of the things I didn&rsquo;t know; I think that made things easier.</p>
<p>For example, I think every developer working in open tools benefits from being comfortable in a terminal and on a remote Linuxish server. So that brings some POSIX and some Bash. But then you get SSH and public key crypto. And then you have basic networking, potentially firewall configuration, at the very least Nginx, a database or maybe a bunch of Docker and cloud native tools that abstract everything away .. until you have to customize something and the complexity comes tumbling back in. The domain of knowledge just sprawls out into countless dimensions.</p>
<p>Yet people are supposed to get into this with a bootcamp. Or even with two years in school. I&rsquo;m glad I started in my teens and don&rsquo;t have to feel like I&rsquo;m playing catch-up any more than I am. If it feels overwhelming to you, regardless of experience level, that&rsquo;s because it is. It&rsquo;s fractal; look closer at any of it and there is always more you could understand and benefit from.  There are always more concerns you could absorb into your full-stackness.</p>
<p>Suddenly we run face first into the thing where companies are only hiring &ldquo;senior&rdquo; developers and everyone starts out being a &ldquo;junior&rdquo; developer. I&rsquo;ve even seen &ldquo;making senior&rdquo; become a thing on Twitter because it is now an arbitrarily codified level in the orgs career progression. And in some ways that might be good. The profession probably needs to mature. But let me be clear and honest, I got my first senior and tech lead titles simply because I was the most experienced in the room, not because I was particularly experienced. But someone needed to wrangle the server, someone needed to stick their neck out. That was it.</p>
<p>But where the hell are people supposed to learn? Is it always going to be the WordPress shops of the world that teach juniors? Because websites are lower stakes and in the CMS world poor technical choices go unnoticed?</p>
<p>I keep trying to find better ways to bring people onboard and up to speed, and to hand them the tools they need. If I try to boil it down I get this:</p>
<p>If you want to do this stuff and build your skills, you have to know how to learn new things, and you have to be able to sit with discomfort.</p>
<p>The learning part is pretty commonly stated, the discomfort is not necessarily mentioned as frequently. You will never know the stack top to bottom to some specific granularity. You have uncertainty in every part of the profession. Your strongest guarantees build on top of systems on systems of unclear guarantees. All these things are learnable on their own, but the entire system of a full-stack web developer is not. You are dealing with uncertainty, and you need to be able to sit with the discomfort of that to get your work done with what you know right now.</p>
<p>I&rsquo;d wager there&rsquo;s a case to be made that the discomfort is what makes us move forward and try to find new solutions that solve our particular discomfort. And at the very least, we get to zoom deeper into that part of the fractal.</p>
<p>Hand someone a fish and they&rsquo;ll never know that the ocean is vast. Teach them how to fish and they&rsquo;ll be forever lost at sea.</p>
<p>Does this match your experience? Is it hopeful or distressing? You can reach me over email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or find me on Twitter as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. If you enjoyed the read, it spawned from <a href="/newsletter.html">the newsletter</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Fear-driven development</title>
      <link>https://underjord.io/fear-driven-development.html</link>
      <pubDate>Fri, 07 Oct 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/fear-driven-development.html</guid>
      <description>Twice burnt, getting the hang of this fire thing.
There’s a thing I’m wary of with both companies and developers: when they seem to operate mostly on fear. In the case of companies, fear can lead to heavy screening during the hiring process, heavy-handed, detail-oriented project management or distrust towards employees. To my mind, these things either come from the basic mode of operation of some founding member or from cultural scars caused by a bad hire, a runaway project or very anxious management.</description>
      <content:encoded><![CDATA[ <p>Twice burnt, getting the hang of this fire thing.</p>
<p>There’s a thing I’m wary of with both companies and developers: when they seem to operate mostly on fear. In the case of companies, fear can lead to heavy screening during the hiring process, heavy-handed, detail-oriented project management or distrust towards employees. To my mind, these things either come from the basic mode of operation of some founding member or from cultural scars caused by a bad hire, a runaway project or very anxious management. Admittedly, there are projects where detailed specifications make sense. There are roles where screening is more important. There are entry-level garbage jobs where mutual distrust is possibly unavoidable at a certain level. However, I believe that, ultimately, all of these are detrimental practices.</p>
<p><em>Note: This post was originally published in <a href="/newsletter.html">the newsletter</a> all about 90 weekly issues ago. I try to pick out some of the best ones for refinement and public publishing. Hope you enjoy. Much thanks to Cornelia Kelinske for helping me with the editing on this.</em></p>
<p>On the developer side I have seen developers, both employed and independent, that negotiate from a position of past wrong-doings. &ldquo;Never this again.. I refuse to ..&rdquo; They try to negotiate away future discomfort based on previous experiences and, to my understanding, a fear of repeating that bad experience. With people, I find this fear much more understandable. But whenever I act from this perspective I find it ends up being short-sighted. I should definitely learn what I dislike and want to avoid from bad experiences. I just no longer believe I can encode a perfect heuristic for that in contract terms or handshakes. There are an endless number of potential pains that can land on me in my work; enumerating them is pointless.</p>
<p>Actually, I think it&rsquo;s even beyond pointless. Because you spend your leverage and career capital on promises that no organization is truly capable of keeping. &ldquo;No crunch&rdquo; or maybe &ldquo;no calls during my vacation&rdquo; are certainly reasonable demands. But will your employer let their business go to hell before they breach those terms? I doubt it.</p>
<p>Instead, I try to do what my first psychotherapist told me as I was working on recovering from burnout: I focus on &ldquo;What do you feel?&rdquo; When something comes up, I try not to just think it through, but instead, I ask myself: &ldquo;what do you feel?&rdquo; and I register those feelings. If I get the feeling that this is not how I want things, that&rsquo;s a signal that I should push back, negotiate or at least air my concerns.</p>
<p>I can’t remove all the discomforts of my life, I can’t negotiate myself away from every imposition or nervous manager I might encounter. Organizations may have their scars; so do developers. I have mine. But I find fear makes us short-sighted and inflexible. Fear is pretty much completely ineffective as a driver for meaningful change. By leaning into new situations, despite the base level of discomfort it entails, I have achieved so many more of the things I want. I earn more, I work less, the work I do is more meaningful and I am much less supervised and/or micro-managed. And when things I don’t appreciate or accept come gliding in, I deal with them then and there.</p>
<p>This doesn’t remove the risk of unpleasant or bad things happening. But in my experience, neither does negotiating based on past hurt. It is better to negotiate for the things you want than to negotiate for what you want to protect yourself from. Because you can actually get the former. The latter is all about managing the situation as it arises.</p>
<p>I don’t want to operate on fear, worry or hesitation. I want to act based on consideration, intentional choices and steady commitments made through clear negotiation. This doesn’t mean I don’t have fear, worry or hesitation. I can be quite anxious. But for me, the way through that is not about building myself a fort. It is about making sure I’m an active participant in the outcome. That’s what I try for. I recommend others to at least give it a shot. Or get a therapist, maybe there are other recommendations for you.</p>
<p>If you find this interesting I cover this type of ground in my newsletter on a regular basis. You can <a href="/newsletter.html">find the newsletter here</a>. If you have questions or feedback you can reach me at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Why can&#39;t I find a CMS?</title>
      <link>https://underjord.io/cant-find-a-cms.html</link>
      <pubDate>Mon, 26 Sep 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/cant-find-a-cms.html</guid>
      <description>I&amp;rsquo;ve been trying to address this particular topic and I find it challenging. It is a topic that&amp;rsquo;s very easy to turn into a negative rant and that&amp;rsquo;s not what I like to do. If you enjoy any of the stacks I dismiss in this post, please know that is not a jab at you or your work. I have worked in these stacks and I&amp;rsquo;ve mostly transitioned away from them where they aren&amp;rsquo;t necessary.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;ve been trying to address this particular topic and I find it challenging. It is a topic that&rsquo;s very easy to turn into a negative rant and that&rsquo;s not what I like to do. If you enjoy any of the stacks I dismiss in this post, please know that is not a jab at you or your work. I have worked in these stacks and I&rsquo;ve mostly transitioned away from them where they aren&rsquo;t necessary. They absolutely work and can be used. I believe my reasons are good and sound, but they are specific to my values and experiences. If you have a different take-away, I&rsquo;m fine with that. With that out of the way, let&rsquo;s talk about Content Management Systems, or more commonly CMS.</p>
<p><em>Editor&rsquo;s note: This post is a refinement of the 7th issue of <a href="/newsletter.html">my newsletter</a>. If you enjoy my thoughts on things, know that I send a small essay every week.</em></p>
<p>A while back I worked with a WordPress site. I used to do a lot of work with WordPress and Drupal many years back. For me, as someone in the open source end of the software development spectrum, WordPress tends to pop up every now and then. Typically a business owner I&rsquo;m familiar with will go, &ldquo;We got a new website, it is a fresh new WordPress instead of the mess we had.&rdquo;</p>
<p>At that point, it is already too late to provide any beneficial input, so I usually withhold my commentary. If they had talked to me sooner, a WordPress site is not what I would have suggested.</p>
<p>But that&rsquo;s what I feel and that&rsquo;s been my experience. I don&rsquo;t find WordPress to be a healthy way of building a website or running a software development project. WordPress has a lot of things going for it; dirt cheap hosting with standard LAMP-stack hosts. Solid UI that any moderately adventurous user can handle by default. Easy to set up if you have some technical skills. Well-known, much loved, huge ecosystem. It is open source and has respectable accessibility. These things tick some serious boxes for many organizations.</p>
<p>So what&rsquo;s my problem?</p>
<p>There are hidden costs. No one budgets for maintenance work. No one plans for having access to a developer when the maintenance needs to happen. No one knows that the PHP-version their host provides may shift under their feet and kill their site whenever their host decides to upgrade. Most small organizations do not set up backups. All editing happens live on the site, which can have enormous consequences.</p>
<p>It’s also incredibly inefficient. WordPress does dynamic rendering by default, taxing CPU and IO for every single page load of what is usually entirely static content. There are tons of ways of caching WordPress; all of them have their own advantages and drawbacks. Expect road bumps. There are even ways of generating sites statically, but that&rsquo;s not by design. Expect even more road bumps. This dynamic nature is part of the simplicity of WordPress and also enables its massive extensibility. It was generally how web applications were built in the 2000s. And even though hosting is cheap, it is usually unreliable, generally bad, or has had its capacity overbooked. You tend to get what you pay for. Many CDNs offer free tiers that would be enough for almost any small org website. Such a typical CDN offering would make for hilariously great performance for any simple website. Unfortunately, they tend to work very poorly with dynamically generated sites. There is a lot of benefit in matching your static content with a static approach to rendering.</p>
<p>There are also no good ways of managing WordPress workflows for more serious projects where you might want some DevOps deployment procedures or want developers to be able to work on the config and content while publishing happens on the production environment. Why? Because content and configuration are deeply intermingled in the database. Especially when you use extensions, which is one of the largest selling points of WordPress. All this boils down to maddening database merging, or else re-doing the development work on the production environment when it is time to deploy. Hope you kept diligent notes because that&rsquo;s how we do software development in 2022. This flies in the face of most good practices and involves a lot of time-consuming, stressful and manual work.</p>
<p>So why hasn&rsquo;t this issue been solved? To my understanding, most of the WordPress ecosystem is not run on in-depth software development shops. It is mostly web agency-level businesses. Their focus is usually not on the development workflow and the average customer&rsquo;s experience level is low enough that many do not see the problem. The pain isn&rsquo;t great enough in the short term, either. The initial project effort is usually fine since there is nothing in production. Deliver to the client, move on. Sustainability is either not in the business model or just a minimal support deal. The businesses that produce most WordPress sites don&rsquo;t have a lot of incentive to shake this up, and some of them don&rsquo;t even know that there&rsquo;s reason to be concerned. I didn&rsquo;t when I was doing it.</p>
<p>So can we use something else? I don&rsquo;t know of any CMS that I would consider a valid replacement. Easy to use for end-users, manageable complexity for the devs, a great CDN story, smooth deployment. Static site generators are great, but they are firmly on the nerd shit end of the spectrum. Great for devs, iffy for less savvy users. Netlify has had some hype with Netlify CMS, but it does lean heavily on inviting you to use the CMS specifically with Netlify services.</p>
<p>There are a few CMS:es, but the ones with the most traction hit other reservations of mine. Craft and Cockpit seem to be more modern CMS:es in PHP. Cool, make use of the well-established tech WordPress builds on and use it to try something more modern. The options I hear most about and see recommended frequently, like Ghost or the headless Strapi, are built with Node.js, which in some ways is the spiritual successor to PHP in web dev popularity.</p>
<p>However, PHP-based projects are just not particularly interesting to me because I don&rsquo;t really enjoy working with the language and I still find the execution model with files in folders to be a source of entire additional classes of security issues. WordPress, Drupal, and Joomla have been enormously efficient sources for automatic compromise, as security issues surface and many users just don&rsquo;t know how to keep all the parts updated. If you can get a bad file upload through, you often have remote code execution. I&rsquo;m sure some projects avoid this, but from what I&rsquo;ve seen, it is still mostly the same model. I have other, additional reservations about PHP overall, but lots of that is just taste and preference. It&rsquo;s a decent enough language, but it&rsquo;s not for me.</p>
<p>Node.js-based projects don&rsquo;t spark joy for me because I’ve had plenty of bad experiences maintaining projects that live with significant Node.js module dependencies. I&rsquo;ve seen it happen with Angular 1, Cordova and Ionic at a previous job. I&rsquo;ve had clients with React Native apps who encountered  the same issue. When left alone for a few months or years, a project like this is an iffy thing to get running again. I can&rsquo;t in good faith give Node high marks for health and sustainability. So while I might say, &ldquo;yeah, try one of those Node CMS options,&rdquo; I would do so because I can&rsquo;t find any options I can heartily endorse.</p>
<p>I like the ideas of the JAM stack, but I find the J to be over-represented. Javascript for frontend interactivity is fine and really the best option right now, but I don&rsquo;t particularly like it for the backend. I don&rsquo;t think Node is necessary to manage the C10K problem. I don&rsquo;t find the event-driven single-threaded approach compelling when I contrast it to Erlang, Elixir, and OTP, with which I typically work. I prefer making efficient use of resources, and I get easily frustrated with the challenges Node can have with racing towards doing IO because CPU-bound work can introduce enormous performance issues. Those are certainly trade-offs you can make for a simpler execution model, but I don&rsquo;t like them.</p>
<p>For most businesses where you just need a working website and the website is not your entire job, you can pay for something like Squarespace and be done with it. It&rsquo;ll probably be fine. But for some orgs, having an open source stack is important, either ideologically, as a point of pride in openness, or because they want to avoid vendor lock-in.</p>
<p>The Open Source CMS is not a solved problem in my book. There is no choice that provides what I want. Perhaps I&rsquo;m a bit niche in that I filter out on both PHP and Node.js, but I don&rsquo;t believe I&rsquo;m alone in that sentiment. I think WordPress won its market leader role on the basis of developer mindshare. It is approachable and effective. It will do in 90% of use cases, you can get at the innards and make it do what you need, and it provides a UI that your users will not complain about. I can&rsquo;t get that with current solutions; they are either trying to be too clever and target developers to the detriment of end users, or they are not built in a way I expect to hold up in the long term.</p>
<p>So what are my requirements?</p>
<ul>
<li>Sound technical stack with a good record in reliability and security</li>
<li>Creates primarily static sites for efficiency, security, and reliability</li>
<li>Can be hosted with a wide variety of generic providers</li>
<li>Supports implementing dynamic features in a JAM Stack manner, with APIs, etc.</li>
<li>Provides a fully online admin UI for managing the content of the site in a smooth way</li>
<li>Implements a thought-out way of working with both production editing and development processes</li>
<li>Can be automated and &ldquo;devops:ed&rdquo; effectively</li>
<li>Provides a decent ecosystem of at least themes and preferably extensions as well</li>
<li>Offers a sound approach to accessibility</li>
<li>Open source</li>
</ul>
<p>I&rsquo;ve already done one run on building something like this where I tried a particular Git-based approach which I&rsquo;ve since abandoned. I think the approach has some merit but it is not easy or straightforward.</p>
<p>A while back I started experiments with a couple of co-conspirators on developing the backbone for a headless CMS in Elixir. It&rsquo;s a slow process as we all have jobs and other silly priorities, but I&rsquo;m hopeful that this approach could actually be quite realistic. The first step is to build good data fundamentals. For featureset I&rsquo;m leaning heavily on my experiences with Contentful at a client where very successfully leveraged that headless CMS.</p>
<p>I don&rsquo;t need it to have the critical mass of WordPress. And I doubt I&rsquo;ll even manage to check my entire list up there. Hopefully, though, we can make something that solves my actual problems and we&rsquo;ll see where I can take it from there.</p>
<p>Thanks for reading. If I&rsquo;ve missed some solution that you think I should consider, or if I&rsquo;m wrong about something I wrote here, don&rsquo;t hesitate to write at me via <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. I’m always open to discuss what I&rsquo;m up to. It would be incredibly convenient to be wrong and not have this problem anymore.</p>
<p>If you want more of my more frequent writing on tech, Elixir and creating things I suggest <a href="/newsletter.html">the newsletter</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Self-hosting: Mattermost</title>
      <link>https://underjord.io/video-self-hosting-mattermost.html</link>
      <pubDate>Fri, 16 Sep 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-self-hosting-mattermost.html</guid>
      <description>Slack, Teams, teamchat. We all use them in our jobs. It is unclear whether that&amp;rsquo;s even legal. I wanted to present one of the more well-known open source options and how to get started with it. This video is a collaboration with GleSYS. Keep them in mind for your cloud and data center needs in the Nordics.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.</description>
      <content:encoded><![CDATA[ <p>Slack, Teams, teamchat. We all use them in our jobs. It is unclear whether that&rsquo;s even legal. I wanted to present one of the more well-known open source options and how to get started with it. This video is a collaboration with GleSYS. Keep them in mind for your cloud and data center needs in the Nordics.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-mattermost-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-mattermost-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-mattermost-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-mattermost.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-mattermost-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Tradition and Ritual in Software, or The Unwritten Code of Code</title>
      <link>https://underjord.io/tradition-and-ritual-in-software.html</link>
      <pubDate>Mon, 12 Sep 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/tradition-and-ritual-in-software.html</guid>
      <description>I’ve been thinking about shared experiences: culture and tradition. As the seed of this was written close to the holidays, it might be that they are to blame. But this post isn’t about Christmas or the new year; it’s about my reflections on these human matters and how they apply in the software development world.
How we run dangerous commands Have you ever had to run a potentially dangerous command on a production server?</description>
      <content:encoded><![CDATA[ <p>I’ve been thinking about shared experiences: culture and tradition. As the seed of this was written close to the holidays, it might be that they are to blame. But this post isn’t about Christmas or the new year; it’s about my reflections on these human matters and how they apply in the software development world.</p>
<h2 id="how-we-run-dangerous-commands">How we run dangerous commands</h2>
<p>Have you ever had to run a potentially dangerous command on a production server? A destructive piece of SQL? A batch update/data migration script? A script that cleans up accumulated garbage files in a recursive manner?</p>
<p>How did you approach it?</p>
<p>Do you have a small alarm bell that goes off in your head when you are close to doing something that would be really painful (or impossible) to revert? Do you adjust your approach because of this signal? I know I do.</p>
<p>A cluster of synapses got together in your brain just to warn you. They strive to prevent me from shooting myself in the foot at the terminal. They probably first formed that time I blew away my entire music collection with a bad shell script. They hardened the first time I rm -rf:ed something that ended up resolving to /. I haven&rsquo;t had this kind of catastrophic failure in a while. Thanks, brain.</p>
<p>When I feel that tingle of &ldquo;are you sure this is the query you want to run?&rdquo; I usually end up taking the SQL query, changing it to a SELECT and just eyeballing the output a bit. Does it hit the expected number of rows? Do the results look reasonable? Did I, in fact, just run a fresh backup?</p>
<p>Similarly, I ask myself before I run any recursive rm command on UNIX: “What files would this hit?”</p>
<p>If possible, I dry-run the command.</p>
<p>I&rsquo;m also well aware that some would snort and say that this is not how it should be done. I wager that&rsquo;s correct but not particularly useful feedback. If you can run testable migrations that can be rolled forward and back, that&rsquo;s better. If you can do it the better way and the trade-offs make sense, do it the better way. But don&rsquo;t feel bad if you don&rsquo;t have those opportunities or resources. We do what we can with what we have. You can get far by just developing a good instinct for when you&rsquo;re about to screw yourself over.</p>
<p>Is there a word for this alarm bell? This instinct? This moment of hesitation and the practices you put in place to make sure you&rsquo;re not screwing up? I think it is a shared experience of many server-wrangling developers out there. I know an old colleague that always commented out any impactful command in case he&rsquo;d accidentally fire it off before it was ready. Another colleague would type it out and Ctrl+C to cancel it but keep the command on screen while checking things.</p>
<p>It&rsquo;s an interesting thing to me. A small moment of recognizing that you are fallible wetware and that the machine, software and hardware, does not care. Is this the central friction of human-computer interface design?</p>
<p>A moment of humility, perhaps.</p>
<h2 id="rituals-around-deployment">Rituals around deployment</h2>
<p>When the new thing is going out, what do you do? Do you run a specific command? Do you press a button in a Cloud console? Do you have a friend or colleague double-check your input values? What leads up to this point?</p>
<p>There are processes you can adopt where CI and CD will completely eliminate the final button press. I&rsquo;ve found these to be rare in actual reality. I <a href="https://www.goodreads.com/en/book/show/35747076-accelerate">hear it&rsquo;s a strong idea</a>. Even with a very comprehensive CI/CD solution in place you aren&rsquo;t really free of this. It&rsquo;s not all about the tech. What does your CI/CD pipeline test for and why does it do that? Who&rsquo;s notified, and what information is broadcast when an update goes out? What rules does it use for determining success and failure?</p>
<p>There seems to be some level of ritual around updates/releases of software wherever I&rsquo;ve worked. In my own projects, I’ve created a ritual based on my habits and lessons learned. In an existing organization, I try to understand the things they already do. If I find their ritual lacking, I&rsquo;ll usually try to leverage my understanding of it to improve it. Trying to change a ritual without understanding it is a fraught process.</p>
<p>Why do I call it a ritual rather than just a process? I&rsquo;m not convinced it is only a technical, structural matter. Often we do things that aren&rsquo;t strictly necessary but are reassuring.</p>
<p>You make sure Alex in Marketing knows the update is going out because they get pissed if they don&rsquo;t feel included. You have a colleague check your thinking on particular things while you let other things right through. You have QA give the OK on the staging environment. Maybe you review the commit messages or the file changes that go into the update in full. Maybe you only check if there are migrations to run. Do you tag a release? Do you push it to a specific branch?</p>
<p>Much like some specific flavor of a burial ceremony, you have things that can seem arbitrary or strange to an outsider. These things are usually part of the culture (colors, songs, phrases), things that serve a clearly social purpose (saying a final goodbye to the deceased, closure) and things that are fair to consider a technical necessity (burying/lowering the casket/incinerating/putting the deceased somewhere).</p>
<p>In a release process some checks, steps and sign-offs can seem arbitrary but are ingrained in the culture and may have an unclear purpose or be entirely vestigial. In any release process there are checks, steps and maybe even sign-offs. These can seem arbitrary, but I believe that is because they are cultural in nature. They exclusively serve a social purpose while others are a technical necessity or clearly technical choices.</p>
<p>I think most dev shops end up with some kind of ritual. Sometimes, it’s only for communicating the changes to the surrounding organization. Other times it can be a technically intricate ritual with many mysterious incantations to get that particular system up and running with a new version. The latter type is usually a good target for significant scripting and automation.</p>
<p>As humans, I think we always attach some level of importance to ritual. Some people feel stronger about it than others. To some, it&rsquo;s about safety, and changes to the ritual can feel frustrating. To some, finding an ideal ritual is the goal, and changes are inevitable. Some desire a minimal ritual, and others want the iron-clad, double-verify, none-shall-pass sort of approach. Sometimes the ritual is dictated by culture, sometimes by actual regulations, sometimes by resources.</p>
<p>I think some people rely on the stability of the ritual, while some view it more as a means to an end. A tool. But that might also be why it works: it is something in which many different types of people can see value. It is a point where the lines cross, where Venn diagrams overlap and where the mixed purposes of the ritual can satisfy the differences of the participants in a shared procedure. We come to it for different reasons but we share the feeling that it is necessary.</p>
<p>The longer a ritual has lived, the harder I think it is to maintain the protective abstractions. Some will want the ritual to remain as it always was, and some will find it stifling and want to change it. I think a lot of people experience this with Christmas, for example. For many people, the question of how to celebrate with your family is a source of potential conflict. While the general idea of the ritual is to come together with loved ones, the particular ritual is loaded with years of tradition that some love, some endure, and others try to avoid.</p>
<p>My family is very progressive. Our Christmas celebration requirements have scaled back quite far. So far, in fact, that I&rsquo;ve had to start re-introducing some Christmas traditions for me, my wife and my daughter because I had ended up missing them. I&rsquo;m also very glad we aren&rsquo;t gripped by a tradition set in stone by our grandparents. I just really want a Christmas tree. Because I like it. The tree awakens fond memories.</p>
<p>So, what&rsquo;s my point? Probably that our process is not exclusively based on cold logic and business realities— and that&rsquo;s fine. We are fallible wetware and we need our creature comforts. Framing a process as a ritual changes my expectation of what it should achieve from the raw technical output to a broader consideration of the people involved. I find that useful.</p>
<h2 id="unix-as-a-tradition">UNIX as a tradition</h2>
<p>I&rsquo;m not sure whether I should say UNIX, or hacking, or what, to center the culture around. That debate is a rich source of “well, actually,” which I don’t really care about. It is not only free software, or open source; it is not just MIT, and it is definitely not exclusive to Silicon Valley startup culture. As a developer on Linux with mostly open source tech, you are working in a world wrought from a mix of the cultures I mentioned— even though I’m surely missing some. I&rsquo;ll center it around UNIX, because I think that&rsquo;s largely accurate.</p>
<p>When you use a term such as grok or borked, you&rsquo;re leaning on that tradition. There is a lot of useful shorthand that stems from this common traditional lexicon. Some of this stuff is also quite the shibboleth, a way to identify if people are part of this tradition.</p>
<p>Tech and software development as a culture is widening, and I&rsquo;m sure there are plenty of developers who have minimal exposure to this particular culture. That&rsquo;s fine. Years in corporate dev might not expose you to this; I don&rsquo;t know, I haven&rsquo;t tried it. If you’re going through a software dev bootcamp at 30 you might have no previous interest, and you won&rsquo;t absorb a culture from an X weeks long intensive course.</p>
<p>So while the traditional culture of UNIX is not the only culture of software development and tech, its influence can probably be found all over. And it is my culture. It is where I found my footing. A lot of the culture is also a bit old and outdated. Most of the terminology in the Jargon file is not in use or generally recognized. A lot of people who are in open source now have no particular connection to the wild 80&rsquo;s hackers covered in The Underground Book (<a href="https://www.gutenberg.org/ebooks/4686">free download by the way</a>). A lot of them have never heard of <a href="http://phrack.org/issues/7/3.html#article">The Hacker&rsquo;s Manifesto</a>, which burned bright to me as a teenager.</p>
<p>That&rsquo;s fine. The culture has its traditions and history, and I don&rsquo;t think this culture will be forgotten. It is also fine to leave some of it behind. Some of it was only funny back then, some stuff seemed dated even when I got into it. Some of it is irrelevant. Some of it should be brought with us as good, useful, relevant ideas, interesting thoughts or inspiring history.</p>
<p>I am becoming  increasingly aware of the fact that my way of knowing about computers, my way of getting into programming, is not the only &ldquo;real&rdquo; way. I continue to practice the UNIX tradition; I rather like it. I&rsquo;m very fond of its history. I appreciate a lot of its culture. But it doesn&rsquo;t live or die with whether newcomers know the heritage of grok. I&rsquo;ll happily put a new-formed hacker in touch with these roots. But if they don&rsquo;t want it, we still have plenty to share.</p>
<p>It is easier for me not to expect new people to know about these things and relate to them if I recognize that it is a tradition I was brought up in. To me, understanding tech and knowing programming are intimately tied to this cultural movement, the history and its current shape. This is true for me, very likely true for many others, yet absolutely not true for everyone. And it would be absurd if I didn’t acknowledge that people can come to the same skill set from entirely different cultural traditions.</p>
<h2 id="shared-experiences-rituals-and-traditions-in-software">Shared experiences, rituals and traditions in software</h2>
<p>I’ve found the trope “it isn’t about $TOPIC, it is about people” pretty tiresome in the past. In the back of my mind I’d go, “sure, but really, $TOPIC is pretty important.” Whether $TOPIC concerns technology, a web framework, software quality or open source software, I still find it a bit disingenuous to claim that it is all about people when the actual thing is what brought them all here.</p>
<p>The trope can still feel tired but I have changed my perspective a fair bit. The people situation has to be in decent working order for anything to happen, for things to get built, delivered and used. And people come in a large variety, with many different opinions, fears, hopes and ambitions.</p>
<p>I think we can most quickly find common ground in shared tradition and we can build an understanding of differences more quickly by recognizing if we come from different places. This applies whether you are from enterprise .Net life in an MS-focused stack, straight UNIX with lots of dynamic scripting on top or career-switch-focused bootcamp graduation. All these places will put you into the world with different tools, priorities and often value systems.</p>
<p>I think the shared experiences often help bridge these differences. We all know how dumb computers are. We all know how dumb we programmers can be. This is the common ground.</p>
<p>Tradition is about where you come from and what feels familiar to you, where you find comfort and meet discomfort. To me, rituals are the recurring patterns in how you perform your practice. It could be the practice of your tradition, of your craft, or of your organization. Lots of ritual practice stems from your culture.</p>
<p>The Github Pull Request is an evolution of the patch sent via email and has firm roots in open source hacker culture and tradition. Those roots have deep implications for what a PR is and how it works. They are asynchronous, require no trust and imply low trust. It is a ritual of checking someone’s work before letting it into your project.</p>
<p>You likely have rituals that you built based on your experience and that are your preferred ways of working. Do you subscribe to a TDD approach in everything you do? Do you always start a piece of work by thinking it through? Or maybe you build the dumbest thing you think could work? With practice, you build up more of these patterns and tear down old ones.</p>
<p>Your organization forms rituals from culture and experience in a similar way. Whatever the driving values are, whatever decision-makers hold dear and press for, whatever the organization enshrines as important by its actions: these will shape the pattern of practice and turn into the rituals of that workplace. A lot of them are accidental, incidental and unintentional— the “long-tail consequences” of choices.</p>
<p>Shared ritual becomes a mix of habit, useful shorthand and practical routine. And the more similar your personal rituals are to your organization’s, team’s, or co-workers’, the less effort it is to settle into working with them. If the rituals  are too far apart, you are very likely to experience friction. Rituals that you unknowingly share can turn into moments of strong common understanding with a colleague: “Oh, you do that too!” or “Yeah, makes total sense to me; I do a similar thing.”</p>
<p>There is a grounding in recognizing that you are not a technical optimization machine. You are not a robot making all your choices according to some absolute best. Likely, you are navigating according to your values and experiences. Your technical tradition is your foundation and a place of origin. Your rituals are the tools you’ve built and the experiences you’ve earned. The practice of making software is a complex human endeavor.</p>
<hr>
<p>If you made through to here you might have reflections or feedback. I&rsquo;m happy to take those in via email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. Thanks for reading.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - My Tools, the current product stack</title>
      <link>https://underjord.io/video-my-tools.html</link>
      <pubDate>Fri, 26 Aug 2022 05:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-my-tools.html</guid>
      <description>In this very serious video I will tell you exactly how you must build your products.
Actually, I&amp;rsquo;m just outlining the tools I&amp;rsquo;d typically look at and consider right now. I hope you enjoy. I rather like the intro personally.
Also on YouTube.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
      <content:encoded><![CDATA[ <p>In this very serious video I will tell you exactly how you must build your products.</p>
<p>Actually, I&rsquo;m just outlining the tools I&rsquo;d typically look at and consider right now. I hope you enjoy. I rather like the intro personally.</p>
<p>Also <a href="https://www.youtube.com/watch?v=Y1wPRAHTE_E">on YouTube</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/my-tools-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/my-tools-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/my-tools-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/my-tools.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/my-tools-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
<p>If you have questions or feedback reach out at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - LiveView on Nerves</title>
      <link>https://underjord.io/video-liveview-on-nerves.html</link>
      <pubDate>Fri, 26 Aug 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-liveview-on-nerves.html</guid>
      <description>A video on doing LiveView with Nerves and having fun with input devices. Published as I finish up my backlog of publishing all my videos to my site. It should calm down now.
This was also more thoroughly covered in text in this post.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
      <content:encoded><![CDATA[ <p>A video on doing LiveView with Nerves and having fun with input devices. Published as I finish up my backlog of publishing all my videos to my site. It should calm down now.</p>
<p>This was also more thoroughly covered in text in <a href="/liveview-on-nerves.html">this post</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/liveview-on-nerves-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/liveview-on-nerves-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/liveview-on-nerves-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/liveview-on-nerves.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/liveview-on-nerves-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Self-hosting: Plausible Analytics</title>
      <link>https://underjord.io/video-self-hosting-plausible-analytics.html</link>
      <pubDate>Thu, 25 Aug 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-self-hosting-plausible-analytics.html</guid>
      <description>This video covers the steps to set up Plausible Analytics. Previously published on YouTube and part of me catching up on site publishing :)
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>This video covers the steps to set up Plausible Analytics. Previously published on YouTube and part of me catching up on site publishing :)</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-plausible-analytics-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-plausible-analytics-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-plausible-analytics-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-plausible-analytics.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/self-hosting-plausible-analytics-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Reacting to React</title>
      <link>https://underjord.io/video-reacting-to-react.html</link>
      <pubDate>Sun, 21 Aug 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-reacting-to-react.html</guid>
      <description>Out of freak fortune I haven&amp;rsquo;t significantly used React. This was an experiment in just diving in and trying to use it. Be warned, if you know React it might hurt. Let me know you like it.
This video has been on the YouTube channel for some time but I&amp;rsquo;m catching up on the transcoding and publishing here.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.</description>
      <content:encoded><![CDATA[ <p>Out of freak fortune I haven&rsquo;t significantly used React. This was an experiment in just diving in and trying to use it. Be warned, if you know React it might hurt. Let me know you like it.</p>
<p>This video has been on the YouTube channel for some time but I&rsquo;m catching up on the transcoding and publishing here.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-react-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-react-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-react-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-react.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/reacting-to-react-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - What Is: Phoenix LiveView</title>
      <link>https://underjord.io/video-what-is-phoenix-liveview.html</link>
      <pubDate>Sat, 20 Aug 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-what-is-phoenix-liveview.html</guid>
      <description>A video explainer on what Phoenix LiveView is. Published late to this site, has been up on YouTube for some time. I&amp;rsquo;m catching up :)
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>A video explainer on what Phoenix LiveView is. Published late to this site, has been up on YouTube for some time. I&rsquo;m catching up :)</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/what-is-phoenix-liveview-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/what-is-phoenix-liveview-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/what-is-phoenix-liveview-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/what-is-phoenix-liveview.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/what-is-phoenix-liveview-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Why Self-Host</title>
      <link>https://underjord.io/video-why-self-host.html</link>
      <pubDate>Sat, 20 Aug 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-why-self-host.html</guid>
      <description>A video on why you should consider hosting your own services and data. Especially in the current EU legal landscape. Created in collaboration with GleSYS, a swedish data centre and cloud provider.
This video lays the explanatory groundwork for a series of guides for setting up some self-hosted services.
It went up on YouTube a while back, I&amp;rsquo;m catching up on the website publishing side :)
 function switch_video(element) { var src = element.</description>
      <content:encoded><![CDATA[ <p>A video on why you should consider hosting your own services and data. Especially in the current EU legal landscape. Created in collaboration with GleSYS, a swedish data centre and cloud provider.</p>
<p>This video lays the explanatory groundwork for a series of guides for setting up some self-hosted services.</p>
<p>It went up on YouTube a while back, I&rsquo;m catching up on the website publishing side :)</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/why-self-host-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/why-self-host-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/why-self-host-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/why-self-host.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/why-self-host-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
<p>If you have questions or thoughts to share you can reach me at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>What ID3v2 could have been</title>
      <link>https://underjord.io/id3-specification-and-speculation.html</link>
      <pubDate>Tue, 07 Jun 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/id3-specification-and-speculation.html</guid>
      <description>Speculations and specifications. If you were a Winamp user back in the day, or curate an MP3 collection currently, you might recognize the humble ID3 tag. It is what the metadata in the MP3 file is made up of. First it was pretty limited in the version later dubbed ID3v1. Like any good 2.0 they added a ton more fields, features, removed character limits and it was suddenly ID3v2. The latest spec is ID3v2.</description>
      <content:encoded><![CDATA[ <p>Speculations and specifications. If you were a Winamp user back in the day, or curate an MP3 collection currently, you might recognize the humble ID3 tag. It is what the metadata in the MP3 file is made up of. First it was pretty limited in the version later dubbed ID3v1. Like any good 2.0 they added a ton more fields, features, removed character limits and it was suddenly ID3v2. The latest spec is ID3v2.4 while the most commonly adopted one seems to be ID3v2.3. I recently found myself having a reason to dig into this specification. If you want more background on it you can find it at id3.org (well, it used to be at id3.org/Introduction but id3.org has been down for a while).</p>
<p>ID3 is my favorite kind of standard. A de facto one. MPEG, specifically our beloved MP3 didn&rsquo;t have a way to embed metadata. So one was invented. Tacked on at the end of the file. With ID3v2 it goes at the start of the file. It&rsquo;s a binary format. If you are used to JSON or XML for your data storage this is not so human-readable. It is pretty straight-forward as binary formats go I think. I haven&rsquo;t really parsed one before. The spec documents are mostly clear and understandable (and used to be linkable at id3.org/id3v2.3.0) but not at all mobile-friendly (I suggest browsing them <a href="https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.3.0.html" title="Mutagen specs - ID3v2.3.0">via mutagen instead</a>, they are better).</p>
<p>This post is not about the technical intricacies of the format. There are things I could write about how it avoids conflict with MPEG synchronisation thingies. I could cover how easy it is to implement parsing in Elixir due to the powerful binary pattern matching syntax and how smooth encoding new binaries can be with IO data. That&rsquo;s not what we&rsquo;re doing. This is about a simpler time, where people saw the wild possibilities of music on computers and when people cared about files, damnit. This is about some of the most interesting and entertaining things I&rsquo;ve run across while reading the spec.</p>
<p><em>Note:</em> I&rsquo;m not a scholar of retro computing or someone who does a lot of research typically. This will not be heavy on the footnotes or the digging, this is about vibes man (to totally butcher together two pieces slang, showing my range here).</p>
<p>Parts of an ID3 tag are called frames. Beyond Album titles, Song titles, Artist names and Genre the ID3 tag offers many more interesting ones. I have no clear idea which of these have seen production use but I think they paint a mighty picture of a very different future that could have been .. question mark?</p>
<p>There are generally useful things such as links, embedded images and embedded anythings really. In more recent times there have been additional specifications for Chapters and Table of Contents which are used to provide podcasts episodes with their chapter information and chapter art. Fabulous stuff. Genuinely useful in my day-to-day and if you keep an eye on what I do the purpose of why I dug into ID3 might lead us back to this usage. The newsletter is there after the post if you are curious for any additional notes on this.</p>
<p>The thing that <strong>first got to me</strong> was what really made me see the MP3 + ID3 file in a different light. <em>Play counter</em> (PCNT). This mighty little frame contains a number and it is intended to be the number of times the file has been played. According to spec it should be incremented when it begins playing. This means that the file changes as people &ldquo;consume&rdquo; the media in it. In that way it gives a single song a memory, a very low-resolution story to tell. The only files I expect to change regularly these days are typically considered documents, text files or production projects (image/video/audio production). Most files I deal with are otherwise copied and converted around now.</p>
<p>There is something that feels absurd about your MP3 player changing your song file. On the other hand I find it much worse that fewer and fewer people stick an MP3 file in a player of any kind these days. I bet most players didn&rsquo;t implement this but imagine if they did. The weird cultural touches that could flourish. Personally I&rsquo;ll never consider listening to any song with less than a 100 play count. I only want files that have clearly achieved popularity. Or finding a legit single-digit playcount copy of a popular rip. That means you got it close to the source!</p>
<p>I&rsquo;m aware that I&rsquo;m being silly but it tickles my mind to imagine that world.</p>
<p>The Play counter is the smaller and simpler sibling of <strong>a much wilder frame</strong>. Popularimeter (POPM). It allows storing an arbitrary number of (up to the max frame size of 16 Mb I believe) email and rating pairs. An email string, a rating from 0-255 and a personal play counter. The use of this rating was discussed on <a href="https://en.wikipedia.org/w/index.php?title=ID3&amp;oldid=1081631805#ID3v2_star_rating_tag_issue" title="Wikipedia - ID3 - Star rating issue">the ID3 Wikipedia page</a> as apparently some OS:es and players use it to display a star rating for a song and someone has opinions. I think this frame seems fantastic!</p>
<p><em>Note:</em> If you&rsquo;ve heard the episode of the <a href="https://regprog.com" title="Regular Programming podcast">Regular Programming</a> podcast where I get into this (not released at time of writing) I had forgotten about the personal playcount on the POPM frame.</p>
<p>Neat enough, I can rate my song. Just note. Due to the email address the file can have a lot of these ratings. If you keep passing along the same file you can build up a massive set of ratings. I can see who played it and how much. That could even be delightfully embarassing. Especially for me. I get stuck on songs.</p>
<p>I wonder if this was imagined to be used so that when you got a sample of a song in MP3 format it could come pre-loaded with ratings from a site or something. Or was it truly just the evolving file as a vehicle for crowd-sourced wisdom. Anyway, shove ratings and Personally Identifiable Information in more files and unleash them to the world I say. There&rsquo;s something glorious about it.</p>
<p>Now the thought that ratings might come pre-loaded from some site, baked into a sample file is not wrought from thin air and mind stuff on my part. We have another odd one that gets to business. The Commercial Frame (COMR). &ldquo;This frame enables several competing offers in the same tag by bundling all needed information.&rdquo;</p>
<p>Every offer has the following. Prices specificed in any number of currencies, a date for how long the price is valid, a contact URL for reaching the seller, a &ldquo;Received as&rdquo; field indicating what the product is delivered as which has a number of options such as &ldquo;Standard CD with other songs&rdquo;, &ldquo;File over the Internet&rdquo; or &ldquo;Stream over the Internet&rdquo;. There are a bunch more musically inclined ones but also merchandise and a useful Other. It can also provide the seller&rsquo;s name and <em>an embedded logo</em>.</p>
<p>Store front as a standard. In a file. I find it rather inspired compared to everything being a SaaS.</p>
<p>Then we get the Ownership frame (OWNE) which I assume makes the file officially an NFT. And the Terms of Use (USER) frame which presumably makes it into a smart contract. Or maybe not. The Ownership frame provides information about purchase price, date of purchase and the seller. Nothing about the actual owner. The Terms of Use is what you&rsquo;d expect, the terms under which the file may be used. There is also a way of grouping frames and signing them cryptographically though parts of the implementation is left to the imagination.</p>
<p>Some honorable mentions as genuinely useful frames are Synchronized and Unsynchronized lyrics. I believe there may be a separate one for captions in an Accessibility Extension but I&rsquo;m not sure.</p>
<p>The Event codes frame seems like fun. The event codes can be used for a number of different things, controlling lights, setting of explosives, whatever the player wants to interpret them as. There are some specific ones like start of song, bridge, end of song and a bunch of other musically related ones but you can specify a number of custom ones as well.</p>
<p>Since SQLite is trendy right now there is also no reason you couldn&rsquo;t shove a SQLite database file into the file embedding part. 16Mb for a frame, 256Mb is the limit for the entire tag from what I gather. You an shove a lot of stuff in this thing.</p>
<p>I <strong>really</strong> wonder how much of this was ever used. It mostly seems sanely put together. Just hopelessly irrelevant to how things work now. It makes me really want to do some silly stuff with some MP3 files. Of course you don&rsquo;t actually need an MP3 file. ID3 can work in a multitude of places. It doesn&rsquo;t require media at all.</p>
<p>The possibilities feel endless to me. Completely unapplicable in the present day but I also desperately want to find out about current day usages. I&rsquo;ve just dipped my toe in ID3, if you know the history and the present day of it, reach out. I&rsquo;d love to know more and if you&rsquo;ve written on it, share you stuff.</p>
<p>Note that this format is also quite dense. No curly brackets here unless you put them in your text field. It is impractical at times to deal with a binary format but it does make me happy that the tag header provides more info in 10 bytes than what it would take me to idiomatically express the version of the tag in JSON.</p>
<pre><code>{&quot;version&quot;: 3}  (15 bytes)

vs.

ID3 3 0 0 0001  (10 bytes)
|   | | | |____ 4 bytes of tag size (here set to 1 byte)
|   | | |
|   | | |______ 8 bit flags, here zeroed out
|   | |
|   | |________ spec revision: 0
|   |
|   |__________ spec version: 3
|
|______________ starting indicator &quot;ID3&quot;
</code></pre>
<p>This stuff has been fun to work with. I expect to share more about it as my implementation evolves and becomes useful. For now I needed to share the human aspect of the specification as I&rsquo;ve enjoyed getting to know it and also share the speculations I concocted as I ran into these frames in my work.</p>
<p>If you have questions, comments or insight  to share, do reach out at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or find me on Twitter as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Code BEAM, Stockholm 2022</title>
      <link>https://underjord.io/code-beam-sto-2022.html</link>
      <pubDate>Sun, 29 May 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/code-beam-sto-2022.html</guid>
      <description>I started writing this on the train home from Code BEAM. It&amp;rsquo;s an Erlang, Elixir (and BEAM in general) conference put on by Code Sync which is part of Erlang Solutions. It was a hybrid conference, I was there entirely in person. It was a very good time.
&amp;ldquo;Isn&amp;rsquo;t Underjord Just You?&amp;rdquo; Having gone recently from being a single-person consulting and contracting shop to having a small team I took a rather different approach to this conference.</description>
      <content:encoded><![CDATA[ <p>I started writing this on the train home from Code BEAM. It&rsquo;s an Erlang, Elixir (and BEAM in general) conference put on by Code Sync which is part of Erlang Solutions. It was a hybrid conference, I was there entirely in person. It was a very good time.</p>
<h2 id="isnt-underjord-just-you">&ldquo;Isn&rsquo;t Underjord Just You?&rdquo;</h2>
<p>Having gone recently from being a single-person consulting and contracting shop to having a small team I took a rather different approach to this conference. My team does not have deep connections in this community. They are new and much of their experience stem from different worlds than tech. I planned for two things before the conference and I scheduled a day for each.</p>
<p>On the first day we did &ldquo;team-building&rdquo;. That is, we spent the day together with a few activities, good food, had some drinks and generally got a chance to spend time together. The whole point was to make sure our remote-only team got a chance to find our footing as a group in person.</p>
<p>On the second day I had pulled together some interested people and co-conspirators, some which we knew and some which we didn&rsquo;t. With this gang we set out to run an informal workshop where we played with Nerves and LiveView and just generally doing tech for most of the day. After that we went for a comprehensive dinner sitting in the evening. Tech for breaking the ice and having some fun and then time to get to know one another. The point was to know some other people before delving into the mass of people that is a conference.</p>
<p>I&rsquo;m very happy with both of those days and the entire plan. They themselves would have made the trip worthwhile.</p>
<h2 id="at-dawn-we-confer">At Dawn We Confer</h2>
<p>The conference started. The starting keynote was great! Sanne Kalkman spoke about the importance and value of bringing in more inexperienced developers. She offered some models I found useful for understanding the challenges in transmitting knowledge to them. If you run a team I definitely recommend keeping an eye out for the recording. I have one overall criticism around the keynotes that I&rsquo;ll get back to further down, it had nothing to do with the speakers but rather execution around their talks.</p>
<p>It was followed by something I was actually quite hyped for. I crave a good ritual. Kenneth Lundin presented the new OTP 25 release. It is a tradition at these events and from my understanding it has been a bit of a meme that it is a dry read of the changelog. I don&rsquo;t know if it was ever literally that but this year definitely seemed very well put together and quite succinct compared to what I remember of my first experience a few years back. Regardless, it is a presentation on the work of building Erlang that I find embodies the character of Erlang itself. It doesn&rsquo;t try to be very fancy and it gets the job thoroughly done. I think the ARM 64 JIT is probably the most appealing thing that affects me in the particular changelog. In reality I was most excited because with an inspirational keynote and a technical rundown the conference was officially on.</p>
<h2 id="talks-but-id-have-to-listen-to-people">Talks? But I&rsquo;d Have to Listen to People..</h2>
<p>Overall I was bad about going to and paying attention at talks. I think the preceding days busted my focus a bit as well as a code problem pulling me into try to fix it. I skipped a fair number of talks and didn&rsquo;t pay a ton of attention so unfortunately I won&rsquo;t be able to give a good rundown. I hope someone else shares their notes.</p>
<p>There might not have been a ton of entry-level technical talks. And understandably there was a split between Erlang and Elixir. Had timing been different we would have likely prioritized ElixirConf. There was one talk about LiveView and JS interop which was much appreciated by my team and was useful to me. Hands-on and practical with tools we use day-to-day. Super nice speakers as well, ended up talking a fair bit throughout the conference.</p>
<p>I had a lot of very decent hallway track interactions. Overall there was a lot of big Erlang shop employees there, your Kivra and Klarna folks that are apparently all over the place. My sympathies to the employees at Klarna by the way, they&rsquo;re having no fun right now (see news about Klarna lay-offs). I spoke to some Elixir folk I know that made the trip. I would say there were significantly fewer there than last time and I think the pandemic/travel balance isn&rsquo;t there yet for people. Very understandable.</p>
<p>The hallway track really got going at the bar after though. Those evening conversations were probably my favorite times. Great people, more time to talk.</p>
<h2 id="fame-recognition-finally">Fame! Recognition! Finally!</h2>
<p>One thing was novel and that was the one person that went. &ldquo;Oh, you&rsquo;re the Underjord guy right. I had a question.&rdquo; This is the first in-person conference since I started doing anything in public with Elixir. I rather enjoyed that though I was rather urgently on my way to the restroom which meant I couldn&rsquo;t nearly savor the glory of the conversation.</p>
<h2 id="we-did-it-twice">We Did It Twice!</h2>
<p>Second day beginning keynote was fantastic. As expected with Quinn Wilton and she was not held back but seemed rather unleashed by having Robert Virding on her side, a solid speaker in his own right. Their talk was probably my favorite overall. I&rsquo;m not sure I can apply it to anything but it scratched my brain in a good way. It gave historical context for a number of things about Erlang, it was funny and it was deeply technical and nerdy. Very enjoyable even in moments when a fair bit of the language-nerdery went over my head.</p>
<p>The ending keynote was interesting. Brooklyn Zelenka who I&rsquo;ve seen give a very good keynote at ElixirConf in Prague a good while back gave this one about burning our laurels. Apparently it connects with her talk at EMPEX so I should check that one out too. Essentially it focused on what would be required to operate in a client-first world. It bundled client-first, edge computing and decentralization as problems in an interesting way. Currently talks about decentralization puts my guard up due to crypto-currencies, zero trust and blockchain hype which I have no interest in and do not see as a particularly promising way forward to .. anything.</p>
<p>Brooklyn interestingly steered clear of the coin side of crypto and blockchains almost entirely but did dig into what we might need to abandon to operate in a way that doesn&rsquo;t require talking to a central source of truth. Essentially giving up data consistency was probably the central thing. I thought the talk was very interesting and probably one of the best put together and most clearly presented ones I attended. It also had some of the more unfortunate after..thing. Nothing that reflects on the talk and I&rsquo;ll get to that whole keynotes-thing shortly.</p>
<p>I spoke to her after the talk, mostly because my team demanded Witchcraft stickers but also because I was curious how into Web 3 this Fission company actually is. From the conversation, my charitable and optimistic read is that they want to see what can be achieved in decentralization, in client-first applications and in general in tackling some of those hard problems.</p>
<p>I&rsquo;m not convinced they aren&rsquo;t a bit too tangled in Web 3 but I get the impression that they are more interested in providing options for solving fundamental problems in low-trust computing than facilitating the sale of bored apes. The type of problems they want to solve seem like they may very well give us useful tools for specific problems, whether or not they achieve their goals. That&rsquo;s the optimistic, charitable read from someone who is generally very skeptical about anything that rubs up against Web 3.</p>
<h2 id="on-keynotes">On Keynotes</h2>
<p>Okay. The keynote thing. I don&rsquo;t think there should be a general prompt for questions after talks unless the speaker explicitly asks for it. Particular I think this is the case for a keynote. It would have to be a very interactive keynote indeed for it to benefit from the crowd&rsquo;s input. The keynotes are supposed to set the key note for the conference. For example. Sanne did a very good job setting up the topic of how and why to open the door for juniors. She did get two questions. I don&rsquo;t remember the first one clearly, it didn&rsquo;t particularly detract from the talk but definitely didn&rsquo;t add to it. The second one was .. more of a comment really.</p>
<p>If this was you, sorry to throw you under the bus, I am sure you meant to do some good. Interpreting it kindly this was someone wanting to make sure that no-one understood this to be an absolute. &ldquo;You must hire juniors!&rdquo; A less charitable interpretation is that they thought the speaker wasn&rsquo;t aware that it is a nuanced thing. There was some unclear, unprepared, unrehearsed (because it&rsquo;s a crowd comment trying to become a question) thing and essentially the question was &ldquo;something, something but it isn&rsquo;t always good to hire juniors right?&rdquo;</p>
<p>Sure. Right. True. Not the point. Trying to raise a key note and then having someone decide the rest of the piano really needs to be taken into consideration is not particularly compelling. That nuance fits in the discussion after and about the keynote. Not as an improvised encore by a member of the crowd half way up on stage. This is to a very small degree the responsibility of the person asking the question, they were actively invited to.</p>
<p>The final keynote which I thought was very well put together and ended very well showed where the issue really lies in my eyes. The invitation to questions. Q&amp;A are often poor ad-hoc addendums on any conference talk. They do have their place though and can be quite good for some types of talks. Even better if the speaker is comfortable to do questions during sometimes. It&rsquo;s a different circumstance, a different goal. For a keynote I don&rsquo;t see the point. This ended up with an MC trying to source questions from an auditorium that was satisfied and ready to wrap up if I was to hazard a guess. It was a good end note. Let it end. End it. Don&rsquo;t let it linger.</p>
<p>Quinn &amp; Robert&rsquo;s keynote entirely avoided the issue by going over time, leaving no space-time for questions. So it went through spotlessly.</p>
<p>I&rsquo;m not sure I would have reacted quite so much to this aspect if I hadn&rsquo;t read the fantastic The Art of Gathering by Priya Parker. That book very much informed how I tried to do the small events I did before this conference and it has some important things to say about opening and closing. Overall I think if you have a keynote to actually set a key note, which I think the talks were well chosen to do, I don&rsquo;t think you should invite questions unless the speakers specifically wants it as part of the performance they are putting out there.</p>
<h2 id="in-conclusion">In Conclusion</h2>
<p>All in all a good conference and this above small criticism aside I have zero complaints and tons of appreciation for the event put on.</p>
<p>It felt fantastic to be back in the BEAM community. For me it felt like the start of many more good and even livelier ones. Unfortunately I won&rsquo;t be able to continue that arc for a bit. I&rsquo;m having a second child soon and we&rsquo;ll be quite busy while the Elixir community congregates and cavorts without me. A hard life but a good one.</p>
<p>Am I wrong about keynotes? Did we meet at the conference? Do you go to conferences? Let me know your thoughts at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>LiveView on Nerves</title>
      <link>https://underjord.io/liveview-on-nerves.html</link>
      <pubDate>Thu, 26 May 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/liveview-on-nerves.html</guid>
      <description>I&amp;rsquo;ve played with Nerves for almost as long as I&amp;rsquo;ve been learning and using Elixir. Nerves is a fantastic way of working with hardware along with the BEAM virtual machine and it is great fun for hobbyist projects like Raspberry Pis. Phoenix LiveView is currently my favorite way of making full-stack web development cohesive and keeping the complexity as low as possible for it. I haven&amp;rsquo;t run into any short compelling demos of getting these started together.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;ve played with Nerves for almost as long as I&rsquo;ve been learning and using Elixir. Nerves is a fantastic way of working with hardware along with the BEAM virtual machine and it is great fun for hobbyist projects like Raspberry Pis. Phoenix LiveView is currently my favorite way of making full-stack web development cohesive and keeping the complexity as low as possible for it. I haven&rsquo;t run into any short compelling demos of getting these started together. There is also <a href="https://youtu.be/Fude1tM3kg0" title="YouTube - LiveView on Nerves">a video covering this exact project</a> that I made.</p>
<p>It is not exceedingly hard to do. There are may compelling first demos we could arguably make to achieve it. This is just one way to do something fun with Nerves and LiveView in a fairly minimal way. I have also made a slightly more involved demo project for an event that you can <a href="https://github.com/lawik/pre_hack" title="pre_hack git repository">find on my GitHub</a>. That one includes Tailwind CSS, basic Ecto migrations with SQLite and some other niceties but it is another demo. The docs for that one are sparse, steps are likely missing. You have been warned.</p>
<p>Let&rsquo;s stick to the slightly simpler demo and unpack the process.</p>
<h2 id="lets-get-going">Let&rsquo;s get going</h2>
<p>You will need a Raspberry Pi device, an SD card reader and some patience with me. I ran this on a Pi Zero W and a Pi 400.</p>
<p>The fundamental steps are taken from <a href="https://hexdocs.pm/nerves/user-interfaces.html" title="Nerves - User Interfaces">the Nerves documentation for User Interfaces</a> which has a whole thing about setting up a &ldquo;poncho&rdquo; project with Phoenix.</p>
<p>First, make sure you have <a href="https://hexdocs.pm/nerves/installation.html" title="Installing Nerves">installed Nerves</a> and have Elixir working.</p>

  <div class="code  shell " >
    <div class="meta">
      <span class="language">shell</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mkdir keybored
cd keybored
mix phx.new keybored_ui --module KeyboredUI --no-ecto --no-mailer</code></pre></div>
  </div>

<p>This gives us a project directory for the whole thing, we create a Phoenix project inside of it. We make sure the name is properly capitalized and then we exclude Ecto (no database, plz) and the default mailer (no email, plz). Ecto would require a few extra steps to handle. The mailer just isn&rsquo;t used.</p>
<p>I want a way of providing interesting and useful inputs to play with so we will bring in a special linux-only dependency. It will work on your host machine if it runs Linux and assuming you put yourself in the <code>input</code> group. It will also work very nicely on the Raspberry Pi with Nerves as Nerves is built on Linux.</p>
<p>In your mix.exs, find the deps function and add this one:</p>

  <div class="code  elixir "  data-file="keybored_ui/mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">keybored_ui/mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">..</span>
{<span style="color:#e6db74">:input_event</span>, <span style="color:#e6db74">&#34;~&gt;1.0&#34;</span>}
<span style="color:#f92672">..</span></code></pre></div>
  </div>

<p>Then run:</p>

  <div class="code  shell " >
    <div class="meta">
      <span class="language">shell</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix deps.get</code></pre></div>
  </div>

<p>InputEvent uses the Linux Input subsystem userspace API (apparently) and there is <a href="https://www.kernel.org/doc/html/v4.17/input/event-codes.html" title="Input event codes">some neat reference here</a>. Essentially it lets you get events from things like buttons, keyboards, mice, touchscreens and such in your system.</p>
<h2 id="this-calls-for-a-genserver">This calls for a GenServer</h2>
<p>Time to make a nice and rough GenServer to capture those events. If you want to run this part of the Elixir application on a non-Linux host you&rsquo;ll need to fake some events on your own.</p>
<p>Create the file <code>lib/keybored_ui/inputter.ex</code> and write up the following:</p>

  <div class="code  elixir "  data-file="keybored_ui/lib/keybored_ui/inputter.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">keybored_ui/lib/keybored_ui/inputter.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">KeyboredUI.Inputter</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">GenServer</span>

  <span style="color:#66d9ef">def</span> start_link(_) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>start_link(<span style="color:#a6e22e">KeyboredUI.Inputter</span>, <span style="color:#66d9ef">nil</span>, <span style="color:#e6db74">name</span>: <span style="color:#a6e22e">Keybored.Inputter</span>)
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> init(_) <span style="color:#66d9ef">do</span>
    devices <span style="color:#f92672">=</span>
      <span style="color:#a6e22e">InputEvent</span><span style="color:#f92672">.</span>enumerate()
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>map(<span style="color:#66d9ef">fn</span> {device, info} <span style="color:#f92672">-&gt;</span>
        {<span style="color:#e6db74">:ok</span>, _pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">InputEvent</span><span style="color:#f92672">.</span>start_link(device)
        {device, info}
      <span style="color:#66d9ef">end</span>)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>new()

    {<span style="color:#e6db74">:ok</span>, devices}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_call(<span style="color:#e6db74">:fetch</span>, _, devices) <span style="color:#66d9ef">do</span>
    {<span style="color:#e6db74">:reply</span>, devices, devices}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_info({<span style="color:#e6db74">:input_event</span>, _device, _values} <span style="color:#f92672">=</span> event, devices) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Phoenix.PubSub</span><span style="color:#f92672">.</span>broadcast!(<span style="color:#a6e22e">KeyboredUI.PubSub</span>, <span style="color:#e6db74">&#34;inputs&#34;</span>, event)
    {<span style="color:#e6db74">:noreply</span>, devices}
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>This GenServer starts, registers with a name. In the init function it does the wildest possible thing and gets the list of devices available for InputEvent. All of them. For each device it then starts a link to it which means it will starts sending messages to use.</p>
<p>We store the list of devices since there is some good stuff in there. The name of the device for example. Take a look at the data though, it also provides a bunch of information about the events you can expect from the device and all.</p>
<p>Then we implement one call-handler which will let us fetch the device list. We&rsquo;ll use it later.</p>
<p>The other handler is the input event handler. The only thing that it does is take the event and broadcast it to the &ldquo;inputs&rdquo; topic via Phoenix PubSub.</p>
<p>Then go to your <code>lib/keybored_ui/application.ex</code> and add it to the Supervision tree as:</p>

  <div class="code  elixir "  data-file="keybored_ui/lib/keybored_ui/application.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">keybored_ui/lib/keybored_ui/application.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e">#..</span>
<span style="color:#a6e22e">KeyboredUI.Inputter</span>,
<span style="color:#75715e">#..</span></code></pre></div>
  </div>

<h2 id="next-we-build-a-liveview">Next we build a LiveView</h2>
<p>Create the folder <code>lib/keybored_ui_web/live</code> and the file <code>input_live.ex</code> in it. It should look as follows:</p>

  <div class="code  elixir "  data-file="keybored_ui/lib/keybored_ui_web/live/input_live.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">keybored_ui/lib/keybored_ui_web/live/input_live.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">KeyboredUIWeb.InputLive</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">KeyboredUIWeb</span>, <span style="color:#e6db74">:live_view</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> mount(_params, _session, socket) <span style="color:#66d9ef">do</span>
    devices <span style="color:#f92672">=</span> <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>call(<span style="color:#a6e22e">Keybored.Inputter</span>, <span style="color:#e6db74">:fetch</span>)

    <span style="color:#a6e22e">Phoenix.PubSub</span><span style="color:#f92672">.</span>subscribe(<span style="color:#a6e22e">KeyboredUI.PubSub</span>, <span style="color:#e6db74">&#34;inputs&#34;</span>)

    {<span style="color:#e6db74">:ok</span>, assign(socket, <span style="color:#e6db74">devices</span>: devices, <span style="color:#e6db74">events</span>: [], <span style="color:#e6db74">dot</span>: {<span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">50</span>})}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_info({<span style="color:#e6db74">:input_event</span>, device, values}, socket) <span style="color:#66d9ef">do</span>
    events <span style="color:#f92672">=</span> [{device, values} <span style="color:#f92672">|</span> socket<span style="color:#f92672">.</span>assigns<span style="color:#f92672">.</span>events]
    dot <span style="color:#f92672">=</span> process_movements(socket<span style="color:#f92672">.</span>assigns<span style="color:#f92672">.</span>dot, values)

    {<span style="color:#e6db74">:noreply</span>, assign(socket, <span style="color:#e6db74">events</span>: events, <span style="color:#e6db74">dot</span>: dot)}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> process_movements(dot, []) <span style="color:#66d9ef">do</span>
    dot
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> process_movements({x, y} <span style="color:#f92672">=</span> dot, [value <span style="color:#f92672">|</span> values]) <span style="color:#66d9ef">do</span>
    dot <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">case</span> value <span style="color:#66d9ef">do</span>
        {<span style="color:#e6db74">:ev_rel</span>, <span style="color:#e6db74">:rel_x</span>, points} <span style="color:#f92672">-&gt;</span> {x <span style="color:#f92672">+</span> points, y}
        {<span style="color:#e6db74">:ev_rel</span>, <span style="color:#e6db74">:rel_y</span>, points} <span style="color:#f92672">-&gt;</span> {x, y <span style="color:#f92672">+</span> points}
        {<span style="color:#e6db74">:ev_key</span>, <span style="color:#e6db74">:key_up</span>, <span style="color:#ae81ff">0</span>} <span style="color:#f92672">-&gt;</span> {x, y <span style="color:#f92672">-</span> <span style="color:#ae81ff">5</span>}
        {<span style="color:#e6db74">:ev_key</span>, <span style="color:#e6db74">:key_down</span>, <span style="color:#ae81ff">0</span>} <span style="color:#f92672">-&gt;</span> {x, y <span style="color:#f92672">+</span> <span style="color:#ae81ff">5</span>}
        {<span style="color:#e6db74">:ev_key</span>, <span style="color:#e6db74">:key_left</span>, <span style="color:#ae81ff">0</span>} <span style="color:#f92672">-&gt;</span> {x <span style="color:#f92672">-</span> <span style="color:#ae81ff">5</span>, y}
        {<span style="color:#e6db74">:ev_key</span>, <span style="color:#e6db74">:key_right</span>, <span style="color:#ae81ff">0</span>} <span style="color:#f92672">-&gt;</span> {x <span style="color:#f92672">+</span> <span style="color:#ae81ff">5</span>, y}
        _ <span style="color:#f92672">-&gt;</span> dot
      <span style="color:#66d9ef">end</span>

    process_movements(dot, values)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> render(assigns) <span style="color:#66d9ef">do</span>
    <span style="color:#e6db74">~H</span><span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    &lt;svg viewBox=&#34;0 0 100 100&#34; style=&#34;position: absolute; top: 0; left: 0; height: 100vh; width: 100vw;&#34;&gt;
</span><span style="color:#e6db74">      &lt;circle cx={elem(@dot, 0)} cy={elem(@dot, 1)} r=&#34;6&#34; /&gt;
</span><span style="color:#e6db74">    &lt;/svg&gt;
</span><span style="color:#e6db74">
</span><span style="color:#e6db74">    &lt;div style=&#34;position: relative; max-height: 800px; overflow: hidden;&#34;&gt;
</span><span style="color:#e6db74">    &lt;%= for {d, e} &lt;- Enum.take(@events,100) do %&gt;
</span><span style="color:#e6db74">      &lt;div&gt;&lt;%= @devices[d].name %&gt;: &lt;%= inspect(e) %&gt;&lt;/div&gt;
</span><span style="color:#e6db74">    &lt;% end %&gt;
</span><span style="color:#e6db74">    &lt;/div&gt;
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>As we mount the LiveView we pull the list of devices and subscribe to the &ldquo;inputs&rdquo; topic. We set up some initial state with the list of devices, an empty list of events and .. a dot?</p>
<p>We add the <code>handle_info/3</code> callback matching for the input events we expect to be working with. We add the event information to the events list and for particular events we let a function called <code>process_movements/2</code>  update the dot. We update the assigns from this.</p>
<p>Our render-function renders the assigns by showing an SVG dot on the screen and printing out some recent events.</p>
<p>Let&rsquo;s add it to the <code>router.ex</code> file by replacing the existing entry for <code>&quot;/&quot;</code> with:</p>

  <div class="code  elixir "  data-file="keybored_ui/lib/keybored_ui_web/router.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">keybored_ui/lib/keybored_ui_web/router.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">live <span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">InputLive</span></code></pre></div>
  </div>

<p>That&rsquo;s it for the LiveView part. If you are on Linux you can test this on your host by making sure your user is in the input group <code>sudo usermod -a -G input &lt;username&gt;</code>, restuffing your shell with <code>newgrp input</code> and then running <code>mix phx.server</code>.</p>
<p>On to the Nerves!</p>
<h2 id="more-nerves">More Nerves!</h2>
<p>We need to generate the Nerves firmware project next to the <code>keybored_ui</code> project. This gives us the foundation for a poncho-style project. It is a way of structuring related Elixir projects where the defining feature is that it isn&rsquo;t an Umbrella project. It also let&rsquo;s you run the UI part of your application without fiddling with the Nerves firmware config. In this case, it mostly speeds up project creation, we can use the Nerves generator and the Phoenix generator without needing to merge their efforts.</p>
<p>In our main <code>keybored</code> folder:</p>

  <div class="code  shell " >
    <div class="meta">
      <span class="language">shell</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix nerves.new keybored_firmware</code></pre></div>
  </div>

<p>Go into the new project&rsquo;s <code>mix.exs</code> file and add your UI project as a dependency, turning these two projects into a poncho:</p>

  <div class="code  elixir "  data-file="keybored_firmware/mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">keybored_firmware/mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e">#..</span>
{<span style="color:#e6db74">:keybored_ui</span>, <span style="color:#e6db74">path</span>: <span style="color:#e6db74">&#34;../keybored_ui&#34;</span>},
<span style="color:#75715e">#..</span></code></pre></div>
  </div>

<p>Now we want to slightly change our usage of esbuild in the UI project.</p>
<p>In our <code>keybored_ui/mix.exs</code> we change the esbuild line to read:</p>

  <div class="code  elixir "  data-file="keybored_ui/mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">keybored_ui/mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">{<span style="color:#e6db74">:esbuild</span>, <span style="color:#e6db74">&#34;~&gt; 0.3&#34;</span>, <span style="color:#e6db74">runtime</span>: <span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span>env() <span style="color:#f92672">==</span> <span style="color:#e6db74">:dev</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span>target <span style="color:#f92672">==</span> <span style="color:#e6db74">:host</span>},</code></pre></div>
  </div>

<p>Specifically adding the bit about the Mix.target.</p>
<p>In the <code>keybored_firmware</code> project run:</p>

  <div class="code  shell "  data-file="keybored_firmware/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">keybored_firmware/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix deps.get</code></pre></div>
  </div>

<p>We do some configuration. We set up the wifi unless you know your USB tethering and want to do that. WiFi is reasonable enough to set up, tethering by USB is still immensely useful if something goes wrong so on a Pi Zero, Pi 3A+ or Pi 4 you should be able to take advantage of that.</p>
<p>In the firmware project we hit the <code>keybored_firmware/config/config.exs</code> to get our config configured out:</p>

  <div class="code  elixir "  data-file="keybored_firmware/config/config.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">keybored_firmware/config/config.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e">#..</span>

config <span style="color:#e6db74">:vintage_net</span>,
  <span style="color:#e6db74">regulatory_domain</span>: <span style="color:#e6db74">&#34;SE&#34;</span>,
  <span style="color:#e6db74">config</span>: [
    {<span style="color:#e6db74">&#34;usb0&#34;</span>, %{<span style="color:#e6db74">type</span>: <span style="color:#a6e22e">VintageNetDirect</span>}},
    {<span style="color:#e6db74">&#34;eth0&#34;</span>,
     %{
       <span style="color:#e6db74">type</span>: <span style="color:#a6e22e">VintageNetEthernet</span>,
       <span style="color:#e6db74">ipv4</span>: %{<span style="color:#e6db74">method</span>: <span style="color:#e6db74">:dhcp</span>}
     }},
    {<span style="color:#e6db74">&#34;wlan0&#34;</span>, %{
      <span style="color:#e6db74">type</span>: <span style="color:#a6e22e">VintageNetWiFi</span>,
      <span style="color:#e6db74">vintage_net_wifi</span>: %{
        <span style="color:#e6db74">networks</span>: [
          %{
            <span style="color:#e6db74">key_mgmt</span>: <span style="color:#e6db74">:wpa_psk</span>,
            <span style="color:#e6db74">ssid</span>: <span style="color:#e6db74">&#34;Kontoret&#34;</span>,
            <span style="color:#e6db74">psk</span>: <span style="color:#e6db74">&#34;underjord&#34;</span>
          }
        ]
      },
      <span style="color:#e6db74">ipv4</span>: %{<span style="color:#e6db74">method</span>: <span style="color:#e6db74">:dhcp</span>}
    }}
  ]

<span style="color:#75715e">#..</span>

<span style="color:#75715e"># config from the nerves UI guide</span>
config <span style="color:#e6db74">:keybored_ui</span>, <span style="color:#a6e22e">KeyboredUIWeb.Endpoint</span>,
  <span style="color:#e6db74">url</span>: [<span style="color:#e6db74">host</span>: <span style="color:#e6db74">&#34;nerves.local&#34;</span>],
  <span style="color:#e6db74">http</span>: [<span style="color:#e6db74">port</span>: <span style="color:#ae81ff">80</span>],
  <span style="color:#e6db74">cache_static_manifest</span>: <span style="color:#e6db74">&#34;priv/static/cache_manifest.json&#34;</span>,
  <span style="color:#e6db74">secret_key_base</span>: <span style="color:#e6db74">&#34;HEY05EB1dFVSu6KykKHuS4rQPQzSHv4F7mGVB/gnDLrIu75wE/ytBXy2TaL3A6RA&#34;</span>,
  <span style="color:#e6db74">live_view</span>: [<span style="color:#e6db74">signing_salt</span>: <span style="color:#e6db74">&#34;AAAABjEyERMkxgDh&#34;</span>],
  <span style="color:#e6db74">check_origin</span>: <span style="color:#66d9ef">false</span>,
  <span style="color:#e6db74">render_errors</span>: [<span style="color:#e6db74">view</span>: <span style="color:#a6e22e">KeyboredUIWeb.ErrorView</span>, <span style="color:#e6db74">accepts</span>: <span style="color:#e6db74">~w(html json)</span>, <span style="color:#e6db74">layout</span>: <span style="color:#66d9ef">false</span>],
  <span style="color:#e6db74">pubsub_server</span>: <span style="color:#a6e22e">KeyboredUI.PubSub</span>,
  <span style="color:#75715e"># Start the server since we&#39;re running in a release instead of through `mix`</span>
  <span style="color:#e6db74">server</span>: <span style="color:#66d9ef">true</span>,
  <span style="color:#75715e"># Nerves root filesystem is read-only, so disable the code reloader</span>
  <span style="color:#e6db74">code_reloader</span>: <span style="color:#66d9ef">false</span>

<span style="color:#75715e"># Use Jason for JSON parsing in Phoenix</span>
config <span style="color:#e6db74">:phoenix</span>, <span style="color:#e6db74">:json_library</span>, <span style="color:#a6e22e">Jason</span>

<span style="color:#75715e">#..</span></code></pre></div>
  </div>

<p>For the WiFi I suggest you change your SSID and PSK in the configuration to match your actual WiFI. OR, and this is certainly an option, you change your WiFi access point to have the same credentials as mine. I&rsquo;m saying, you have options.</p>
<p>The later part is essentially copy-pasted from the Nerves UI guide. Keep your secret key base very secret, don&rsquo;t betray your signing salt, use this for fun not function. The reason we set up the full Phoenix config in here is that the config in a dependency, such as <code>keybored_ui</code> which we treat as a dependency, is not inherited by your application. We need this stuff in here or Phoenix has no clue what it should do.</p>
<p>Back to <code>keybored_ui</code> and run the following:</p>

  <div class="code  shell "  data-file="keybored_ui/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">keybored_ui/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">export MIX_ENV<span style="color:#f92672">=</span>dev
export MIX_TARGET<span style="color:#f92672">=</span>host
mix deps.get
mix assets.deploy</code></pre></div>
  </div>

<p>This should build the static assets we need for the firmware project. In <code>keybored_firmware</code> we run the following snippet. You can prepare by <strong>putting the SD card in the card reader</strong>. Please note the MIX_TARGET and match it to your intended hardware device:</p>

  <div class="code  shell "  data-file="keybored_firmware/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">keybored_firmware/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">export MIX_ENV<span style="color:#f92672">=</span>dev
export MIX_TARGET<span style="color:#f92672">=</span>rpi0 <span style="color:#75715e"># or rpi3a, rpi4 or what matches your hardware</span>
mix deps.get <span style="color:#75715e"># Downloads the appropriate system</span>
mix firmware <span style="color:#75715e"># Compiles the firmware</span>
mix firmware.burn <span style="color:#75715e"># Burn to the card, if you have multiple devices it asks</span></code></pre></div>
  </div>

<p>After burning. Shove the card in the Pi. Give it power and within a little while it should go on to the configured WiFi or show up via USB tethering if you do that. You should find it at <a href="http://nerves.local">http://nerves.local</a> which works on everything except Android devices because &hellip; boooh. If something went wrong tethering is your best bet. You can reach it via <code>ssh nerves.local</code> if you have a network connection to it. Otherwise, reburn? I don&rsquo;t know, troubleshooting is out of scope. Poke me in the #nerves channel on the Elixir Slack if you want.</p>
<p>If you plug a keyboard, mouse or other input device into this Pi it should let you steer the dot via arrows or pointer events, it should show the events captured and give you a sense of what LiveView could do for you. If you want to make code changes that&rsquo;s surprisingly simple. Edit the code.</p>

  <div class="code  shell "  data-file="keybored_firmware/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">keybored_firmware/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix firmware <span style="color:#75715e"># Recompile</span>
mix firmware.gen.script <span style="color:#75715e"># Generate an upload script (only necessary once)</span>
./upload.sh nerves.local <span style="color:#75715e"># Upload new firmware over SSH</span></code></pre></div>
  </div>

<p>I think that&rsquo;s it!</p>
<p>Try LiveView, try Nerves, try &lsquo;em together. I enjoy them both immensely. Knock yourself out. If you want more updates about Nerves, get <a href="https://underjord.io/nerves-newsletter.html">the Nerves Newsletter</a>. If you want more of my shenanigans, get <a href="https://underjord.io/newsletter.html">my weekly no-tracking newsletter</a>.</p>
<p>If you have questions, thoughts or more of a comment really. Let me know at  <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Fundamentals &amp; Deployment</title>
      <link>https://underjord.io/fundamentals-and-deployment.html</link>
      <pubDate>Mon, 21 Mar 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/fundamentals-and-deployment.html</guid>
      <description>I&amp;rsquo;ve had Gerhard Lazu on my livestream once where he showed me a way of doing CI/CD with k3s and ArgoCD. The podcast Ship It! that he hosts on Changelog recently had Kelsey Hightower on. Kelsey is to many this guru of the cloud native and Kubernetes space. I&amp;rsquo;ve had good value from what I&amp;rsquo;ve heard him say in the past. In this episode he really spoke to me.
So he essentially said, I&amp;rsquo;m interpreting here, that when it comes to deploying software to servers the documented manual steps for deploying something need to be the canonical reference.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;ve had <a href="https://twitter.com/gerhardlazu">Gerhard Lazu</a> on my livestream once where <a href="https://underjord.io/k3s-argocd-livestream.html">he showed me a way of doing CI/CD with k3s and ArgoCD</a>. The podcast Ship It! that he hosts on Changelog recently <a href="https://changelog.com/shipit/44">had Kelsey Hightower on</a>. Kelsey is to many this guru of the cloud native and Kubernetes space. I&rsquo;ve had good value from what I&rsquo;ve heard him say in the past. In this episode he really spoke to me.</p>
<p>So he essentially said, I&rsquo;m interpreting here, that when it comes to deploying software to servers the documented manual steps for deploying something need to be the canonical reference. Then whether you build bash scripts, Ansible playbooks, Makefiles, Dockerfiles, Terraform, Kubernetes or something else to encode that procedure into something repeatable and scalable that&rsquo;s a separate step. Having documented the process required to set it up means that there is an answer to the question: How do I get this running? An answer that doesn&rsquo;t require you to parse the .yml files or grok Ansible roles and groups.</p>
<p>I feel like I can spring onward from that. I will also very likely not land on Kubernetes as the answer for anything that I&rsquo;m trying to do with deployments. This still seems applicable to me. All of these deployment tools are abstractions for systematizing and automating procedures. How much abstraction you need should reflect who you are and what you are trying to do. Given that we create this documentation for the manual steps to set something up. Then when we are trying to work with a script of some sort that automates the process we can at any time return to that source of truth and see why any particular part of that process is there.</p>
<p>Some might say that a Dockerfile can be that step-by-step description. Typically I rarely see a Dockerfile capture the &ldquo;why&rdquo; in the way documentation typically does. There are also concerns that are mostly about Docker that get enshrined in a Dockerfile. There are things that are done a certain way in a Makefile because that&rsquo;s the most convenient way of describing it in that DSL. An Ansible playbook has a ton of potential concerns that are not about describing how to deploy your application but rather about how to deploy it on particular infrastructure.</p>
<p>So if I write up what steps it takes to get an application running for production in the README and make sure those steps are working, by testing them myself, I can then take whatever automation tool I prefer for simplifying things and translate that documentation into that tool.</p>
<p>One of the big points he made in that conversation is that if you don&rsquo;t figure out the manual process you won&rsquo;t have a clear explanation of the steps involved and if you don&rsquo;t understand the thing you are doing you are going to end up in a mess. You&rsquo;ll may even end up in the situation of &ldquo;I don&rsquo;t want to touch it, I don&rsquo;t know why it works&rdquo;. Kubernetes is that to me and I have no compelling reason to demystify it. I don&rsquo;t operate at immense scale with a massive team. Regardless of the deployment system and underpinnings you work with, someone needs to know how to deploy your application, it needs to be well understood somewhere. At the human scale. Documentation is a good way of making that information transmissible. It is not a solution for immense scale, it is a solution for the human that needs to implement it at scale.</p>
<p>I&rsquo;d be happy to learn some k8s if I have a good reason to. One day I&rsquo;m sure I&rsquo;ll be paid for to work on something involving it. Hearing this view of starting with a solid foundation and only really building as high as necessary off of it was good. It was especially good to hear from someone that&rsquo;s both highly regarded and deeply connected to these massively complex deployment solutions. Seems to me that he deals with massively complex deployment solutions because he deals with massively complex deployments.</p>
<p>Now I don&rsquo;t want to perform all the manual steps myself. I certainly want to automate my deployments. I&rsquo;d rather not introduce a ton of complexity to do it though. I think my ideal level of complexity is just above a bash-script. Somewhere in the range of a bash-script and an Ansible playbook. Maybe it&rsquo;s time to get into Makefiles?</p>
<p>CI/CD isn&rsquo;t exactly a thing that sparks joy for me so far. I&rsquo;ve had a decent run at my current client with GitLab CI. It all feels like a Dockerfile which is sort of not offensively bad but not delightful either. I find them both easier to understand than GitHub Actions which are either extremely straight-forward or mind-bendingly weird depending on what I try to make them do. Once I have it set up it is very satisfying to see all the things I don&rsquo;t have to do happen. The tools often leave something to be desired.</p>
<p>I&rsquo;m currently considering setting up some boxes and running self-hosted things. That&rsquo;s not in line with what Kelsey recommended. He spoke a fair bit about managed services. For me it is more about idealism, enthusiasm &amp; Schrems II. I think I&rsquo;ll go for GitLab because I find Git + CI tooling living together convenient. I think it could be more elegant with smaller tools, Gitea and some other CI tool perhaps, but the integration might just save me a ton of steps. If all my Git repos want CI anyway, might be a win to keep them coupled a bit more tightly.</p>
<p>What this means to me is, I should document the production deployment steps of my various Elixir apps properly, maybe even standardize a bit. Then I can consider automating a way to deploy them well. Right now I&rsquo;m mostly doing a bunch of manual BS because I haven&rsquo;t invested time in automating it and don&rsquo;t love any of the options.</p>
<p>If you have suggestions on lightweight no-nonsense ways to ship things to a server and get them set up I&rsquo;d be curious to hear and synthesize that input into whatever I end up doing. You can share your thoughts via email <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
<p>My writing is supported by a couple of nifty sponsors and they are looking for Elixir developers. If you are an Elixir developer or aspire to be one, please do <a href="/jobs.html">take a look at the jobs</a> for good positions with companies I&rsquo;ve vetted.</p>
<p>Thanks for reading.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Is LiveView Overhyped?</title>
      <link>https://underjord.io/is-liveview-overhyped.html</link>
      <pubDate>Thu, 17 Mar 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/is-liveview-overhyped.html</guid>
      <description>To alleviate any sense of clickbait. No, it is not overhyped as a technology, in my opinion. Let&amp;rsquo;s get into the nuances.
In a recent tweet a while back a member of the Elixir community expressed some frustration with how everything is all about LiveView. It led to some interesting and useful discussion and as someone who writes a fair bit about Elixir, Phoenix and such I have thoughts. Following on that discussions other people expressed further concern about the unreserved enthusiasm with which some of us have pushed LiveView and maybe even more, the PETAL stack.</description>
      <content:encoded><![CDATA[ <p>To alleviate any sense of clickbait. No, it is not overhyped as a technology, in my opinion. Let&rsquo;s get into the nuances.</p>
<p>In a recent tweet a while back a member of the Elixir community expressed some frustration with how everything is all about LiveView. It led to some interesting and useful discussion and as someone who writes a fair bit about Elixir, Phoenix and such I have thoughts. Following on that discussions other people expressed further concern about the unreserved enthusiasm with which some of us have pushed LiveView and maybe even more, the PETAL stack. I doubt they were just thinking of me there but I&rsquo;d be surprised if I wasn&rsquo;t considered a bit of a cheerleader for it. So taking some of that very reasonable criticism and skepticism, let&rsquo;s try to address that and also dig into some of the concerns about the LiveView hype.</p>
<p>The discussion can be somewhat uncomfortable if you are enthusiastic about LiveView. It is easy to get defensive and that&rsquo;s not all that useful. If we look past that the discussion itself can be useful and should be very welcome. There was no drama in that thread I saw and it was all very civil. So lets continue along those lines.</p>
<h2 id="my-understanding">My understanding</h2>
<p>How qualified am I to write about Phoenix LiveView? Somewhat. I&rsquo;ve seen all the keynotes, followed it since the inception and I&rsquo;ve used it a fair bit. Many hobby projects, some production work. I&rsquo;ve used it in anger and joy, run into problems and delights. So I think I can speak with some nuance on it. As I go into writing this my stance is that I find it useful, powerful and a very interesting approach for building web UI. I am enthusiastic about it. It makes some trade-offs that even I find frustrating. It is not best for everything.</p>
<p>Let&rsquo;s line up some of the criticisms brought up around LiveView:</p>
<ul>
<li>It cannot work offline.</li>
<li>Latency is a concern.</li>
<li>Bandwidth and poor connections, are they screwed?</li>
<li>It deviates from standard server-side rendering and will not by default fall back gracefully.</li>
<li>It has been overhyped.</li>
</ul>
<h2 id="offline">Offline</h2>
<p>This is the major trade-off. If you need to make an offline-first Progressive Web App or similar you should either not use LiveView or only use LiveView for the parts that don&rsquo;t make sense offline. There are potential ways of building on top of LiveView and the JS integration to deliver something that mixes this together but you&rsquo;d be off in the wilds with a machete at that point. That&rsquo;s not what is meant to do. There&rsquo;s nothing good about this and there is no big point in dancing around it. The gains you get from shifting state and much of the interactivity to the server in terms of deduplication, security aspects and simplicity comes with the trade-off that it isn&rsquo;t on the client.</p>
<p>I don&rsquo;t expect this to change but I could see efforts like Lumen (Elixir to WASM compiler) or Gleam (targets the BEAM and JS) make it potentially possible to build some shared state management code for both client and server but that&rsquo;s a far ways off if it ever happens. I think for now we should accept that this is the major trade-off we make when using LiveView.</p>
<h2 id="latency">Latency</h2>
<p>LiveView communicates near-realtime events and state changes over WebSockets and that means I can do things like validate as the user types. Compared to a client-side frontend this means we have more latency concerns for similar validations. Round-trip latency affects every update and if we&rsquo;re in Australia talking to the US we&rsquo;ll see ping times of around 250ms which is noticeable. Bad internet weather can cause the same symptom. This is a good case why LiveView really isn&rsquo;t meant for games or other latency-sensitive applications. It may be annoying that your validation takes a quarter of a second but if you are playing an action-oriented game, that&rsquo;s often considered unplayable.</p>
<p>Now LiveView doesn&rsquo;t prevent doing client-side validations and interactivity so for smaller operations you can cut that latency by doing things on the client. However, part of the idea of LiveView is that it reduces the amount of JS you need to write. So saying you need to do client-side JS is a bit of a cop out. Now for actual validation against server state and more serious interactivity LiveView is the same or better as talking to an API using a client-side JS framework. Going over WebSockets should reduce latency compared to most HTTP API calls. The BEAM is good at consistently keeping reasonably latencies so you shouldn&rsquo;t run into many random latency spikes from the runtime.</p>
<p>The latency concerns are warranted if you have a lot of work you could do client-side. Whether you should be doing a separate JS frontend or use LiveView JS interop is really up to you at that point. The latency of LiveView should typically be lower than doing things over a full HTTP requests and responses though since LiveView runs over an open WebSocket. This also lets the server trivially push events and state. So I&rsquo;d expect typically lower latencies than your REST-driven SPA for anything that requires a server roundtrip.</p>
<h2 id="inclusivity-and-poor-connectivity">Inclusivity and Poor Connectivity</h2>
<p>There were also concerns expressed about how it impacts poor connections, bandwidth usage and such. José Valim adressed some of this in the Twitter thread. LiveView sends as little data as it can figure out how to by initially calculating what parts of a template can change and then only sending diffs when state does change. So it would typically send less data than a normal JSON payload to achieve a change. It does of course send more data than performing the operation on the client. Assuming that the javascript code required for the client-side operations is not huge and a cause of bandwidth issues itself.</p>
<p>I will say, I don&rsquo;t know the experience of using WebSocket webapps heavily on poor connections. Being stateful connections I imagine you might drop them due to your network being dodgy. LiveView should recover that pretty well but in that sense it cuts down on overhead by setting up a persistent connection but you might be paying a bit in fragility. WebSockets are more fragile in comparison to HTTP requests that are typically just request, response, done. This could hit any system that leans on WebSockets though, not just LiveView and I&rsquo;ve not seen any real issues using LiveView apps while out and about on cellular data.</p>
<p>I think it is a sensible trade-offs for many modern web apps but it is always worthwhile to consider your audience and whether they might be negatively impacted by your technical choices. My feeling is that LiveView does more by default to keep things lean and efficient than most web apps do when actively trying, just by virtue of having the diffing in place to minimize payloads.</p>
<h2 id="graceful-degradation">Graceful degradation</h2>
<p>I don&rsquo;t know if this is more a criticism someone has brought up or something I have on my mind. How do we handle falling back to the lowest common denominator? How good a citizen of HTML and HTTP can LiveView be? How good is it by default?</p>
<p>LiveView will by default render a normal web page if it cannot establish the WebSocket. If it is a form it will not work unless you put extra effort in. So most likely readable, not interactable.</p>
<p>I&rsquo;d be interested to see a library that does some nice work to essentially create a normal form endpoint based on the submit event handler. It <em>seems</em> like it could be pretty straight-forward. I&rsquo;m not sure if that&rsquo;s just optimism on my part. Making more nuanced forms work well would have all the normal challenges of making something work great both ways.</p>
<p>I think LiveView does a fair job and a reasonable trade-offs here. What I&rsquo;d wish is that it had a perfect fallback strategy if JS isn&rsquo;t available. My wish to see perfect solutions isn&rsquo;t really LiveView&rsquo;s problem though and I&rsquo;ll own that one myself.</p>
<p>It seems entirely possible to do great graceful degradation but as always it isn&rsquo;t necessarily easy or typically a very compelling return on investment. Unfortunately.</p>
<h2 id="overhype">OVERHYPE!</h2>
<p>The Elixir community often self-identifies as a pragmatic bunch. I think that&rsquo;s mostly true. I saw some concern raised that LiveView was being swung around with rose-tinted glasses. I think that certainly happens. We have plenty of people who like the shiny thing. I&rsquo;m a sucker for it personally. However, in fairness to the pragmatic streak, I think the discussion of trade-offs has been with LiveView since the very start. I think José and Chris have addressed it continuously as well. I see the community being enthusiastic but not unreservedly so. I do have my own sense of this hype being a bit much sometimes and I imagine I know where the feeling comes from.</p>
<p>The many conference talks, the numerous articles and the general air of active excitement you might see when following the community. So much of it is about LiveView. As someone actively engaged you&rsquo;ve heard more about LiveView than you can stand by now, probably. Yet every day someone&rsquo;s exposure to that new talk is their first glimpse of LiveView. I think we should keep talking about it, writing about it and sharing how we use it. I&rsquo;m painfully aware that I&rsquo;m mostly not the audience though. It&rsquo;s not new to me, building an app in LiveView is not a thing of excitement to me, it&rsquo;s just a thing I do, a tool I enjoy. Much like Phoenix itself, Phoenix Channels, Ecto and all these tools that I was so excited about are now just things I apply to do what I do.</p>
<p>I&rsquo;m still very interested in Elixir, Phoenix, Ecto, LiveView and everything else. I&rsquo;m excited about what they let me do. Depending on where you are along your path you&rsquo;ll have exhausted your hype about a thing. There&rsquo;s also the possibility that a particular tool just never speaks to you the way it does to others. That&rsquo;s fine as well. In that case the enthusiasm is probably overbearing.</p>
<h2 id="in-summary">In summary</h2>
<p>I don&rsquo;t find LiveView to be overhyped as in being claimed to be better than it technically is. It is cool tech that achieves some interesting capabilities by making some well-chosen trade-offs. I think it&rsquo;s great, a good design, well iterated, a compelling tool. Most importantly for me, it lets me do more building, faster and with fewer frustrations.</p>
<p>It has also been promoted a lot and it continues to be. Some will be breathless in their praise, some will be nuanced. I&rsquo;ve definitely spent my time getting out of breath shouting about it. Hopefully this weighs in on the nuanced side.</p>
<p>Seems good to me but isn&rsquo;t a panacea. <strong>Very hot take.</strong></p>
<p>Thanks for reading. If I&rsquo;m very wrong, very right or somewhere in the middle and you want to have words about it, good or bad, I&rsquo;m available at <a href="mailto:lars@underjord.io">lars@underjord.io</a> and <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
<p>There are some companies that support my publishing and they <strong>want to hire Elixir developers</strong>, even <em>aspiring ones</em>. I personally talk to these companies to figure out what they want and what they can offer, so please do <a href="/jobs.html">check the jobs page</a>. It really does help.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Nerves Quickstart</title>
      <link>https://underjord.io/video-nerves-quickstart.html</link>
      <pubDate>Fri, 04 Feb 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-nerves-quickstart.html</guid>
      <description>This is a labor of love where I just wanted the Nerves project to have a nice promo video. Frank and crew put a lot of work into making Nerves approachable while all their day-to-day use and needs is in the nitty-gritty industry side of it. So what does all that effort get them? Well, they&amp;rsquo;ve gotten it to the point where a complete step by step can be a one minute, 38 seconds long video.</description>
      <content:encoded><![CDATA[ <p>This is a labor of love where I just wanted the Nerves project to have a nice promo video. Frank and crew put a lot of work into making Nerves approachable while all their day-to-day use and needs is in the nitty-gritty industry side of it. So what does all that effort get them? Well, they&rsquo;ve gotten it to the point where a complete step by step can be a one minute, 38 seconds long video. Showing the common steps for getting an RPi from inert to you controlling the system LEDs in Elixir from a web UI. Kudos to Livebook on the interactive web programming bit :)</p>
<p>You can <a href="https://github.com/livebook-dev/nerves_livebook">try Nerves Livebook</a> yourself with any Raspberry Pi you have.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/nerves-quickstart-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - What Is: Elixir</title>
      <link>https://underjord.io/video-what-is-elixir.html</link>
      <pubDate>Sun, 30 Jan 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-what-is-elixir.html</guid>
      <description>Trying to put words to the technology that has been most important to me for the last 4-5 years. Elixir. A language I picked up and now work almost exclusively with and specialize in. It is not the mainstream and I think that has serious advantages.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
      <content:encoded><![CDATA[ <p>Trying to put words to the technology that has been most important to me for the last 4-5 years. Elixir. A language I picked up and now work almost exclusively with and specialize in. It is not the mainstream and I think that has serious advantages.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/what-is-elixir-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/what-is-elixir-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/what-is-elixir-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/what-is-elixir.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/what-is-elixir-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
<h2 id="some-of-my-elixir-content">Some of my Elixir content</h2>
<ul>
<li><a href="https://underjord.io/overview-elixir.html">Overview of Elixir</a></li>
<li><a href="https://underjord.io/why-am-i-interested-in-elixir.html">Why am I interested in Elixir?</a> (an early one)</li>
<li><a href="https://underjord.io/why-am-i-still-excited-about-elixir.html">Why am I still excited about Elixir?</a></li>
<li><a href="https://underjord.io/50-mil-round-on-elixir.html">Building a startup to a $50 million dollar round</a> (interview)</li>
</ul>
<h2 id="projects-mentioned">Projects mentioned</h2>
<ul>
<li><a href="https://elixir-lang.org/">Elixir</a></li>
<li><a href="https://www.phoenixframework.org/">Phoenix</a></li>
<li><a href="https://www.nerves-project.org/">Nerves</a></li>
<li><a href="https://www.membraneframework.org/">Membrane</a></li>
<li><a href="https://github.com/elixir-nx/nx/tree/main/nx#readme">Nx</a></li>
<li><a href="https://livebook.dev/">Livebook</a></li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - How Do: Server</title>
      <link>https://underjord.io/video-how-do-server.html</link>
      <pubDate>Sun, 30 Jan 2022 03:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-how-do-server.html</guid>
      <description>This one has been a while at the back of my mind. A lot of my readers have mentioned wanting to learn Linux and servers. This is an attempt at just unleashing you on the very first steps of your journey to do server work.
It covers setting up a server with Linode (taken as an example) and then installing a webserver on it. I also cover a few bits about securing a server and potential next steps.</description>
      <content:encoded><![CDATA[ <p>This one has been a while at the back of my mind. A lot of my readers have mentioned wanting to learn Linux and servers. This is an attempt at just unleashing you on the very first steps of your journey to do server work.</p>
<p>It covers setting up a server with Linode (taken as an example) and then installing a webserver on it. I also cover a few bits about securing a server and potential next steps.</p>
<p>I hope it helps.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/how-do-server-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/how-do-server-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/how-do-server-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/how-do-server.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/how-do-server-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://help.ubuntu.com/community/IptablesHowTo">Iptables</a></li>
<li><a href="https://www.linode.com/docs/guides/configure-firewall-with-ufw/">Ufw</a></li>
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-create-a-new-sudo-enabled-user-on-ubuntu-20-04-quickstart">Creating a sudo user</a></li>
<li><a href="https://www.cyberciti.biz/faq/how-to-disable-ssh-password-login-on-linux/">Disable SSH password login</a></li>
<li><a href="https://jvns.ca/blog/2021/12/06/dns-doesn-t-propagate/">Julia Evans on DNS</a></li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - What Is: Server</title>
      <link>https://underjord.io/video-what-is-server.html</link>
      <pubDate>Sun, 30 Jan 2022 02:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-what-is-server.html</guid>
      <description>A brief explanation of servers, the concept.
Next step should be to show you good folks some stuff about how I set one up.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>A brief explanation of servers, the concept.</p>
<p>Next step should be to show you good folks some stuff about how I set one up.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/what-is-server-original.mp4" onclick="return switch_video(this);" target="_blank">Original</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/what-is-server-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/what-is-server-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/what-is-server.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/what-is-server-original.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>My Elm Experience</title>
      <link>https://underjord.io/my-elm-experience.html</link>
      <pubDate>Thu, 20 Jan 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/my-elm-experience.html</guid>
      <description>I&amp;rsquo;ve been using Elm since about one year back now. At the start of 2021 I began working with a client that had an existing Elm frontend code base and an early stage backend built in Elixir and Phoenix. This post will be about my experiences running face first into Elm. I&amp;rsquo;ll be vague on some details as I can&amp;rsquo;t share too much about the specific client and code base. I&amp;rsquo;ll be much less vague about Elm itself.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;ve been using Elm since about one year back now. At the start of 2021 I began working with a client that had an existing Elm frontend code base and an early stage backend built in Elixir and Phoenix. This post will be about my experiences running face first into Elm. I&rsquo;ll be vague on some details as I can&rsquo;t share too much about the specific client and code base. I&rsquo;ll be much less vague about Elm itself.</p>
<p>To put my thoughts into context. I know an okay amount of Functional Programming (FP). I know it mainly from the Elixir perspective, so high level, dynamic. Not a lot of focus on types and much more focus on run-time than compile-time. Overall my experience as a programmer trends heavily to high-level and dynamic (I went Perl, PHP, Python with some JS mixed in). I do have some experience poking around more type-heavy languages like ActionScript (for Flash, I think they got really OOP and typed around 4), some C# with .Net, some Java for Android and probably some I&rsquo;m forgetting. But mainly dynamic stuff.</p>
<p>For this project I was mainly brought in to bring the Phoenix code up to snuff and get it production-ready, secure and at all deployable. One of the big things I discovered was just how prototype-level the server application was at this point. I&rsquo;m not privy to what the plan was originally, the original team is not involved any more, but the charitable read is that the server was a proof of concept when I was brought in. You can envision the frontend as a domain-specific multi-user collaborative document editor of some kind. It does a bunch of fancy UI and updates a data structure. The way it was being persisted was essentially by packing the document state into a big chunk of JSON and sending it from Elm over Phoenix Channels as a &ldquo;document_change&rdquo; event. This event would then be distributed to every other user connected and also saved to file. File writes were serialized using a GenServer which took the new data and overwrote the file. A user checks a checkbox, they update their local document state, the ship that document to the server, the server writes it to a file and sends it to everyone else.</p>
<p>You do not have to have read Designing Data-Intensive Applications by Martin Kleppmann to know that this can cause issues. As it happens I was in the midst of reading the book though which made it all the more amusing. So if Bob and Ellen both check different checkboxes at essentially the same time only one change will actually be kept. The last one. And state will flicker back and forth as a result. To make it much more reliable I would need to break apart the individual operations and make sure that they could happen separately. We could deal with some contention if multiple people changed the same thing, but we didn&rsquo;t want someone renaming the document clobbering another persons carefully written note and vice versa. So we didn&rsquo;t need CRDTs or OT for full on collaboration. But we did need more granularity.</p>
<p>I figured out a way to structure this on the Phoenix and Elixir side rather quickly. I normalized the data model with Ecto and backed it with Postgres. Nothing fancy, very straightforward, typical relational database fare. Then I spelunked the Elm codebase to find which change operations actually occurred on this document. That was well structured and I found a type that described all of them. So I stubbed all of them as command events in the Phoenix Channel and made them all throw exceptions until implemented. The concept was that a command could be sent in, we&rsquo;d update the data and then Phoenix would send out that it had committed a command for all connected users. Since the logic for applying the changes already existed in the Elm code on the client side I&rsquo;d essentially be able to re-use that for updates from other users. I&rsquo;d have to mirror it correctly on the server but that seemed tractable (and was, eventually). If a command failed for some reason it would get a rejection event in response and wouldn&rsquo;t be broadcast to other users.</p>
<p>So I had to create JSON commands from these Elm types, produce some new Phoenix Channel events and receive some new ones as well. This seems like it should be very achievable. Maybe I&rsquo;d even be able to be clever about it.</p>
<p>I spent <strong>two weeks more than I expected</strong> to get this done. Fixed bid though, don&rsquo;t worry about my client, they did fine ;)</p>
<p>To get it done I had to accept a few things as undeniable truths (within Elm):</p>
<ul>
<li>The Compiler is Right</li>
<li>The VS Code plugin is sometimes right, sometimes wrong and often breaks, when in doubt run the compiler</li>
<li>Being Clever is Forbidden</li>
</ul>
<p>I was trying to be clever a fair bit and Elm fought me all the way. Elm does not allow you to do side effects, it doesn&rsquo;t take kindly to unspecified behavior and it doesn&rsquo;t really do generic code very much. Especially not with your JSON.</p>
<p>So I had an easy time finding the changes I wanted to turn into commands. Figuring out how I could hook into it gracefully and defer application of the change until committed was significantly more difficult. A big thing was my complete inexperience reading the language and just how resistant it was to me hacking my way to success.</p>
<p>I was recommended the Elm Guide which is a good starting point for getting going with Elm. It is not a good starting point for getting deep into an existing, large, Elm codebase. Or it wasn&rsquo;t enough for my needs at least. There were lots of <code>|&gt;</code> which felt familiar, <code>&lt;|</code> which I could mostly grasp, <code>\foo -&gt;</code> which I&rsquo;d never seen and so much <code>andThen</code> for both <code>Result</code> and <code>Maybe</code>. I had to get into &ldquo;the continuation passing style&rdquo; this particular code used for certain JSON operations, some specialty Monad things they&rsquo;d implemented because apparently <code>Result</code> was not sufficient and some syntax I&rsquo;m still not sure about.</p>
<p>There were choices made in this code way back that I&rsquo;m not really sold on. I can&rsquo;t get into the details but I think that helped make things a bit extra dense and inscrutable at times. However I think the nature of Elm is that it is simple, much like I&rsquo;ve heard Go is simple, and the combos of strict, simple and explicit tends to lead to a lot of code. Not verbose code, Elm is quite lean in many ways, sometimes even terse, but you end up producing a lot of code. Since there are no side-effects to speak of the code is rather easy to grok and even change parts of in isolation. At least once you can read the syntax and common flow well enough. Getting into it I felt like I was the victim of too clever programming. Some of it was/is but much of it was just me being entirely not used to living with <code>Result</code> and <code>Maybe</code>. There is no null except the very explicit <code>Maybe</code> and there is no way to disregard a failure without actually properly dealing with (or defaulting on) a <code>Result</code>. There are very limited options for saying &ldquo;I&rsquo;ll do this part later&rdquo;, &ldquo;this will never happen&rdquo; or &ldquo;I don&rsquo;t care about it&rdquo;. You application has to compile.</p>
<p>This is all very unusual to me.</p>
<p>It is also very powerful. I&rsquo;ve discussed types and typesystems on assorted BEAM Radio episodes and I don&rsquo;t want Elixir to be like this. I&rsquo;ve also discussed types specifically in <a href="https://www.regprog.com/15">this Regular Programming episode</a>. I&rsquo;ve been very resistant. And I mostly still am. I will say that Elm makes an interesting case though. A pragmatic use of strict Functional Programming with all the types, all the time. I bet it does a cleaner job than TypeScript + React/Vue/Angular for the same domain. With some asterisks where those frameworks have a faster path to doing dirty things and Elm requires that you formalize your dirty boundaries. But that&rsquo;s what people want by switching to TypeScript right? Anyway, Elm has definitely let me see types more for the tool they are and what they <strong>can</strong> offer.</p>
<p>I still don&rsquo;t agree with people who go all in on a dynamic language and go &ldquo;this is great!&rdquo; and shortly after &ldquo;we need types!&rdquo; because I think trade-offs are a thing. It feels close to people who discuss writing tests or test coverage as undeniable truths of things that must be done otherwise software is objectively bad. Or people that want a rewrite in Rust. Or that consider Erlang/OTP irrelevant now because they could &ldquo;just be reimplemented in Go/Zig/Rust/Java/their-preferred-language&rdquo;.</p>
<p>The most satisfying thing with Elm is when the frustrating strictness lets up because you&rsquo;ve satisfied the compiler and logic itself, suddenly things work. Once I&rsquo;d bitten the bullet and implemented a ton of decoders and encoders, handled <code>Maybe</code> and <code>Result</code> more than I ever wanted, then suddenly the parts I&rsquo;d implemented worked. And then it was a matter of repeating through all the different commands. Fixing some nuanced behaviors. And it worked really well.</p>
<p>Since this frustrating, learning-heavy, start I&rsquo;ve become fairly fluent at reading and writing Elm. I&rsquo;ve built a number of frontend-centric features for this product, revamped UI and just written a ton of Elm. It is still the case that the amount of code can become quite unwieldy, it grows vertically rather quickly. That is unless you refactor it heavily to flow in pipelines and if you do that you get a fast proliferation of functions which explodes your understanding of things across many functions. And still a lot of vertical text, just elsewhere. So designing software and making the right trade-offs for source code that is easy to work with remains a challenge. It has a particular flavor in Elm but it is the same problem as always.</p>
<p>I&rsquo;ve also introduced four developers that are fairly new to Elixir entirely freshly into Elm. They definitely found the length of a given module frustrating as well. I will say that their process of &ldquo;this is inscrutable&rdquo; over into &ldquo;I guess I just do this and that, and then this&rdquo; was very similar to mine. That put my own frustration into perspective and has probably eased my view on both learning the language by immersion and the code base itself.</p>
<p>I bet we&rsquo;d all have had an easier time learning Elm on a green-field project. Now we had to learn all of it at once to be able to read what we were doing. The code already contained everything from the simple to the intermediate to the advanced but nothing looks like the basic examples from the guide because everything is more nuanced in the messy reality of an actual product. We all had to absorb all of it or we&rsquo;d have large gaps in our understanding. There are parts I still haven&rsquo;t dug into to be fair but I know how this code works and can work on it comfortably.</p>
<p>The compiler is very helpful almost all the time. It can really indicate very well what part of the code is currently incorrect. Now that can still be confusing because types and type aliases can get wild.</p>
<p>The VS Code dev tools have given me so many parsing errors, crashes, delays and mis-indicated errors that I essentially only use it as a soft indicator, my dev server tells me if there is a real issue. Unfortunately the parsing errors often break go-to definition and find references which are incredibly powerful in navigating this codebase. I get that it is an open source community effort and it is super helpful but I can&rsquo;t say it works very well on this codebase. Large files and deep nesting seem to be issues on their tracker already so hopefully it will improve.</p>
<p>The best editor tip I can give when your files get long and you need to work in multiple locations is to split your editor so you are actually editing the same file in multiple panes/buffers. That&rsquo;s been critical for me and I think the pattern of long files is pretty common in Elm, I&rsquo;d be surprised otherwise.</p>
<p>A thing I missed is a quick reference of all operators and other assorted syntax where I could just look up &ldquo;what does this mean?&rdquo;. I usually found it on some nice helpful post someone had put together, probably from the Elm team. But I haven&rsquo;t found something I&rsquo;d call a reference manual to the language. There&rsquo;s the guide which is useful but not very comprehensive. And then the documentation pages for the standard library and community packages are quite fine. And over time I&rsquo;ve bridged most of the missing pieces.</p>
<p>A thing I like is that much like Elixir the Elm community is smaller than mainstream langs. This means it churns slowly, it also seems to have the quality of Elixir I like where many libraries don&rsquo;t change much. If it worked three months ago odds are it still works. Maybe there&rsquo;s something new you&rsquo;d get by updating, but probably you won&rsquo;t need to.</p>
<p>I don&rsquo;t think Elm as a community is something I&rsquo;m into and I&rsquo;m not feeling myself getting deeply invested in this language and ecosystem. But I definitely would consider using it again and now I have it in my back pocket as an option. It also makes me a lot more curious to try Gleam for certain types of work. Gleam is a lang with types on the BEAM. It can also compile to Javascript. From reading the Gleam introduction it felt quite similar to Elm but also a bit more familiar as someone coming from Elixir. I really should try it sometime. Curious if it works well for writing matching backend and frontend code and if you could do some interesting optimistic UI or offline-friendly LiveView with that.</p>
<p>All in all. Elm has been an incredibly useful learning experience to me. It also shows just how tight and neat something can be when it is heavily designed for a tight purpose. While Elm concepts could move outside the frontend space it feels like a DSL for building web frontend apps. Capture interactions, update state, render a representation of the state. Tightly and reliably. Making web UI entirely deterministic.</p>
<p>However, it also highlights that this isn&rsquo;t entirely true. There are a lot of odd kludges you run into to handle some of the nuances of web development. There are things Elm doesn&rsquo;t know about and handling some things via Ports is the least convenient part of Elm. But it is also the trade-off that makes the magic happen. Web application development is an inherently complex thing and there is cost to simplifying parts of it. I think Elm does a good job with that tradeoff. I haven&rsquo;t seen it entirely fall down yet.</p>
<ul>
<li>Easy: Making an interactive UI with many forms of interactivity that reacts in near-realtime and behaves consistently.</li>
<li>Intermediate: Rendering complex SVG UIs that interact heavily with mouse events from a complex state.</li>
<li>Unreasonably complicated: Focusing a text input on first render.</li>
</ul>
<p>You win some, you lose some. Web development.</p>
<p>If I&rsquo;m very wrong about Elm, or very right, or you have something to add or discuss you can reach me at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or via Twitter as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. If you want more of my writing my newsletter is weekly and gets into a lot of nuanced tech career stuff. Signup below, it don&rsquo;t track.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Fixing my LiveView app</title>
      <link>https://underjord.io/livestream-noted-liveview-fixing.html</link>
      <pubDate>Fri, 07 Jan 2022 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-noted-liveview-fixing.html</guid>
      <description>My note-taking web app build in LiveView needed some fixing. Also updated it to use the new heex templates.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>My note-taking web app build in LiveView needed some fixing. Also updated it to use the new heex templates.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2022-01-07-noted-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2022-01-07-noted-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2022-01-07-noted-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2022-01-07-noted-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2022-01-07-noted.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2022-01-07-noted-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Guest post: Cross-cutting Elixir in Teams</title>
      <link>https://underjord.io/guest-feature-amclain-cross-cutting-elixir.html</link>
      <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/guest-feature-amclain-cross-cutting-elixir.html</guid>
      <description>Release day. Although the product team was used to a bit of a hustle to wrap up open features, perform final testing, and prepare for deployment, today presented some additional challenges. This morning Jerry, the team’s product manager, discovered that one of the major features slated for this release was implemented in a way that would cause problems for some key users on the platform. Jerry notified Mark, the backend engineer, who said he might be able to fix the issue by modifying the data sent to the frontend.</description>
      <content:encoded><![CDATA[ <p>Release day. Although the product team was used to a bit of a hustle to wrap up open features, perform final testing, and prepare for deployment, today presented some additional challenges. This morning Jerry, the team’s product manager, discovered that one of the major features slated for this release was implemented in a way that would cause problems for some key users on the platform. Jerry notified Mark, the backend engineer, who said he might be able to fix the issue by modifying the data sent to the frontend. Jerry thought he might be out of the woods and back on track for the release. A couple hours later Mark returned with bad news: The fix would cause a breaking API change. Although Mark completed the fix on his end, the issue would now need to be handed over to Kelly, the team’s frontend engineer. Kelly had taken the day off for a personal emergency. Jerry was now left with a difficult decision: delay the release of a feature that had been publicly announced to the users, or proceed with the release knowing that some key users were going to have a bad experience. How could Jerry and his company’s leadership set themselves up with more options in this scenario?</p>
<p><em>This is a guest post to Underjord where Alex McLain, who I know from the Nerves community, lays out his ideas about how Elixir can fill a special role in software teams. Thanks to <a href="https://dockyard.com">DockYard</a> for supporting our work on this piece. They are a digital product consultancy with deep roots in the Elixir community and ecosystem. You&rsquo;ve seen them support Phoenix, ElixirConf as well as this blog and several others. Let&rsquo;s hear from Alex.</em></p>
<p>I started my professional software engineering career working with low-level control system hardware for commercial audio/video system automation. These systems contained a central controller unit that orchestrated the other devices in the system, therefore uptime and resiliency were important properties. That led me to search for programming languages that were designed with an emphasis on these properties, in which Elixir was one that rose to the top.</p>
<p>During this time I had also been using Ruby in areas not sensitive to system performance, due to its developer friendliness. This reduced the time it took to deliver a control system project. This led to an opportunity to do web development in Ruby, so I made the transition. While working on an enterprise IoT platform built with Ruby, we started running into performance and cost challenges at scale. Recalling that Elixir could be a good solution to these problems while maintaining the developer friendliness of Ruby, we transitioned part of the system to Elixir and were able to reduce the compute resources required for the platform. The culmination of my experience at this point drew me to IoT consulting, with a focus on utilizing Elixir across the stack for developing both software and hardware.</p>
<p>It’s probably no surprise that web-based platforms running at scale are complex. Today more and more devices that were once standalone are now “connected”, utilizing cloud and/or edge computing to supplement their functionality. This means that products are no longer confined to the bounds of a website or a widget. A modern product can consist of hardware, a cloud platform, data science, and web/mobile/desktop applications to deliver the full user experience. Product complexity is increasing substantially. The obvious approach is to staff engineers from this wide range of disciplines onto the product team. However, the budgets for these connected products have not necessarily grown proportionally to their complexity, which can result in fewer resources than ideal to create the product. In practice this can manifest into situations like in the opening story: The critical path for feature delivery can depend on specific team members more frequently, which can not only affect their work/life balance, but also increases overall project risk. So what are some other approaches to this situation?</p>
<p>If you practice Agile you’re most likely familiar with building cross-functional teams, in which each member of the team is able to perform more than one role. If Jerry’s team had been set up this way, the release wouldn’t hinge on a single team member. Mark would have been able to complete the changes to the application without being dependent on Kelly. For projects that span a small number of disciplines, like frontend and backend, this solution may be enough. However, for more complex projects that include more disciplines like DevOps, firmware, native mobile, data science, etc., then it’s likely not feasible for every team member to be able to work proficiently across every discipline. There are just too many skills for any one person to learn. To overcome this challenge we need to look at cross-functional teams from another angle: the barrier to entry of each discipline, the amounts in which these disciplines contribute to the project’s complexity, and team size.</p>
<p>Barriers to entry can be created when drastically different tooling is used across disciplines. One notable example is the choice of programming languages used, each which can have a substantial learning curve to reach proficiency. The greater the number of languages in a project, the more complex the project becomes, and the more difficult it can be for someone to work in different areas of the project. For example, a typical IoT project could utilize at least four different programming languages: Elixir, JavaScript, C, and Bash.</p>
<p><a class="image" href="assets/images/blog/amclain-1.png" target="_blank"><img alt="Chart showing examples of development types with typical languages associated. Firmware and C. Backend and Elixir. Frontend and JavaScript. DevOps and Bash." class="blog-image" src="assets/images/blog/amclain-1.png"/></a></p>
<p>A strategy to reduce this barrier and reduce complexity is to find tooling that can be utilized by multiple disciplines. In this case, let’s say the team decided to use Elixir as their primary programming language across disciplines. Elixir’s Phoenix web framework can be used for the frontend and backend, Mix tasks for DevOps, and the Nerves Project for firmware. Given that the community is actively expanding Elixir’s capabilities, Nx and Livebook are relatively new tools that could start bringing data science into the stack as well. Now all of the disciplines share a certain amount of baseline knowledge with each other, and can understand each other’s code. There may still be a need for traces of other technologies, but stepping into another discipline is more fluid.</p>
<p><a class="image" href="assets/images/blog/amclain-2.png" target="_blank"><img alt="Chart showing examples of development types all grouped under Elixir. Firmware, Backend, Frontend and DevOps. All in Elixir." class="blog-image" src="assets/images/blog/amclain-2.png"/></a></p>
<p>Fluidity comes with other benefits as well. If team members can move across parts of the project easier, this breaks down silos within the team that could have a tendency to form otherwise. Each section of code can now have more eyes on it, increasing the opportunity for engineers to help each other, and improving code quality. This also spreads project knowledge across more of the team, reducing the amount of information that could get lost in translation, and enabling more team members to be able to help solve issues that arise. If your product management methodology is to deliver features in vertical slices, this can also enable engineers to work across more or all of the slice. From a resourcing perspective, if more team members are cross-discipline, the project may require fewer resources to achieve the same output compared to a project resourced traditionally. This can also be beneficial for startups or those trying to get to MVP quickly with a small team.</p>
<p>For companies that maintain more than one product or are involved in consulting, not only can it be beneficial to find tooling that works across multiple areas of a given project, but that also works across multiple projects in the company. From a technical standpoint this can be an opportunity to leverage code reuse, allowing new projects to start on second base. From a staffing perspective it can reduce the friction of rotations between projects.</p>
<p>Although we’ve covered many advantages of this philosophy, there are situations it may not apply well to. For example, if a mobile app is required, there is currently no well-established way to do this in Elixir. Inevitably other technologies like React Native and JavaScript would need to be brought in, resulting in less reuse across disciplines. Another challenge is that engineers who are fluid across disciplines need to have a broader skill set and be able to keep more context in their head. One way to help less experienced engineers in this environment is to start them off in an area that suits their strengths, and support them in expanding throughout the project from there. However, some engineers prefer to narrow their focus rather than to expand more broadly. Finally, diminishing returns can be encountered with larger teams, especially when each discipline has its own squad. At this scale it can be more effective to run focused squads rather than having them be jacks of all trades.</p>
<p>In conclusion, modern products are increasing in complexity and requiring more engineering disciplines to be involved in creating and maintaining them. Cross-functional teams that find technological reuse across disciplines can maintain an advantage in this environment. I’ve personally found Elixir to be a great programming language for this due to its solid foundation, versatility across several disciplines, and the community’s drive to keep expanding it into new areas.</p>
<p><em>Big thanks to Alex for writing this up and letting me share it. If you want to reach him you can find the user <code>amclain</code> on the Elixir Slack or reach out to alex (at) alexmclain.com.</em></p>
<p>If you have thoughts about guest posts as far as Underjord is concerned you&rsquo;ll find me as usual at <a href="mailto:lars@underjord.io">lars@underjord.io</a> and on Twitter as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. Please note, I don&rsquo;t take random solicitations about guest posts. That&rsquo;s weird.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Building Out Indie Livestreaming with LiveView and Membrane</title>
      <link>https://underjord.io/livestream-indie-livestreaming-continued.html</link>
      <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-indie-livestreaming-continued.html</guid>
      <description>On a previous stream we tested out the library for RTMP in Membrane to achieve streaming on our own in Elixir. This time we make it more convenient, flexible and work our way towards using it.
Being able to prepare and start livestreams, setting them up with LiveView, giving meaningful progress to users. Things are fun when you have near real-time events :)
 function switch_video(element) { var src = element.</description>
      <content:encoded><![CDATA[ <p>On a previous stream we tested out the library for RTMP in Membrane to achieve streaming on our own in Elixir. This time we make it more convenient, flexible and work our way towards using it.</p>
<p>Being able to prepare and start livestreams, setting them up with LiveView, giving meaningful progress to users. Things are fun when you have near real-time events :)</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-26-building-out-livestream-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-26-building-out-livestream-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-26-building-out-livestream-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-26-building-out-livestream-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-26-building-out-livestream.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-26-building-out-livestream-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Elixir and Computer Vision with evision</title>
      <link>https://underjord.io/livestream-elixir-and-computer-vision.html</link>
      <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-elixir-and-computer-vision.html</guid>
      <description>From Twitter I stumbled on this neat, still early, but working project for using the OpenCV library from Elixir. So I decided to give it a whirl!
The project is called evision and I had a great time on stream screwing around with it :)
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
      <content:encoded><![CDATA[ <p>From Twitter I stumbled on this neat, still early, but working project for using the OpenCV library from Elixir. So I decided to give it a whirl!</p>
<p>The project is called <a href="https://github.com/cocoa-xu/evision">evision</a> and I had a great time on stream screwing around with it :)</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-21-evision-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-21-evision-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-21-evision-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-21-evision-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-21-evision.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-21-evision-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Improving a GenServer, My IoT Lightswitch</title>
      <link>https://underjord.io/livestream-improving-a-genserver.html</link>
      <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-improving-a-genserver.html</guid>
      <description>GenServer is the typical Actor abstraction in Erlang and Elixir. I&amp;rsquo;ve used one for talking to my Elgato Keylights in the lightswitch project. I made it sort of dumb.
It was a quick job so I want to improve it in a few ways.
 Make it not need to pull the new state from lights before sending commands. Pull state in the background occasionally to keep relatively up-to-date. Always fetch status after sending commands.</description>
      <content:encoded><![CDATA[ <p>GenServer is the typical Actor abstraction in Erlang and Elixir. I&rsquo;ve used one for talking to my Elgato Keylights in the <a href="https://github.com/lawik/lightswitch">lightswitch project</a>. I made it sort of dumb.</p>
<p>It was a quick job so I want to improve it in a few ways.</p>
<ul>
<li>Make it not need to pull the new state from lights before sending commands.</li>
<li>Pull state in the background occasionally to keep relatively up-to-date.</li>
<li>Always fetch status after sending commands.</li>
<li>If there is time, improve the lighting on the keybow to represent the state of the lights.</li>
</ul>
<p>Spoiler, most of it got done during the stream :)</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-17-improving-lightswitch-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-17-improving-lightswitch-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-17-improving-lightswitch-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-17-improving-lightswitch-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-17-improving-lightswitch.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-12-17-improving-lightswitch-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Indie Livestreaming with Elixir &amp; Membrane</title>
      <link>https://underjord.io/livestream-indie-livestreaming-with-membrane-framework.html</link>
      <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-indie-livestreaming-with-membrane-framework.html</guid>
      <description>So Software Mansion, the makers of Membrane Framework, have kindly conspired with me to get RTMP ready for Membrane. RTMP is the protcol used when streaming from popular open source tool OBS. The RTMP module is currently at a technical preview level so be warned, might have some sharp edges.
They made sure there was a chain of building blocks from RTMP to HLS (HTTP Live Streaming) and now I can actually set up a completely independent version of the Underjord livestreams.</description>
      <content:encoded><![CDATA[ <p>So Software Mansion, the makers of Membrane Framework, have kindly conspired with me to get RTMP ready for Membrane. RTMP is the protcol used when streaming from popular open source tool OBS. The RTMP module is currently at a technical preview level so be warned, might have some sharp edges.</p>
<p>They made sure there was a chain of building blocks from RTMP to HLS (HTTP Live Streaming) and now I can actually set up a completely independent version of the Underjord livestreams. So that&rsquo;s what we are going to be looking at this stream.</p>
<p>What parts are there? What considerations and concerns go into it? How does it work? How does it scale?</p>
<p>I think we&rsquo;ll be able to show it MVP style on this stream as well as discuss where I&rsquo;d like to take it and maybe even hack some towards that.</p>
<p><em>Note: This livestream happened back in November, I&rsquo;m a bit behind on processing those livestream recordings to the site. There has since been a follow-up to this one as well. They&rsquo;ll all arrive here eventually but they are on <a href="https://www.youtube.com/c/Underjord/videos">the YouTube channel</a> already.</em></p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-05-membrane-live-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-05-membrane-live-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-05-membrane-live-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-05-membrane-live-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-05-membrane-live.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-11-05-membrane-live-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Winners - Manning book giveaway</title>
      <link>https://underjord.io/giveaway-manning-winners-2021-11.html</link>
      <pubDate>Mon, 20 Dec 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/giveaway-manning-winners-2021-11.html</guid>
      <description>Just a brief note announcing the three winners of the giveaway that take home a copy of Elixir in Action by Saša Jurić. Thanks to Manning for providing those copies. If you want 35% off of their books you can use the discount code nlunderjord21 and you can find their collection via this link that definitely will track some statistics about click-through rates (you have been warned): Manning&amp;rsquo;s other books.</description>
      <content:encoded><![CDATA[ <p>Just a brief note announcing the three winners of <a href="/giveaway-manning-2021-11.html">the giveaway</a> that take home a copy of Elixir in Action by Saša Jurić. Thanks to Manning for providing those copies. If you want 35% off of their books you can use the discount code <code>nlunderjord21</code> and you can find their collection via this link that definitely will track some statistics about click-through rates (you have been warned): <a href="http://mng.bz/QWQ1">Manning&rsquo;s other books</a>.</p>
<p>The winner&rsquo;s have all been notified and codes for the ebooks sent out. And they are (names as permitted):</p>
<ul>
<li>From the old guard of <a href="/newsletter.html">newsletter</a> subscribers, since many, many issues back, Marcelo!</li>
<li>New newsletter subscriber, ryoung786!</li>
<li>Podcast subscriber for <a href="https://regprog.com">Regular Programming</a>, Manuel!</li>
</ul>
<p>Congratulations to our three winners and better luck next time to everyone else. Thanks for giving it a shot. I&rsquo;ll note that the pod-pool and the old subscriber pools were the biggest chances of winning. So maybe that&rsquo;s a hint to get in on that newsletter now. I fully intend to keep offering opportunities to my existing readership as I very much appreciate their attention.</p>
<p>If you have question, you can as per usual reach me at <a href="mailto:lars@underjord.io">lars@underjord.io</a> by email or on Twitter <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Are you hiring Elixir developers?</title>
      <link>https://underjord.io/are-you-hiring-elixir-developers.html</link>
      <pubDate>Tue, 14 Dec 2021 12:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/are-you-hiring-elixir-developers.html</guid>
      <description>Is your company actively trying to hire Elixir developers? Or should you be?
I care about making good software happen. Even if I&amp;rsquo;m not writing it. And good software is formed from good teams.
To help this happen I want to connect companies and developers in the Elixir space. I know and connect with plenty of both.
This is the first time I mention this particular offer to the public. If the interest is at the level it seems this will also be the only chance to get in at the initial pricing.</description>
      <content:encoded><![CDATA[ <p>Is your company actively trying to <strong>hire Elixir developers</strong>? Or should you be?</p>
<p>I care about making good software happen. Even if I&rsquo;m not writing it. And good software is formed from good teams.</p>
<p>To help this happen I want to connect companies and developers in the Elixir space. I know and connect with plenty of both.</p>
<p>This is the first time I mention this particular offer to the public. If the interest is at the level it seems this will also be the only chance to get in at the initial pricing. If your company is ready to take finding great Elixir developers seriously, reach out at <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Are Contexts a Thing?</title>
      <link>https://underjord.io/are-contexts-a-thing-in-phoenix-web-apps.html</link>
      <pubDate>Mon, 06 Dec 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/are-contexts-a-thing-in-phoenix-web-apps.html</guid>
      <description>The discussion of Contexts in Phoenix and their general usefulness feels like a common point of disagreement in Elixir. I&amp;rsquo;ve gathered that the discussions went high and low as it was becoming a thing in mainline Phoenix. I don&amp;rsquo;t really care for controversy but what I see is a topic which gets confusing to wrangle with and which I never know quite how to explain. So this will be an attempt to explain what Contexts as provided are, cover some common concerns around this and two rather opposing suggestions about how to deal with them.</description>
      <content:encoded><![CDATA[ <p>The discussion of Contexts in Phoenix and their general usefulness feels like a common point of disagreement in Elixir. I&rsquo;ve gathered that the discussions went high and low as it was becoming a thing in mainline Phoenix. I don&rsquo;t really care for controversy but what I see is a topic which gets confusing to wrangle with and which I never know quite how to explain. So this will be an attempt to explain what Contexts as provided are, cover some common concerns around this and two rather opposing suggestions about how to deal with them. Then I&rsquo;ll conclude with some kind of nuanced take that doesn&rsquo;t really help you <strong>decide what you believe</strong> because <em>that&rsquo;s your job</em>.</p>
<h2 id="the-default---contexts-in-phoenix">The Default - Contexts in Phoenix</h2>
<p>The Phoenix generators are very helpful and if you are generating controllers etc. they will gladly help you generate a <em>Context</em>. If you check the Phoenix documentation you can see what&rsquo;s <a href="https://hexdocs.pm/phoenix/1.6.1/contexts.html#starting-with-generators">generated by default</a>. I&rsquo;ll add that code below for reference. You can generate your own (this was 1.6.1) with <code>mix phx.gen.context Catalog Product products title:string</code> or get the same as part of a <code>mix phx.gen.html</code> call. The thing that is called a Context is in <code>lib/hello/catalog.ex</code> as the <code>Hello.Catalog</code> module. You can see the file here, I&rsquo;ll strip out docs, aliases and imports to give a more skimmable thing and then we&rsquo;ll try to say something about <strong>what it is</strong>.</p>

  <div class="code  elixir "  data-file="lib/hello/catalog.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/hello/catalog.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Hello.Catalog</span> <span style="color:#66d9ef">do</span>
  <span style="color:#66d9ef">def</span> list_products <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Repo</span><span style="color:#f92672">.</span>all(<span style="color:#a6e22e">Product</span>)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> get_product!(id), <span style="color:#e6db74">do</span>: <span style="color:#a6e22e">Repo</span><span style="color:#f92672">.</span>get!(<span style="color:#a6e22e">Product</span>, id)

  <span style="color:#66d9ef">def</span> create_product(attrs \\ %{}) <span style="color:#66d9ef">do</span>
    %<span style="color:#a6e22e">Product</span>{}
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Product</span><span style="color:#f92672">.</span>changeset(attrs)
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Repo</span><span style="color:#f92672">.</span>insert()
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> update_product(%<span style="color:#a6e22e">Product</span>{} <span style="color:#f92672">=</span> product, attrs) <span style="color:#66d9ef">do</span>
    product
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Product</span><span style="color:#f92672">.</span>changeset(attrs)
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Repo</span><span style="color:#f92672">.</span>update()
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> delete_product(%<span style="color:#a6e22e">Product</span>{} <span style="color:#f92672">=</span> product) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Repo</span><span style="color:#f92672">.</span>delete(product)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> change_product(%<span style="color:#a6e22e">Product</span>{} <span style="color:#f92672">=</span> product, attrs \\ %{}) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Product</span><span style="color:#f92672">.</span>changeset(product, attrs)
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>So a Context <code>Catalog</code> with only a single type of entity called <code>Product</code> to manage can look like this. If we&rsquo;d add another schema of concern we&rsquo;d get <code>list</code>, <code>get</code>, <code>create</code>, <code>update</code>, <code>delete</code> and <code>change</code> for that as well. So this is CRUD. I&rsquo;m not insulting, that&rsquo;s shorthand for Create, Read, Update &amp; Delete. Plus <code>list</code> which is a common need and something called <code>change</code> which is a bit unusual and relates to making forms from changesets in Phoenix.</p>
<p>The Context is <em>just a module</em>. That is, it is not a required portion of making the Phoenix web framework do web application stuff. Many pieces you deal with in a typical Phoenix app are load bearing functional components. Functional as in &ldquo;it performs a function&rdquo; or &ldquo;has a job to do&rdquo;, not as in Functional Programming. Your Endpoint, your routes, your controller, your views and your templates are all fulfilling a particular piece of the puzzle. And for database operations your have Ecto providing a number of pieces that do work: Schemas, Changesets, Migrations and the Repo are all there to fulfill a certain functionality requirement. If you generate using <code>mix phx.gen.html</code> the Context will take a load-bearing position in your code, between the Phoenix Controller and Ecto. But it is not itself a required part of making the puzzle work. You can make those same calls from a Phoenix controller and that works fine.</p>
<p>I think this not-absolutely-necessary aspect of the Context is a common place for people to get tripped up. You are in there learning how to make the web app work at all. You need every part, except this piece that gets a bit fuzzy around the edges. What is it? What should I put there? I&rsquo;m not convinced Contexts are bad. But they introduce design choices for the developer to make into a system that is otherwise very mechanical. Phoenix is full of design choices that are already made for you. Contexts recommend a certain approach but they don&rsquo;t require any particular approach, there is not forcing function to keep you in line in any manner. While a Phoenix Controller works a certain way, routes work a certain way, LiveView works a certain way. Contexts are not nearly so tangible. You don&rsquo;t <code>use Phoenix.Context</code>, there&rsquo;s no <strong>there</strong> <em>there</em> in a sense.</p>
<p>And not being a mechnically required component doesn&rsquo;t invalidate anything from being useful code. We would have to strip away a lot of abstraction if we only wanted to deal with the strictly mechanically necessary. For example, <code>Ecto.Changeset</code> is mostly a pure abstraction but it is mechanically pretty much required for operating Ecto effectively. The reason Phoenix is a nice web framework to work with it that it manages a lot of complexity for you, allowing you to opt in to it if you need it while keeping it mostly out of sight for most of your work. So that is a way in which the Phoenix Context sticks out compared to other parts of Phoenix. It is a less clear-cut piece, it is providing a software design for you.</p>
<p>So a Context is not required. Why did it get introduced? What is it for?</p>
<p>The Context is an attempt to provide order that your apps can grow with. It is an attempt to provide a generic way of encapsulating concerns which could make managing your code-base as it grows easier. You can also see it as the bridge between <code>HelloWeb</code> which is your Phoenix code and <code>Hello</code> which is your Elixir code. Essentially <code>Hello.Catalog</code> being your interface into Elixir-code, devoid of web concerns.</p>
<p>I get why there has been a bunch of discussion and contention about whether Phoenix Contexts are good. It is a design and it is a design of &ldquo;your code&rdquo; and in that way it is quite different from the design of other parts of Phoenix. It goes a step beyond a minimal generated controller and tries to provide you with some scaffolding for what I imagine is a suggested best practice. It is not a suprise that people have opinions on good software design.</p>
<p>One common criticism of Contexts are that they are a somewhat clumsy abstraction on top of just calling Ecto. Another is that they are a leaky abstraction. So what is this about?</p>
<p>I think we can lean on Saša Jurić to touch on some of that.</p>
<h2 id="refining-contexts-according-to-saša-jurić">Refining Contexts according to Saša Jurić</h2>
<p><em>&ldquo;The problem with the “official” approach is that the interface concerns leak into the core, blurring the border between the two layers. This defeats the main goal of the design: clear separation between the layers.&quot;</em></p>
<p>This quote is from <a href="https://medium.com/very-big-things/towards-maintainable-elixir-the-core-and-the-interface-c267f0da43">this worthwile article</a> on how Saša and Very Big Things have worked on making their Elixir code maintainable.</p>
<p>You really should read it to get the full perspective but the key point about leaking here is concerned with this:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#f92672">..</span>
  <span style="color:#66d9ef">def</span> create_product(attrs \\ %{}) <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">..</span>
  <span style="color:#66d9ef">def</span> update_product(%<span style="color:#a6e22e">Product</span>{} <span style="color:#f92672">=</span> product, attrs) <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">..</span></code></pre></div>
  </div>

<p>These function heads indicate that they take any map of attributes to turn into a product. Usually these come straight from an API, HTML or LiveView controller and contain a map of string keys pointing at varyingly typed data. It is then, inside the Context, turned into proper data via an Ecto Changeset cast, validation happens and can fail. So it is a thin layer on top of the Ecto Changeset API and as an abstraction it doesn&rsquo;t provide a lot of clarity about what you can expect to do and still have it work. If we send in <code>%{&quot;name&quot; =&gt; &quot;Foo&quot;}</code> that&rsquo;d be correct, <code>%{name: &quot;Foo&quot;}</code> would also work fine, <code>%{&quot;title&quot; =&gt; &quot;Foo&quot;}</code> would only fail if the changeset or database requires the <code>name</code> field to exist.</p>
<p>It lets us send in anything and sorts it out. Which is convenient when you need to change things but it doesn&rsquo;t impose any real separation between Ecto and your Controller or LiveView. And maybe you don&rsquo;t want those to be separated?</p>
<p>So Saša&rsquo;s article suggests strictening the interface of your Context by using something like <code>def create_product(name, price, description) do</code> instead where you break apart the attributes and you can actually even guard the values explicitly. So do you abandon Ecto Changesets for casting and validation? How will you render forms conveniently? No-no. The suggestion is to add a separate schemaless changeset in the Controller which knows how to validate and maybe even render the form. And then you wall off using the database parts of Ecto using the <a href="https://hexdocs.pm/boundary/readme.html">Boundary</a> library.</p>
<p>So this takes what is a bit of a sticky layering where Ecto-friendly data structures flow in and out and separates it into something where you get well-structured input. You can have more meaningful typespecs this way as well. The article doesn&rsquo;t say specifically because it focuses on slightly different things, I imagine the Context&rsquo;s functions still return some Ecto data structures such as a Changeset on error and an Ecto Schema Struct on success. You could of course strip out the Ecto Schema Struct and reshape that data to be more neutral in this layer as well.</p>
<h2 id="getting-rid-of-contexts-by-listening-to-chris-keathley">Getting rid of Contexts by listening to Chris Keathley</h2>
<p><em>&ldquo;I&rsquo;m pretty sure Contexts are not a thing&rdquo;</em></p>
<p>I don&rsquo;t believe Chris Keathley has put his ideas on Phoenix Contexts in writing. I&rsquo;ve picked them up from his conversations with Amos and occasionally Anna on the <a href="https://elixiroutlaws.com/episodes">Elixir Outlaws podcast</a> along with when we had him on BEAM Radio to <a href="https://www.beamrad.io/18">speak of heresy</a>. It was a good episode, he really put us to work discussing design.</p>
<p><em>Note: Chris has since ducked out of public activity in the Elixir community, if you disagree with my interpretation of his ideas, reach out to me, not him. I&rsquo;ve found his thinking and ideas immensely useful through the years and that&rsquo;s part of why I share my interpretation of them.</em></p>
<p>Brutally paraphrasing I understand his points to boil down to Contexts being a leaky and limiting abstraction. They leak Ecto concerns, both changesets and schema structs, but they don&rsquo;t provide the power of Ecto&rsquo;s full API. They also hide implementation detail behind a layer of abstraction where in many cases you&rsquo;d benefit from having it clearly laid out at the call site.</p>
<p>Chris has mentioned the &ldquo;call site&rdquo; a number of times through the episodes of the Outlaws. The call-site is the place in the code where you decide to initiate a particular operation. Often in Phoenix this is a Controller or a LiveView callback. In wider discussions than the Contexts one he has mentioned that the call site is usually the only place that really knows what to do in the event of a failure. Contexts, as generated, don&rsquo;t really change that, they just return what Ecto would. But if you abstract deeper than those generated contexts you might start running into that. So overall, I take his recommendation to be: give the call-site the capability to explicitly handle errors.</p>
<p>Beyond that he has spoken about using Ecto directly in controllers, building &ldquo;fat controllers&rdquo;. The idea being that the Controller action shows you what the entire operation does. Some Ecto operations, some error handling, sending an email to notify users. You&rsquo;d see the entire flow in the controller. You&rsquo;d know all the steps it takes by reading that page of code. It won&rsquo;t require you to keep in your brain the context of additional Contexts. Does <code>MyAccounts.create_user/1</code> send an invite email? Who knows. Does this?</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e"># ..</span>
<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">MyUser</span><span style="color:#f92672">.</span>create_changeset(params) <span style="color:#66d9ef">do</span>
    {<span style="color:#e6db74">:ok</span>, user} <span style="color:#f92672">-&gt;</span>
        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Repo</span><span style="color:#f92672">.</span>insert(user) <span style="color:#66d9ef">do</span>
            {<span style="color:#e6db74">:ok</span>, user} <span style="color:#f92672">-&gt;</span>
                <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Mailer</span><span style="color:#f92672">.</span>deliver(<span style="color:#a6e22e">MyUser.Emails</span><span style="color:#f92672">.</span>welcome(user)) <span style="color:#66d9ef">do</span>
                    <span style="color:#e6db74">:ok</span> <span style="color:#f92672">-&gt;</span> <span style="color:#75715e"># .. render success</span>
                    {<span style="color:#e6db74">:error</span>, errors} <span style="color:#f92672">-&gt;</span> <span style="color:#75715e"># .. render errors</span>
                <span style="color:#66d9ef">end</span>
            {<span style="color:#e6db74">:error</span>, changeset} <span style="color:#f92672">-&gt;</span>
                <span style="color:#75715e"># .. render errors</span>
        <span style="color:#66d9ef">end</span>
    {<span style="color:#e6db74">:error</span>, changeset} <span style="color:#f92672">-&gt;</span>
        <span style="color:#75715e"># .. render errors</span>
<span style="color:#66d9ef">end</span>

<span style="color:#75715e"># ..</span></code></pre></div>
  </div>

<p>I&rsquo;ve intentionally not made this super clean. It uses case statements and stair-steps heavily as a result. But is it unclear what it is doing? I don&rsquo;t think so. Is it elegant? Not really. It is tempting to pack these things into abstractions and build neat functions. But I know there is benefit to being able to just read the flow directly. Many small functions can lead to a lot of jumping around and at a certain point threatens sanity in the same way that OOP inheritance does.</p>
<p>So this is very explicit and it is leaning on the necessary abstractions such as Ecto and a Mailer that both manage other systems. It doesn&rsquo;t introduce additional abstractions that concern what we are trying to do, creating a user. I think this is important to be mindful of when creating a lot of nice abstraction layers. What is being made easier? What is being made harder?</p>
<h2 id="some-kind-of-conclusion">Some kind of conclusion</h2>
<p>I don&rsquo;t actually care which one <strong>you</strong> use. I sometimes barely care which one I use. But when building a code-base which is intended to grow and be shared it is important to be mindful of what approach you are taking. The fact that multiple people with rather opposing positions see problems with the Contexts design as it is generated tells me it is definitely worth considering whether it really serves your purposes. It could also just be that it is a bit middle-of-the-road and as such not satisfying either end bell curve. When it comes to opinionated approaches I think it is helpful to know which opinion you lean towards. Mixing them is probably the worst approach. I think being consistent beats using the exact right thing in many cases. I&rsquo;m partial to (my interpretation of) Keathley&rsquo;s approach because it involves less code, fewer layers and actually defers the option of adding abstraction layers to a later point.</p>
<p>I think there is good stuff in Saša&rsquo;s approach. It seems like it improves on the separation, makes the layering more meaningful and in that way fulfills more of the purpose of the original design. I especially like the honesty in suggesting you probably want a tool like Boundary to enforce the layering. It is very easy to shortcut through a layer like that and break through that design and having an automated tool tell you when you are breaking your own design is useful.</p>
<p>What other consequences are there? With the explicit in-controller approach you would likely only write one set of tests, for the controller. With the Context approach you&rsquo;d likely be writing tests for both the controller and the Context. And with a thin controller they&rsquo;d be very similar but likely not the same. Oppositely, if you provide multiple interfaces into your system, several types of API, webhooks, Web UI and they all need the exact same functionality, Contexts offer the potential for re-use which isn&rsquo;t part of the explicit in-controller approach.</p>
<p>So what do I recommend? Try one and see how you like it. It won&rsquo;t matter for a throwaway project, as the challenges here are mostly about growing and maintaining a code-base, but you might get a sense of how it is to work in that way. To me this is mostly lines up to be a division of values. Do you prefer more structure and more abstraction or less? This is a rather nuanced piece of software development and involves all the good topics around hiding complexity versus making the code clearly readable in a single function. Essentially eternal topics.</p>
<p>If you don&rsquo;t know what to believe. You&rsquo;re just trying to get your web app to work. I&rsquo;d just as well roll with Contexts as generated. This type of nuance in design can be meaningful for teams and growing systems, I wouldn&rsquo;t put it high on the list for a new learner. As far as making the web go brrrrr, it doesn&rsquo;t matter.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Giveaway: Elixir in Action</title>
      <link>https://underjord.io/giveaway-manning-2021-11.html</link>
      <pubDate>Tue, 23 Nov 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/giveaway-manning-2021-11.html</guid>
      <description>The book Elixir in Action by Saša Jurić is widely recognized as one of the best books on this language I favor. I&amp;rsquo;ve been meaning to run some giveaways and this book was always on the agenda. It shouldn&amp;rsquo;t be the only one but I think it makes a very appropriate starting point. Especially as the book has been such a starting point for many Elixirists through the years.
Transparency note: This giveaway is supported by Manning, the publisher that published Elixir in Action.</description>
      <content:encoded><![CDATA[ <p>The book Elixir in Action by Saša Jurić is widely recognized as one of the best books on this language I favor. I&rsquo;ve been meaning to run some giveaways and this book was always on the agenda. It shouldn&rsquo;t be the only one but I think it makes a very appropriate starting point. Especially as the book has been such a starting point for many Elixirists through the years.</p>
<p><em>Transparency note:</em> This giveaway is supported by Manning, the publisher that published Elixir in Action. I haven&rsquo;t been paid to do the giveaway but I also didn&rsquo;t have to pay for the e-books I&rsquo;m giving away. I think that&rsquo;s cool of them. Ideally this also means that they sell some extra books, in which case they&rsquo;ll likely let me give away more books. I&rsquo;ll attempt to be very clear about how this all holds together and any privacy considerations you should be mindful of.</p>
<p><strong>Privacy note:</strong> Links to Elixir in Action and Manning books in this post have some of those UTM parameters that let them know if I drove any traffic with this &ldquo;campaign&rdquo;. If you find that upsetting I&rsquo;d suggest avoiding this post. I try to be clear about this stuff and I don&rsquo;t sneak things like that in. If you have software installed that strips that stuff out, I don&rsquo;t mind, it&rsquo;s your browser.</p>
<p>Let&rsquo;s give away some books shall we?</p>
<p>I&rsquo;m giving away three e-book copies of <a href="https://www.manning.com/books/elixir-in-action-second-edition?utm_source=newsletter&amp;utm_medium=organic&amp;utm_campaign=book_juric2_elixir_1_17_19&amp;utm_content=underjord">Elixir in Action</a> in this particular giveaway and I&rsquo;ll divy them up a bit to be fair to old readers and new:</p>
<ul>
<li>One to any of my existing newsletter subscribers that decide to participate in the giveaway. You&rsquo;ll get a notice about this in this week&rsquo;s email.</li>
<li>One to anyone who decides to sign up for <a href="/newsletter.html">my newsletter</a> between now and until the end of the giveaway period. You are free to unsubscribe after the giveaway period.</li>
<li>One to anyone who <a href="mailto:lars@underjord.io">sends me an email</a> with a screenshot of them subscribed to the <a href="https://regprog.com">Regular Programming</a> in their podcast client before the end of the giveaway period. Include the word &ldquo;giveaway&rdquo; in the subject line of the email.</li>
</ul>
<p>The giveaway period ends on the 17th of December when I sit down and pick winners at an arbitrary time. Get your submissions in before then.</p>
<p>Now if you are keen on some books beyond winning this I suggest you <a href="https://www.manning.com/?utm_source=newsletter&amp;utm_medium=organic&amp;utm_campaign=underjord&amp;utm_content=discount">take a look at Manning&rsquo;s other books</a> and use the discount code <code>nlunderjord21</code> for 35% off.</p>
<p>If you have some strong recommendations for books from Manning. Let me know and I&rsquo;ll see if I like them and if I think I should arrange a giveaway of those. You can reach me via <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Building a Startup on Elixir to a $50 mil round</title>
      <link>https://underjord.io/50-mil-round-on-elixir.html</link>
      <pubDate>Wed, 17 Nov 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/50-mil-round-on-elixir.html</guid>
      <description>I became familiar with Marcin Kulik when I was brought in to work with a company called Vic.ai. A fintech startup. Marcin is the guy who started building their entire Elixir system and was the person to primarily onboard me to the work. When I was brought in they were a small team of 4 devs, looking to grow up to around 7 at the time.
Special thanks to DockYard for supporting my work on this piece.</description>
      <content:encoded><![CDATA[ <p>I became familiar with Marcin Kulik when I was brought in to work with a company called Vic.ai. A fintech startup. Marcin is the guy who started building their entire Elixir system and was the person to primarily onboard me to the work. When I was brought in they were a small team of 4 devs, looking to grow up to around 7 at the time.</p>
<p><em>Special thanks to <a href="https://dockyard.com">DockYard</a> for supporting my work on this piece. They are a digital product consultancy with deep roots in the Elixir community and ecosystem. You’ve seen their efforts supporting Phoenix and ElixirConfs and with their support I can bring you more stories from companies and people using Elixir. Let’s get to it.</em></p>
<p>Vic.ai has a product that makes a lot of sense to me. My wife is an accountant (when she isn’t editing podcasts, growing vegetables, etc) and I’ve run my own small business at least three times. So I’m familiar with the domain. I know how disjointed invoicing can be. It is a field which lives and breathes PDF files sent over email. The grand interchange format of a massive industry.</p>
<p>PDFs and photos of documents are not a standardized format as far as what data they contain while invoices are themselves fairly structured and formal documents. Vic trains machine learning models to automate the interpretation of these document files into a structured form and offers ways to verify, make corrections and pass the results on to your ledger. I can see how this can save a lot of time. For a startup this is an unusually lucid value proposition.</p>
<p>I think it is a remarkably good fit for ML. The documents are somewhat formalized and fit in a certain mold. An invoice for a particular region typically contains a limited set of features to look for. Your due date, invoice number, totals, line items, vendor information and such. But it is formalized for humans not machines, there isn’t a single template being used, so it is hard to automate as is. And here ML becomes a missing piece translating human-readable to machine-readable and feeding it into whatever your process is. A mix of OCR and ML.</p>
<p>Vic.ai hasn’t been the most visible company in the Elixir space so far. You are perhaps more likely to know of Marcin from his work with <a href="https://asciinema.org">Asciinema</a> which is a very cool tool for recording a terminal and replaying it. You should check it out.</p>
<p>I sat down to talk to Marcin about the use of Elixir in the company and before I had transcribed and edited our conversation the company closed a $50 million round of investment. So that little team is already growing significantly and will likely continue to do so. <em>If you are interested in applying for a job with Vic you can <a href="mailto:lars@underjord.io">reach out to me</a>, we can talk and if it seems like a good fit I can give a warm intro.</em></p>
<p>On to the interview, which lands us smack in the middle of going from small-talk into relevant topics.</p>
<p>—</p>
<p><strong>Marcin</strong>
I know how it is. I used to run my own business as well.</p>
<p><em>Lars</em>
Oh, really?</p>
<p><strong>Marcin</strong>
Yeah. Before this. So this will transition us into the history of Vic because I knew Alex before, for a couple of years. I met him in 2015 I think. I left a Ruby on Rails shop in 2013 and I started my own company doing consulting gigs.</p>
<p>_(editor’s note: Alex refers to Alexander Hagerup, CEO at Vic.ai) _</p>
<p>From a colleague at that previous company I got in touch with some people in Norway and then did some projects for them. Then I met the guys behind the Gitorious project. A project similar to Github but self-hosted and open source. That project was later acquired by Gitlab.</p>
<p>It was through that chain of people I got to know Alex.</p>
<p>Alex had this idea for a mobile app he was building and he needed an API. So that’s how we got connected. We met and I did two projects for him. During this time I still had my own company in Krakow. So like you I was managing my own time, paying my own taxes and all of that. Until 2017 I think, when I had already done a couple of months of work on Vic.</p>
<p><em>Lars</em>
Was this app Alex was building the foundation for Vic?</p>
<p><strong>Marcin</strong>
No, so in 2015 he had an idea for an app that was supposed to be for sharing images with your closer circles. Something like Instagram but with friends and family rather than public.</p>
<p>I was building the backend. We worked on this for two years but nothing came of it.</p>
<p>Then around the time I was getting into Elixir and Phoenix, Alex asked if I wanted to build a site for a US startup he was building. It was for booking places at restaurants and venues to use as workspaces during off hours. Essentially providing co-working space when the business is mostly empty.</p>
<p>So this was how Elixir was introduced to Alex. He didn’t care much about the specific technology. That has been a nice thing working with him, he trusted my tech choices. He was never going “Let’s use PHP or Python”. I built the photo app backend in Clojure.</p>
<p><em>(Editor&rsquo;s note: Alex would let the record show that he did care about the choice but trusted the suggestion :)</em></p>
<p><em>Lars</em>
So backing up a bit, and looking at your background, how did you get started with programming?</p>
<p><strong>Marcin</strong>
I was in seventh grade of primary school and there was a computer shop in my hometown. I already had an Amiga 600 that I was mostly playing games on. I went to that shop one day and they had a book, with a disc, on their shelf which said AMOS and it was basically the handbook for the AMOS programming language. So that was the Amiga version of BASIC.</p>
<p>So I went home and asked my parents if I could have it. They saw that it was programming, figured it was too difficult for me and didn&rsquo;t want to waste money on it. They assumed it would end up unused on a shelf. So I went to bed crying because my career was in ruins already.</p>
<p>I don&rsquo;t remember how I finally got it but I think it was a year later, so in eighth grade. So I started programming on the Amiga. After eighth grade they bought me a PC because I was going to high school with a focus on mathematics. So they got me this nice PC, an Intel Pentium at 100 Mhz or so.</p>
<p>So after initially just playing PC games. This was the summer of 95 or 96 and Quake was out, Duke Nukem was already there so I was playing all of these shooters. But after a year I got bored and wanted to do some stuff myself.</p>
<p>I was looking into Turbo Pascal. The IDE was super nice, F5 to compile, F6 to run or something like that. Everything was integrated nicely and it was simple. So I learned Pascal. I was also observing the demo scene, all these crazy people who do magic things with programming. I was hooked. I collected the demos and started reading the zines of the scene. There was a lot of good insight there, in the tech corner they would cover things like doing plasma effects in assembly or casting 3D points into screen pixels and such.</p>
<p>So I was reading that and then I started writing my own silly demos in Turbo Pascal. There were some pieces where that was too slow because I was trying to calculate a lot of pixels, 320 by 240 so over 60.000 of them and Pascal was not ideal for some of this stuff. So I needed to go lower.</p>
<p>I went into C and then hit similar problems and had to do some things in Assembly. And C has this nice ability to just write some assembly in-line with some macro. So I did that and then I was just jumping between languages as I needed them. C, C++ and my first professional job was PHP.</p>
<p><em>Lars</em>
So do you have formal training in computer science?</p>
<p><strong>Marcin</strong>
Yeah, I do. I have a master&rsquo;s degree in computer science. So that happened in parallel to my explorations. I wouldn&rsquo;t say I didn&rsquo;t learn anything at the university but it was mostly complimentary.</p>
<p><em>Lars</em>
It sounds like you were learning quite actively outside of school.</p>
<p><strong>Marcin</strong>
Yeah, exactly. I didn&rsquo;t have much trouble going through university, to put it lightly.</p>
<p>So then around 2003 I was doing PHP and making some websites. Then I learned Python and some other things. In 2006 I got into Ruby a lot and worked at a Ruby shop in Krakow where I spent 6 years. At the end of that period I was kind of burnt out by the complexity that&rsquo;s hidden under the surface. The magic of that brings you misery while you are not paying attention.</p>
<p>So I was looking into various languages and some people at the company were looking into Clojure for a side-project. They brought it to my attention and that was my gateway drug into functional programming.</p>
<p>Although, if you look at my late Ruby code it was very non-idiomatic and was looking more like functional code. Very explicit, with explicit dependencies and immutable. So then I went into Clojure and it was amazing. It opened so many ideas for me and a completely different look at how software can be built. If you spend your whole career doing object-oriented programming, you never touch functional, then you may not know what simplicity means. It was like enlightenment for me. An &ldquo;aha&rdquo; moment.</p>
<p>I didn&rsquo;t want to go back.</p>
<p>So I used Clojure for some projects, like the one I described with the API for Alex. I was aware of Elixir early on, back in 2014 or 2015. José Valim lives near Krakow and had showed up to some Meetups in Krakow and I was at one of those. He was showing off opening two terminals with different IEx sessions and connecting them as nodes.</p>
<p>At that time Phoenix looked like it was trying to please Rails developers and my personal feeling was &ldquo;Nah, this is just another Rails and I don&rsquo;t wanna go there&rdquo;. So I had mixed feelings at that point.</p>
<p>Everything else looked great but I wasn&rsquo;t sure about the web part and how it would evolve. Today we know it evolved into this amazing space of interactive, real-time solutions like LiveView and LiveBook among others. But as it was I decided to observe from a distance. Around 2016 or so I started playing with it.</p>
<p>Yeah. So I read the Programming Elixir book by Dave Thomas and then the Programming Phoenix by Bruce Tate (Editor’s note: not to forget Chris McCord &amp; José Valim). After that I was like, oh man, this is it. The language, the whole platform, it solves so many problems I&rsquo;ve observed and dealt with in the past. How is it possible that nobody knows that this gold is here?</p>
<p><em>Lars</em>
So did Vic start with Elixir?</p>
<p><strong>Marcin</strong>
When I joined Vic.ai there was already a guy working on some AI parts. So we ended up having two areas. The AI parts with all the OCR, document processing and machine learning. That part uses Python.</p>
<p>So Alex started the company with the guy doing the Python work but they needed an API and many other missing pieces. So when I joined we discussed what we should use for the API.</p>
<p>I suggested that Elixir and Phoenix would be a great fit for it. It wasn&rsquo;t hard to convince them, they trusted me with this choice. We didn&rsquo;t see a need to rebuild the Python code in Elixir because we didn&rsquo;t need to closely couple the AI part with the API.</p>
<p>So I built the API from scratch with Phoenix. There was nothing before that.</p>
<p><em>Lars</em>
How long did you spend working on the Elixir side more or less on your own?</p>
<p><strong>Marcin</strong>
Over two years. I believe I started in 2017 around June and at that time it was still consulting, not as an employee. And late 2019 we hired three Elixir developers, my first Elixir colleagues.</p>
<p><em>Lars</em>
Did they already have Elixir experience when they came on?</p>
<p><strong>Marcin</strong>
Yeah, yeah. They did. We explicitly looked for people who had experience. I posted on ElixirForum and we had many responses and lots of great candidates. So we spent quite a while doing interviews. There were so many candidates and we could only hire three. With how good the pool was we could have easily hired three more if the company would have been able to at the time.</p>
<p><em>Lars</em>
How was it like to bring people into that Elixir code base? A two-year old code base has a lot of stuff in it already, especially one under startup pressure.</p>
<p><strong>Marcin</strong>
Right, especially one created by a single person. Well, so the onboarding, I remember, was hard work for me. It was mostly challenging due to the fact that I now had to share this codebase and what is in my mind with the external world. Previously I&rsquo;d only needed to share relevant details with the Python developer and they weren&rsquo;t necessarily interested in the Elixir codebase itself.</p>
<p>So I had to go from not documenting anything and knowing how everything connects in my head to be more explicit in every aspect of it. So for me it required a lot of time. But as for those colleagues they were already experienced when we hired them, they didn&rsquo;t require any training. They just needed many introductions to various pieces of the system and corners of the codebase.</p>
<p>So one thing we did was that they all started during the same week. So we brought them together in our office and we had an onboarding week where we spent a whole week on that. Explaining how things are connected and how all of it works. And then we fixed one or two bugs together. It was great. I think it did wonders to get them up to speed. Just to be able to be in the same room for a week. (Editor&rsquo;s note: This was before COVID-19, this is being published during the pandemic and in some places bringing people into the same room is still weird so note that this was normal at the time.)</p>
<p><em>Lars</em>
So this was about two years ago. And you&rsquo;ve kept bringing in people, especially recently. How would you compare bringing people into an Elixir codebase and other language codebases you&rsquo;ve worked with?</p>
<p><strong>Marcin</strong>
That&rsquo;s a good question. This is not a scientific answer but I feel like with Elixir, when they ask questions about why things were done this way where do I find something, I can just point them to the right part of the code. A specific module or area. Then they could just read and understand it due to the simplicity of the language and explicitness. Most issues with understanding have been where we rely too much on macros.</p>
<p>There was less back-and-forth because people can figure things out on their own because the code is more understandable. There&rsquo;s not a lot of hidden things and no chains of parent classes bringing in new methods and different behaviors.</p>
<p>They can figure out the context on their own easier. It could just be that the guys we hired just did really well. They are fantastic developers. So that helped. But I think the aspect of having less magic and being easier to understand made a difference.</p>
<p><em>Lars</em>
Have you experienced any difference in recruiting and hiring for Elixir? Has it been harder or easier than your other experiences?</p>
<p><strong>Marcin</strong>
It was much easier. We had so many good candidates. Actually, when we did hire we were also recruiting for the Python team and me and the guy who was in charge of the Python system were doing interviews together. There was no-one else who could pair up and discuss the candidates.</p>
<p>We had so many Python candidates. But the average experience level was lower and we had to spend much more time determining who could be a good candidate.</p>
<p><em>Lars</em>
What have been the best parts of working with Elixir, for the product and the codebase?</p>
<p><strong>Marcin</strong>
The one I see the most is that Elixir enables much, much easier refactoring of the code. Because everything is a module, which is a bag of functions, nothing is hidden. So when refactoring you just move things, cut and paste functions around. And it always just works. In most cases the compiler can tell you if you broke something. I feel like refactoring in Elixir could not be easier.</p>
<p>When changing configuration, or more in-depth OTP stuff, dynamic runtime concerns and supervision trees then you have to pay more attention. But just moving functions around is easier.</p>
<p>So, a fun example of this, as our codebase was growing I missed a feature from Clojure which was preventing cyclic dependencies. Initially I was bummed about it but later realized that it is probably to be interoperable with Erlang and hot code updates. That would make it hard to enforce.</p>
<p>So to get some of those guarantees I converted the application into an umbrella application which means it is broken up into multiple applications and they can&rsquo;t have circular dependencies between themselves or the application won&rsquo;t boot. When the first colleagues came that changed, the umbrella structure was getting in the way of the daily programming and consensus was to change back into a monolithic app. So one of them made a PR for changing it back and  it was pretty much just moving things. Moving files, updating the project mix project file. A lot of moved files and folders.</p>
<p>It wasn&rsquo;t a big struggle, war and suffering. He just opened the PR, moved all the stuff, we merged it and it wasn&rsquo;t an umbrella app anymore.</p>
<p><em>Lars</em>
Typically moving from a decoupled system into a monolith or vice versa is a horrible process. Often months or potentially even years, right?</p>
<p><strong>Marcin</strong>
Yes, exactly. So I think this is a testament to how refactoring is simpler in Elixir.</p>
<p><em>Lars</em>
So right now you are doing more ops than regular development, how is Elixir on that end, operating it?</p>
<p><strong>Marcin</strong>
Yeah, when we hired my three colleagues I started my transition into a more DevOps role. We needed this position at the company. For me it was an opportunity to do something different in a familiar environment.</p>
<p>Our Elixir deployment is fairly similar to other languages. We use Docker containers. We build a release, wrap it in a container and deploy it.</p>
<p>We do use Erlang Distribution for clustering and we use libcluster to form it. This allows us to use Phoenix Channels and pg2 process groups for messaging.</p>
<p>We actually also have a number of separate Elixir projects that we use for integrations with external systems, we call them connectors. They are developed separately but join the same cluster as our main API service. So our cluster is heterogenous. And we call these services via RPC calls. So we didn&rsquo;t need to implement Phoenix endpoints and JSON serialization. We just send native terms over the wire. It also allows us to use pg2 groups for service discovery without any extra infrastructure.</p>
<p><em>Lars</em>
If you were building another SaaS backend, would you choose Elixir again?</p>
<p><strong>Marcin</strong>
Yeah. Totally. I would not hesitate to choose it again.</p>
<p>The only issue we&rsquo;ve had that is worth mentioning that could sometimes be a problem is that you won&rsquo;t find as many ready-made libraries as you would in Python or Java. More than 80% of the libraries we needed were there and more often than not they&rsquo;ve been of high quality.</p>
<p>We&rsquo;ve also worked around that with a Port at one point, where we were already using a Python library on the machine learning side, so we wrapped that up in a Port and it has been working quite well to this day. We are looking at reimplementing it in Elixir now but it has been great.</p>
<p>Once you learn how to use Ports it is tremendously useful because you can run anything, a Python library or a super efficient Rust implementation or anything that you can communicate with over standard input.</p>
<p>If you treat the BEAM, Erlang and Elixir, as a robust orchestrator then you can pretty much integrate with anything. The concurrency of the BEAM allows you to hide anything behind a function call, GenServer calls, HTTP calls. And the caller waiting there doesn&rsquo;t block any other workloads in the system.</p>
<hr>
<p>I want to thank Marcin and Vic.ai for letting me do this interview. And I don’t know which take-away I like more, <strong>”trust your developers on their choice of tools”</strong> or <em>”Elixir is a solid choice for your startup”</em>. Both sound good to me.</p>
<p>The thing is, I’ve worked on this code. It has both good parts and rough edges, as all code-bases I’ve ever seen do. I think the fact that I felt comfortable making significant updates to it within a week of starting speaks to what Marcin said, changing code, reworking and refactoring is made easier by Elixir. I spent my first few months deep in the backend and out in some integration services. I never had to care about the GraphQL API while doing that because they just weren’t coupled. And when I got into the GraphQL stuff I didn’t have to spend effort thinking about the deeper parts of invoice processing because I was working at the surface level. Part of this is because someone, probably Marcin, did a good job of layering the design of the system but some of it is just the conceptual simplicity of the language.</p>
<p>I’ll add that one of the things I worked on is something I don’t know how I’d do as well in most other languages and runtimes. The Vic.ai system has integrations to invoicing systems and those systems have published rate limits and constraints that we need to conform to. We want to conform in an efficient way and even if we hit an undocumented limitation we need to handle that. So we built an approach to distributed rate limiting according to particular rules, pooling to constrain concurrency of requests, back-off and circuit breakers for handling problems predictably. Without storing anything in Redis, without relying on a single source of truth to store ephemeral information about our communication. It was fascinating to put together and seems to have worked really well.</p>
<p>This is all at the technical detail level and the talent onboarding level. Perhaps as interesting to a decision-maker should be that Elixir paid off in effectively orchestrating a normal Python-based Machine Learning platform to provide a service to customers that shows enough potential and success that it received $50 million in funding. To me that is a data point supporting that the technical merit of Elixir isn’t just appealing to in-depth nerds like me but actually useful and beneficial in commercial applications. When your customer is an accounting firm you better provide a clear ROI, it&rsquo;s almost like they look at the numbers.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Finally using your Raspberry Pi &amp; Elixir</title>
      <link>https://underjord.io/finally-using-your-raspberry-pi-and-elixir.html</link>
      <pubDate>Sat, 30 Oct 2021 12:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/finally-using-your-raspberry-pi-and-elixir.html</guid>
      <description>How many Raspberry Pi do you have sitting in a drawer. I think I still have about 4? And at least 2 on a shelf. I may be over the average there but the story itself is common. &amp;ldquo;Oh! This is neat, I can automate X with this! And learn Y tech doing it!&amp;rdquo; And yet it never gets quite done. For reasons.
No one needs to feel too bad about this.</description>
      <content:encoded><![CDATA[ <p>How many Raspberry Pi do you have sitting in a drawer. I think I still have about 4? And at least 2 on a shelf. I may be over the average there but the story itself is common. &ldquo;Oh! This is neat, I can automate X with this! And learn Y tech doing it!&rdquo; And yet it never gets quite done. For reasons.</p>
<p>No one needs to feel too bad about this. Not all projects pan out. But I&rsquo;ve recently found my footing on putting these devices to use and figured I&rsquo;d share some thoughts. The idea of Pis gathering dust in a drawer along with all your ambitions is a bit of a meme. As the introduction might have intimated I have a worse Raspberry Pi habit than many others. Going from memory this is my idle inventory right now:</p>
<ul>
<li><strong>Pi 1</strong>, some revision</li>
<li><strong>Pi 3</strong>, at least 2 of them</li>
<li><strong>Pi 3 A+</strong></li>
<li><strong>Pi Zero WH</strong> with OLED Bonnet</li>
<li><strong>Pi 400</strong>, swedish layout</li>
<li><strong>Pi Zero 2 W</strong></li>
</ul>
<p>Currently in use:</p>
<ul>
<li><strong>Pi 4</strong>, handed off to the inlaws to provide a computer for their TV, straight Raspbian</li>
<li><strong>Pi Zero WH</strong> with Inky eInk Phat running the <a href="https://github.com/lawik/calendar_gadget">calendar_gadget</a></li>
<li><strong>Pi Zero WH</strong> with Keybow 12-key, running the <a href="https://github.com/lawik/lightswitch">lightswitch</a></li>
</ul>
<p>Let&rsquo;s not talk about the Arduinos and compatibles. I swear I&rsquo;ll get to them any day now.</p>
<p>To give myself some additional points I&rsquo;ll note that both the Pi0 OLED Bonnet and Pi 400 have been frequently featured in recent livestream experiments. They are seeing some use in that way but they are not in service as production devices yet. The Pi Zero 2 W just arrived.</p>
<p>The two Pi Zero I&rsquo;ve recently put into service for showing my calendar and controlling my workstation camera lights respectively are both set up with the Elixir/Erlang-based <a href="https://www.nerves-project.org/">Nerves project</a> (<a href="https://github.com/nerves-project/nerves">less markety Github</a>) instead of your typical Raspbian. Why? Because I prefer it. Why do I prefer it? A few things come to mind. For one thing it makes doing hardware on Linux in Elixir a darned delight. It turns the whole project into a single repo for generating firmware that doesn&rsquo;t depend on setting up an OS, installing language runtimes or compiling anything on-device. It makes shipping code updates to the device comfortable and flexible.</p>
<p>Nerves has great ergonomics for development and it also has the useful property of constraining your possible approaches a bit. It needs an Erlang language, so you don&rsquo;t have to decide between all languages you could run on a Pi. It has a way to deploy firmware so you do not have to think about Ansible, Docker or any other deployment approach. It comes with an OS set up to let you make the device an IoT style device, you don&rsquo;t have to rummage through Raspbian making all of the choices. The world is no longer your oyster so you might get some actual stuff done.</p>
<p>So what is the OS? The Nerves project is built on Buildroot which is a bunch of tooling for building embedded Linux systems. So for kernel, drivers, OS-level packages we get all of that via Buildroot. Sort of like a compile-time apt you get a repository of packages to choose from to include in your OS. Typically I don&rsquo;t have to touch Buildroot but I&rsquo;ve customized it when needed. Once a Nerves system has booted Linux it will start the Erlang virtual machine, the BEAM, to start your project. And everything you want to do, you typically implement in, or orchestrate from Erlang or Elixir. So Nerves runs a much leaner install than Raspbian would and you build your project and install on your dev machine, burn the entire thing to SD card or ship it over SSH, its a very nice workflow. You can mock out hardware and iterate quickly on your dev host, or you can paste a bunch of code into your machine over SSH just to try it. Lots of workflow options.</p>
<p>Nerves does a lot of neat stuff for you, such as keeping most of your SD card read-only, protecting you from the dangers of corruption. And if your SD-card craps out, setting up a new environment is just burning the firmware to a new card. No need to set up deployment infrastructure to be able to repeat the build. I think that&rsquo;s one place where Pi projects go to die. Reproducing the builds or recovering from a screw-up. The <a href="https://hexdocs.pm/nerves/getting-started.html">Getting Started guide</a> is pretty good.</p>
<p>The Pi Zero W or Pi 3 A+ are ideal devices for all of this since they have everything you need to work conveniently. WiFi, USB Gadget mode, easy to power off of your computer USB. But any WiFi Pi is easy enough and any Pi at all should work.</p>
<p>So once you&rsquo;ve burned a Nerves system firmware to an SD card you can just plug the device into your computer, it should start up and present itself as <code>nerves.local</code> over mDNS and you can SSH to it. All across the USB umbilical, power and data, the glory of gadget mode. If you gave it WiFi credentials it&rsquo;ll also announce itself there. If you want the fatest way to try it out you can download and flash <a href="https://github.com/livebook-dev/nerves_livebook">Nerves Livebook</a> which will give you a Nerves device which provides the Livebook web UI for trying out code, has lots of examples of fun stuff you can do with the device and shows how to get started with a ton of examples. Livebook is a pretty wild Elixir take on Jupyter Notebooks and Nerves really makes good use of it as an introduction.</p>
<p>Now, for additional hardware.. Most hobbyist RPi hardware comes with Python libraries which is convenient on Raspbian but less useful for us here. Hardware support will vary wildly. But if the device does SPI/I2C/GPIO the Circuits library provides the foundation you need. I&rsquo;ve ported some stuff, just implemented a subset for some others. There is <a href="https://elixir-circuits.github.io/">a list on the Circuits site</a>, I don&rsquo;t expect that&rsquo;s exhaustive.</p>
<p>Anyway. I&rsquo;d suggest starting with something that is known to work. Either something with inputs, buttons or sensors or some output, lights, a motor, a relay.</p>
<p>Make it mostly use skills you already have and let the hardware be the new thing if that is unfamiliar. If you already know web dev for example you could have a device with buttons that talks to an API on the web somewhere to make things happen.</p>
<p>So for example, I had the Pi with an eInk display already. It doesn&rsquo;t have any fun hardware input connected so I made a Web UI for setting up a calendar URL to import and made pulling that calendar the input. The Web UI was easy enough to make and Phoenix LiveView made it a pleasure. The novel part was rendering a nice visual of the next calendar event to the device and I did do a bit of yak shaving in the form of a live web preview.</p>
<p><a class="image" href="assets/images/blog/inky-calendar.jpg" target="_blank"><img alt="Pi Zero with Inky Phat on it showing some calendar information with sensitive information redacted" class="blog-image" src="assets/images/blog/inky-calendar.jpg"/></a></p>
<p>Look for controllable things in what you use. Your computer itself, remote servers, simple devices on your network. My other project I got working controls my Elgato Keylights which are normal Web API endpoints. The tricky new part there was mostly around discovery via mDNS and there are good Nerves libraries for that.</p>
<p><a class="image" href="assets/images/blog/keybow-lightswitch.jpg" target="_blank"><img alt="Keybow keypad on a desk, looks absolutely inert but can actually control lights" class="blog-image" src="assets/images/blog/keybow-lightswitch.jpg"/></a></p>
<p>You can follow some of the process of me getting familiar with different parts of using these devices from my livestreams where I&rsquo;ve done both exploration and implementation work on these projects. There is a playlist <a href="https://www.youtube.com/watch?v=lsM40ioNG0o&amp;list=PLk_-dV_ai0LHqGRsfCv8Qo36G47aKZcnn">here on YouTube</a> or you can find them on this blog if you&rsquo;d rather feel indie.</p>
<p>So what has been the key in making these Pis stop gathering dust for me? I&rsquo;ve chosen tools I like and care about. I know that if I want to set up these projects in 2 years it should all just work. The work doesn&rsquo;t feel ephemeral, it feels finished now and trivial to continue if I want to. These kinds of projects can easily turn into unwieldy yak shaves where I make no progress. I feel like Nerves constrains me and saves me from that sprawling complexity by providing solutions I like and don&rsquo;t have to pour energy into. I also set aside time to do it, mostly around the livestream. That&rsquo;s an important non-technical aspect, know when you are planning on actually doing it, if you care about it getting done.</p>
<p>If you are curious about Nerves I suggest signing up for <a href="https://underjord.io/nerves-newsletter.html">the Nerves Newsletter</a> which is bi-weekly and updates you on what is happening in the project as well as neat projects people have put together with it. It is hand-typed by a human (me) that is active in and excited about the project. The <a href="https://nerves-project.org/newsletter/">entire archive is available here</a>.</p>
<p>I&rsquo;ve also experimented a fair bit. Some of the most fun stuff I&rsquo;ve done with these things is to set up Erlang clustering where all of my Pi devices can detect each other on the network as they start and connect up to form and Erlang cluster. This would allow easy communication between the devices if I want to integrate them with each other. I&rsquo;m not using it for anything currently but it is an interesting piece. I&rsquo;m considering setting up the OLED Bonnet to be a blinking reminder of meetings that are about to start as it has buttons that would let me also dismiss the meeting. If I set up a node that runs on my desktop computer as well I could even have the button presses trigger opening the calendar event information on the desktop over the cluster. There are a lot of fun possibilities when your devices can connect to each other and communicate via message passing.</p>
<p><a class="image" href="assets/images/blog/unused-bonnet.jpg" target="_blank"><img alt="Unused Pi Zero with OLED Bonnet, looks like a small game console" class="blog-image" src="assets/images/blog/unused-bonnet.jpg"/></a></p>
<p>The next thing I would want to get started on is a radio thing. I have a number of Arduino-style boards with RFM69 transceivers on them. I also have a transceiver with an RPi-friendly breakout. I&rsquo;ve had them talking in the past but I want to revisit it with a new approach. I want the Arduinos on battery, reading sensors or operating relays and I want an Rpi on my network being the hub for collecting sensor data and controlling the devices. That&rsquo;s a bit more involved to do right but it should be a fun project.</p>
<p>If you have projects with Pis that you think are cool or plans that you would like to share, don&rsquo;t hesitate to reach out over email (<a href="mailto:lars@underjord.io">lars@underjord.io</a>) or on Twitter (<a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>).</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Attempting to Control Elgato Keylights from Nerves</title>
      <link>https://underjord.io/livestream-keylights-and-keypads-feat-failure.html</link>
      <pubDate>Sun, 24 Oct 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-keylights-and-keypads-feat-failure.html</guid>
      <description>This livestream we tried to control my Elgato Keylights from a Nerves device. We really tried gang, come on. A good attempt.
We ran into an issue with the expectations we had on what mDNS would give us. Those issues are still under investigation but they seem to be a change or regression in underlying parts of Nerves or Erlang. I&amp;rsquo;ve since gotten it to work by going back a few versions.</description>
      <content:encoded><![CDATA[ <p>This livestream we tried to control my Elgato Keylights from a Nerves device. We really tried gang, come on. A good attempt.</p>
<p>We ran into an issue with the expectations we had on what mDNS would give us. Those issues are still under investigation but they seem to be a change or regression in underlying parts of Nerves or Erlang. I&rsquo;ve since gotten it to work by going back a few versions.</p>
<p>Those frustrations aside we also verified that we can control the lights and detect the keypresses for the keypad we are using. The Keybow 12-key thing with RGB lighting.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-22-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-22-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-22-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-22-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-22.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-22-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Automatically Clustering Nerves devices</title>
      <link>https://underjord.io/livestream-nerves-nodes-and-mdns.html</link>
      <pubDate>Fri, 22 Oct 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-nerves-nodes-and-mdns.html</guid>
      <description>Sitting down to show some of the work I&amp;rsquo;ve done with Nerves and Erlang Distribution for clustering Raspberry Pis. In this case automatically, via mDNS. This is what I do when I have a cold and am not streaming I guess.
We also poke around with an HDMI screen on the Pi400. I want to run Scenic on that one.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.</description>
      <content:encoded><![CDATA[ <p>Sitting down to show some of the work I&rsquo;ve done with Nerves and Erlang Distribution for clustering Raspberry Pis. In this case automatically, via mDNS. This is what I do when I have a cold and am not streaming I guess.</p>
<p>We also poke around with an HDMI screen on the Pi400. I want to run Scenic on that one.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-15-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-15-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-15-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-15-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-15.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-15-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Making a Calendar Gadget with Nerves and LiveView</title>
      <link>https://underjord.io/livestream-liveview-nerves-eink-calendar-gadget.html</link>
      <pubDate>Fri, 15 Oct 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-liveview-nerves-eink-calendar-gadget.html</guid>
      <description>Cleaning up previous work and making the eInk display look fabulous by iterating in our LiveView-based simulator. We get this thing ready for small office production :)
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>Cleaning up previous work and making the eInk display look fabulous by iterating in our LiveView-based simulator. We get this thing ready for small office production :)</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-01-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-01-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-01-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-01-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-01.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-10-01-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Forming an Erlang cluster of Pi Zeros</title>
      <link>https://underjord.io/livestream-pi0-erlang-cluster-with-nerves.html</link>
      <pubDate>Thu, 30 Sep 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-pi0-erlang-cluster-with-nerves.html</guid>
      <description>I sat down with the stream and gently coaxed a couple of Raspberry Pi Zeros to cluster up with each other using the Nerves project.
With recent support for Erlang distribution in MdnsLite we got the power to help nodes find each other via mDNS instead of some other method. So I wanted to give it a whirl, and I did :)
Special thanks to Isaac for his help in the chat.</description>
      <content:encoded><![CDATA[ <p>I sat down with the stream and gently coaxed a couple of Raspberry Pi Zeros to cluster up with each other using the Nerves project.</p>
<p>With recent support for Erlang distribution in MdnsLite we got the power to help nodes find each other via mDNS instead of some other method. So I wanted to give it a whirl, and I did :)</p>
<p>Special thanks to Isaac for his help in the chat.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-24-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-24-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-24-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-24-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-24.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-24-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Unbox a Pi400 and put Nerves on it</title>
      <link>https://underjord.io/livestream-unbox-pi400-nerves.html</link>
      <pubDate>Mon, 20 Sep 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-unbox-pi400-nerves.html</guid>
      <description>I&amp;rsquo;ve been enjoying some Nerves recently which prompted me to finally order the Pi400 to play around with. In this stream I unpack it and get Nerves running on it without having tried it before or hearing that anyone had tried it. I just trusted the hard work of the Nerves team.
Turns out that was a great call. It worked beautifully, almost confusingly well. Then we investigated some of the things we can do with a Pi with input devices but no output connected.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;ve been enjoying some Nerves recently which prompted me to finally order the Pi400 to play around with. In this stream I unpack it and get Nerves running on it without having tried it before or hearing that anyone had tried it. I just trusted the hard work of the Nerves team.</p>
<p>Turns out that was a great call. It worked beautifully, almost confusingly well. Then we investigated some of the things we can do with a Pi with input devices but no output connected. It was a fun stream. Lovely little device and that prompted Frank to go and get us ethernet gadget mode. So that might be something for next time.</p>
<p>Enjoy the video and let me know if there&rsquo;s more hardware you&rsquo;d like me to play around with.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-17-pi400-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-17-pi400-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-17-pi400-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-17-pi400-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-17-pi400.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-17-pi400-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Nerves, Livebook &amp; small displays (eInk, OLED)</title>
      <link>https://underjord.io/livestream-nerves-displays-and-livebook.html</link>
      <pubDate>Thu, 16 Sep 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-nerves-displays-and-livebook.html</guid>
      <description>The Inky eInk display was one of my first real things done with Elixir. And I&amp;rsquo;m apparently back on my bullshit. Because I just pulled that thing out and tried it with Nerves + Livebook. Had a really good time this stream and I think I want to do more things with hardware like this. Let me know if you&amp;rsquo;d be curious to see more of this.
This triggered a not-yet-working Nerves device project I&amp;rsquo;m calling my calendar gadget which will be more documented at some point I&amp;rsquo;m sure.</description>
      <content:encoded><![CDATA[ <p>The Inky eInk display was one of my first real things done with Elixir. And I&rsquo;m apparently back on my bullshit. Because I just pulled that thing out and tried it with Nerves + Livebook. Had a really good time this stream and I think I want to do more things with hardware like this. Let me know if you&rsquo;d be curious to see more of this.</p>
<p>This triggered a not-yet-working Nerves device project I&rsquo;m calling my <a href="https://github.com/lawik/calendar_gadget">calendar gadget</a> which will be more documented at some point I&rsquo;m sure. The Phoenix part of that under <code>calendar_app</code> is completely locally runnable. You have to add some fonts and use iex to add your calendar URL though. Not very ergonomic yet. The idea is to show my next calendar event on the eInk display. And since I didn&rsquo;t want to push firmware for every change to the display I made a silly/naive eInk preview in LiveView. If you are curious about what my just-get-it-working code is like, that project is full of it.</p>
<p>Anyway, I&rsquo;m really glad to be doing some more hardware stuff and be back with Nerves. The livestream video covers putting text on these displays and some capturing of button presses :)</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-2021-09-10-nerves-displays-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-2021-09-10-nerves-displays-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-2021-09-10-nerves-displays-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-2021-09-10-nerves-displays-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-2021-09-10-nerves-displays.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-2021-09-10-nerves-displays-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Phoenix 1.6 RC, Preview</title>
      <link>https://underjord.io/livestream-elixir-phoenix-1-6-preview-liveview.html</link>
      <pubDate>Fri, 03 Sep 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-elixir-phoenix-1-6-preview-liveview.html</guid>
      <description>There are some very exciting updates in Phoenix 1.6 that just recently hit RC. We go through and try them out. It all seems to work great. I consider every bit of it an improvement.
The official blog announcement for RC 0 has the details and I reference it through the video.
I particularly tried out the LiveView Heex template changes which was great. This seems much more ergonomic and it wasn&amp;rsquo;t bad to begin with at eex/leex.</description>
      <content:encoded><![CDATA[ <p>There are some very exciting updates in Phoenix 1.6 that just recently hit RC. We go through and try them out. It all seems to work great. I consider every bit of it an improvement.</p>
<p>The <a href="https://www.phoenixframework.org/blog/phoenix-1.6-released">official blog announcement</a> for RC 0 has the details and I reference it through the video.</p>
<p>I particularly tried out the LiveView Heex template changes which was great. This seems much more ergonomic and it wasn&rsquo;t bad to begin with at eex/leex.</p>
<p>Then we actually had time to try the new Esbuild setup as the replacement for Webpack. We even added in TailwindCSS. Surprisingly straight-forward. Look forward to use it.</p>
<p>Thanks to the people who dropped in, especially Marcel Fahle for providing some back-and-forth on the frontend-y features.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-03-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-03-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-03-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-03-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-03.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-09-03-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Elixir &amp; Ecto, generating queries</title>
      <link>https://underjord.io/livestream-elixir-ecto-query-generation.html</link>
      <pubDate>Fri, 27 Aug 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-elixir-ecto-query-generation.html</guid>
      <description>As part of some foundational work on a CMS concept I&amp;rsquo;ve been building out this fairly dynamic Ecto-based way of dealing with content types and content (we call it entity types and entities) in a database.
And I wanted this to be broadly compatible. That&amp;rsquo;s what most of this livestream ended up being about from what I remember. Trying things and swearing about internals. All in all a good time right?</description>
      <content:encoded><![CDATA[ <p>As part of some foundational work on a CMS concept I&rsquo;ve been building out this fairly dynamic Ecto-based way of dealing with content types and content (we call it entity types and entities) in a database.</p>
<p>And I wanted this to be broadly compatible. That&rsquo;s what most of this livestream ended up being about from what I remember. Trying things and swearing about internals. All in all a good time right?</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-27-ecto-query-generation-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-27-ecto-query-generation-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-27-ecto-query-generation-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-27-ecto-query-generation-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-27-ecto-query-generation.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-27-ecto-query-generation-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video Companion - Teaching Elixir - Strings</title>
      <link>https://underjord.io/video-companion-teaching-elixir-strings.html</link>
      <pubDate>Mon, 23 Aug 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-strings.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time, strings.
You can find the livebooks of the modified lessons in my fork of ElixirSchool.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time, <a href="https://elixirschool.com/en/lessons/basics/strings/">strings</a>.</p>
<p>You can find the livebooks of the modified lessons in <a href="https://github.com/lawik/elixirschool/tree/master/en/lessons/basics">my fork of ElixirSchool</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-11-strings-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-11-strings-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-11-strings-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-11-strings-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-11-strings.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-11-strings-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Elixir CMS Thoughts, and code</title>
      <link>https://underjord.io/livestream-elixir-cms-thoughts.html</link>
      <pubDate>Mon, 23 Aug 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-elixir-cms-thoughts.html</guid>
      <description>I&amp;rsquo;ve had a lot of thoughts and a few failed runs at creating a CMS in Elixir. This isn&amp;rsquo;t because Elixir is particular hard to do it in. Rather the opposite. The reason I&amp;rsquo;ve abandoned those efforts have to do with what exactly I want solved with a CMS.
I&amp;rsquo;ve outlined some of that in The WordPress merging problem and WordPress &amp;amp; the gross inefficiencies. I&amp;rsquo;m trying to create a sound technical solution with a sound human solution on top of it.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;ve had a lot of thoughts and a few failed runs at creating a CMS in Elixir. This isn&rsquo;t because Elixir is particular hard to do it in. Rather the opposite. The reason I&rsquo;ve abandoned those efforts have to do with what exactly I want solved with a CMS.</p>
<p>I&rsquo;ve outlined some of that in <a href="/the-wordpress-merging-problem.html">The WordPress merging problem</a> and <a href="/wordpress-and-the-gross-inefficiences.html">WordPress &amp; the gross inefficiencies</a>. I&rsquo;m trying to create a sound technical solution with a sound human solution on top of it. My first efforts were quite top-down in some aspects. I took a small piece of the pie and tried to solve it. This effort is more ground up.</p>
<p>This video is me showing some of the work I&rsquo;ve done on some foundations of what could turn into an Elixir CMS. I also try to explain some of my requirements and some of my thinking. There is a long way still to go. Clearly. Hope you enjoy my perspective on these challenges. If you want to be involved you can reach out over email or find me on Twitter.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-20-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-20-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-20-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-20-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-20.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-20-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video Companion - Teaching Elixir - Comprehensions</title>
      <link>https://underjord.io/video-companion-teaching-elixir-comprehensions.html</link>
      <pubDate>Mon, 16 Aug 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-comprehensions.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time, comprehensions, the most arcane.
You can find the livebooks of the modified lessons in my fork of ElixirSchool.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time, <a href="https://elixirschool.com/en/lessons/basics/comprehensions/">comprehensions</a>, the most arcane.</p>
<p>You can find the livebooks of the modified lessons in <a href="https://github.com/lawik/elixirschool/tree/master/en/lessons/basics">my fork of ElixirSchool</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-10-comprehensions-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-10-comprehensions-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-10-comprehensions-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-10-comprehensions-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-10-comprehensions.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-10-comprehensions-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Attempting Operational Transformation .. again</title>
      <link>https://underjord.io/livestream-attempting-operational-transformation-again.html</link>
      <pubDate>Mon, 16 Aug 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-attempting-operational-transformation-again.html</guid>
      <description>Getting deeper and farther into trying to do Operational Transformation with Erlang wxStyledTextCtrl, aka. Scintilla and TextDelta.
I end up doing almost no TextDelta or Operational Transformation in this one as I try to pin down whether I can reliably insert and delete text from the different editor sessions without causing endless loops of updates. Turns out that there is no great facility for handling that. We learn quite a bit.</description>
      <content:encoded><![CDATA[ <p>Getting deeper and farther into trying to do Operational Transformation with Erlang wxStyledTextCtrl, aka. Scintilla and TextDelta.</p>
<p>I end up doing almost no TextDelta or Operational Transformation in this one as I try to pin down whether I can reliably insert and delete text from the different editor sessions without causing endless loops of updates. Turns out that there is no great facility for handling that. We learn quite a bit. But spoiler, I wrap it up deciding that making it actually work with what is available to me will be too painful.</p>
<p>I do want to revisit Operational Transformation. I&rsquo;ll probably do it with Quill.js which produces Deltas of changes by default. At some point. Let me know if you do figure out any options for this.</p>
<p>I&rsquo;m still very keen on doing things with this wxStyledTextCtrl. Live collaboration is a very particular use-case, I still see all sorts of fun possibilities with implementing an editor with it.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-13-operational-transformation-revisited-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-13-operational-transformation-revisited-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-13-operational-transformation-revisited-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-13-operational-transformation-revisited-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-13-operational-transformation-revisited.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-13-operational-transformation-revisited-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video Companion - Teaching Elixir - Sigils</title>
      <link>https://underjord.io/video-companion-teaching-elixir-sigils.html</link>
      <pubDate>Fri, 13 Aug 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-sigils.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time, sigils, the most arcane.
You can find the livebooks of the modified lessons in my fork of ElixirSchool.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time, <a href="https://elixirschool.com/en/lessons/basics/sigils/">sigils</a>, the most arcane.</p>
<p>You can find the livebooks of the modified lessons in <a href="https://github.com/lawik/elixirschool/tree/master/en/lessons/basics">my fork of ElixirSchool</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-9-sigils-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-9-sigils-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-9-sigils-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-9-sigils-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-9-sigils.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-9-sigils-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Attempting Operational Transformation</title>
      <link>https://underjord.io/livestream-attempting-operational-transformation.html</link>
      <pubDate>Fri, 13 Aug 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-attempting-operational-transformation.html</guid>
      <description>In an act of hubris I try to tackle with wxStyledTextCtrl and Operational Transformation in an unholy alliance of native UI and offline-friendly collaboration. I get partway there but nothing actually works.
Hopefully the flailing is interesting :)
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
      <content:encoded><![CDATA[ <p>In an act of hubris I try to tackle with wxStyledTextCtrl and Operational Transformation in an unholy alliance of native UI and offline-friendly collaboration. I get partway there but nothing actually works.</p>
<p>Hopefully the flailing is interesting :)</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-06-operational-transformation-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-06-operational-transformation-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-06-operational-transformation-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-06-operational-transformation-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-06-operational-transformation.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-08-06-operational-transformation-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video Companion - Teaching Elixir - Modules</title>
      <link>https://underjord.io/video-companion-teaching-elixir-modules.html</link>
      <pubDate>Thu, 05 Aug 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-modules.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover modules.
You can find the livebooks of the modified lessons in my fork of ElixirSchool.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover <a href="https://elixirschool.com/en/lessons/basics/modules/">modules</a>.</p>
<p>You can find the livebooks of the modified lessons in <a href="https://github.com/lawik/elixirschool/tree/master/en/lessons/basics">my fork of ElixirSchool</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-8-modules-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-8-modules-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-8-modules-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-8-modules-360.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-8-modules.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-8-modules-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>It is not about Elixir</title>
      <link>https://underjord.io/it-is-not-about-elixir.html</link>
      <pubDate>Fri, 30 Jul 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/it-is-not-about-elixir.html</guid>
      <description>That&amp;rsquo;s right. It never has been.
Before you read this post with your teeth clenched and ready for some hot takes, chill. This isn&amp;rsquo;t where I throw Elixir under the bus. Rather it is where I try to put my finger on the values that makes Elixir a good fit for me and a language I consistently recommend to people.
So to summarize the values:
 Software should be efficient Software should be maintainable Software should be suitable for continuous development Developers should not burn out from the work Teams should be able to deliver consistently without stress Technically conservative but extremely progressive  So some of my values are fairly specific to software though they apply in other contexts as well.</description>
      <content:encoded><![CDATA[ <p>That&rsquo;s right. It never has been.</p>
<p>Before you read this post with your teeth clenched and ready for some hot takes, chill. This isn&rsquo;t where I throw Elixir under the bus. Rather it is where I try to put my finger on the values that makes Elixir a good fit for me and a language I consistently recommend to people.</p>
<p>So to summarize the values:</p>
<ul>
<li>Software should be efficient</li>
<li>Software should be maintainable</li>
<li>Software should be suitable for continuous development</li>
<li>Developers should not burn out from the work</li>
<li>Teams should be able to deliver consistently without stress</li>
<li>Technically conservative but extremely progressive</li>
</ul>
<p>So some of my values are fairly specific to software though they apply in other contexts as well. Some other ones are much more focused on the people. We can start on the software side.</p>
<h2 id="software-should-be-efficient">Software should be efficient</h2>
<p>I find it immensely frustrating that we have incredibly powerful computers now and pretty much everything feels slower to me. I think back to the days of native desktop software that was snappy and fast. Our browsers are beastly software platforms and everything is now a browser.</p>
<p>Beyond that we have programming languages and paradigms that keep things simple by constraining it to single core operation and making processing that is both concurrent and parallel a second class citizen. In a time where clock-speed is no longer going up but core-count is, this is terrible for efficiently utilizing modern hardware.</p>
<p>Efficiency is not just about hardware utilization. It is also about the experience of using things. If you are crunching the numbers quickly but your software is unresponsive during the whole process that can be just as bad as processing slowly.</p>
<p>In some cases Python is perfectly performant enough and you&rsquo;ll never see an issue. In others you might want Go, Rust or even Fortran, I don&rsquo;t make the rules about what&rsquo;s efficient for your use-case. With Elixir I have the BEAM virtual machine which provides low latency and which typically protects me from screwing up latency too badly. It models applications in primitives that map well to concurrent workloads and they make it much, much easier to not botch it. I find Elixir efficient at utilizing hardware while making it straight-forward to maintain a responsive experience.</p>
<h2 id="software-should-be-long-term-maintainable">Software should be long-term maintainable</h2>
<p>I&rsquo;ve built systems that have been hard to maintain. And then I&rsquo;ve had to maintain them. I don&rsquo;t recommend it.</p>
<p>There is some software that through a combination of persistent developers and maybe good design just keep going and stick with us. I&rsquo;ll try to paint a wide picture here. I wish more software could be like VLC. A piece of software that just keeps working. Since I discovered it I have never had to think about what to use if I want to play media. It has persisted because someone has obviously been able to maintain it.</p>
<p>There is so much software we rely on that some person or group of people maintains for us. Postgres, ffmpeg, Linux, git, bash, vim, Ubuntu, Photoshop, Office. Some of these are maybe driven more by a headstrong business making sure they persist than good architectural choices. With proprietary things that&rsquo;s hard to know. But if we could reduce the churn of software that launches and then dies from complexity I think that&rsquo;d be great.</p>
<p>With my consulting work I&rsquo;ve also dove head-first into other people&rsquo;s code on a regular basis and I&rsquo;ve seen approachable code bases and less approachable ones. Unwinding other people&rsquo;s code to understand it is often challenging and also a big part of the job as a developer.</p>
<p>Some products die as the team decides it needs a full rewrite and the rewrite fails. Or because the only person who knew how it fit together left. Or the team burnt out because every update was a thankless slog of complex drudgery. These are typical costs of unmaintainable systems.</p>
<p>Being able to write parts of the code in isolation, mostly reason about code in isolation and know the scope of changes you make is incredibly important to maintainability. I have never had this experience as strongly as in Elixir. I think this is a combination of the high-level dynamic pragmatism and a general feature of functional programming meeting the Actor model. It is easier to reason about state while the dynamic nature of it keeps the threshold to actual coding low and unceremonious. In Elixir I have been able to contribute code in the first few days of a project in a way that hasn&rsquo;t happened elsewhere.</p>
<p>I&rsquo;ve seen some hairy Elixir code. But it has generally been better than hairy PHP, Python or Javascript from my experiences. I think Elixir provides a paradigm that makes it easier to build maintainable systems.</p>
<h2 id="software-should-be-well-suited-for-continuous-development">Software should be well suited for continuous development</h2>
<p>The development of software is commonly a continuous labor. Every week developers all around the world pick up issues and implement solutions to them. There are a number of ways in which this can work. But important parts are generally around delivering improvements and avoiding the introduction of defects. Usually there are also heavy incentives towards moving fast.</p>
<p>Good tool support, a strong testing story and all of the previously mentioned maintainability concerns feed into what a language is like to do continuous development with. There are also things like productivity, in the sense that Rails is known to be very productive, you can build new features quickly. Rails is not known for maintainability or efficiency. I think this is why a lot of people that have moved from Rails to Phoenix and Ruby to Elixir have done so.</p>
<p>Any later stage software project tends to achieve a certain level of complexity and at a certain point it matters a lot if your tools are good at managing complexity. If the paradigm you work in supports you in keeping things manageable that helps you to move forward. It is quite possible to move forward in something less structured but it places higher demand on each developer to make good choices at every turn.</p>
<hr>
<p>So aside from software values I try to think in terms of individuals, teams and wider organizations.</p>
<h2 id="no-developer-should-be-burning-out-due-to-the-work">No developer should be burning out due to the work</h2>
<p>This happens all the time of course.</p>
<p>It is not primarily a tools issue. But as noted in discussing maintainability and continuous development there is pressure to proceed and complexity to manage and each individual developer is under this pressure. How you manage it is partly up to you. However, it also matters what you are dealing with in the work. Is it clear, is it well-constructed. Are there strong conventions to support you? Do the tools help or hinder you?</p>
<p>I&rsquo;ve heard plenty of complaints from people working in stacks they hate and how the tools cause more problems than they solve or how frustrating it all is.</p>
<p>At the individual level I find developers have an easy time being enthusiastic about Elixir and often delighted by the power and clarity it provides. The practical tooling is very good. Documentation is great. I think this is a tool that supports individual developers in creating software without causing undue stress.</p>
<p>At the end of the day, therapy, experience and a few run-ins with how not to do it has probably done more for me in this area than working with Elixir. But I do feel like the language supports my work personally and that&rsquo;s a good feeling that contributes to building a good stable self in this line of work.</p>
<h2 id="teams-should-be-able-to-deliver-consistently-without-stress">Teams should be able to deliver consistently without stress</h2>
<p>All teams can screw up. That&rsquo;s not entirely avoidable. But consistency isn&rsquo;t about never failing, it is about repeatably being able to succeed. Much like with individual devs I find this to be a people challenge more than a tools challenge. But a tool can provide benefit. Having strong conventions around the tools gives some stable expectations for a team.</p>
<p>I would describe the idioms and conventions of Elixir as a language, tool and community as pretty strong but not very strict. There is direction, there are core ideas, recommendations and writing to lean on for how you can do things. But you don&rsquo;t have to do it that way. In other parts, the language and frameworks actually press you to work within certain constraints.</p>
<p>The Actor model is a particular way of solving particular problems and working with it forces you to think and work within that solution the absolute majority of the time. And those constraints can be very helpful. There are mistakes it simply won&rsquo;t let you make or where it will make the wrong thing really hard to do.</p>
<p>I&rsquo;ve found Elixir to enable and support what I want to do and be enjoyable to work with. When working fast on something that needs to be delivered quickly it lets me do that without ruining the future potential of the system. Reworking internals and refactoring code is very doable due to the limits on shared state and mutability. And when I&rsquo;m working with complex, intricate things and need to slow down and move carefully there is good tooling for creating comprehensive tests. The language and frameworks allow you to build complex and intricate things that can be tested in isolation. It doesn&rsquo;t force me to always prioritize one way.</p>
<h2 id="technically-conservative-but-extremely-progressive">Technically conservative but extremely progressive</h2>
<p>I don&rsquo;t think we should avoid making up new stuff. I do however think there is value in tempering what we invent with some of the wisdom in using what already exists. And I think that balance is very present in Elixir as a technical solution and as a community.</p>
<p>The BEAM it runs on is proven with Erlang since the 80&rsquo;s and that has only improved with time. The Elixir language itself was sort of <a href="https://www.youtube.com/watch?v=suOzNeMJXl0">dubbed &ldquo;done&rdquo; in a keynote</a> (re-iterated <a href="https://elixirforum.com/t/is-elixir-done/20830">here</a>) and it doesn&rsquo;t change very much while still adding nice improvements consistently along with improvements to Erlang/OTP. That&rsquo;s a fairly conservative stance and I think it has a good chance of preventing large breakages like Python 2/3 or enormous legacy to maintain in the standard library like what I&rsquo;ve seen in PHP. Only time will tell.</p>
<p>Elixir as a project, community and going concern hasn&rsquo;t nearly stopped moving though. There is an enthusiasm and ambition in this community that drives forward some remarkable progress on top of a VM that allows us to do things that simply would be unadvisable in other runtimes. Such as Phoenix LiveView. Which allows developers to build reactive HTML-over-the-wire applications with a minimum of frontend/backend ceremony.</p>
<p>The Nerves project, an IoT-ish embedded framework where the BEAM is most of your OS on a Linux kernel, is already <a href="https://twitter.com/fhunleth/status/1388557426283229188">sniffing some RISC-V boards</a>. Dashbit and friends is bringing Machine Learning to Elixir with <a href="https://github.com/elixir-nx">the Nx project</a> and their ludicrously ambitious <a href="https://github.com/livebook-dev/livebook">Livebook</a> project lets us do some very cool things that I don&rsquo;t think would be feasible outside of Elixir.</p>
<p>A big reservation I have about the JS ecosystem is the rate of change, churn and the breakages I&rsquo;ve experienced in projects just from time passing. My experience with Elixir has been very stable, some libraries barely move at all, they are essentially done. Some move slowly, some move faster but I haven&rsquo;t experienced any issues getting older projects going or handling dependencies. Progress, but not very much churn.</p>
<p>Elixir has taken documentation, tooling and developer experience seriously from early on and it shows. Phoenix and LiveView are both great examples of tools that are built to keep you happy and productive when doing the bulk of the work. This is not an ecosystem that is just reaching the baseline, it is pushing forward. You can see Livewire (Laravel) and Hotwire (Rails, etc) picking up what LiveView put forward. I&rsquo;ve heard many devs from other langs speak well of Hex, the package manager and the hexdocs it provides.</p>
<p>I don&rsquo;t need new things for no reason, that&rsquo;s the realm of experiments. I do want new things, actually useful progress, powerful tools built on solid foundations.</p>
<p>Stability and progress.</p>
<h2 id="in-conclusion">In Conclusion</h2>
<p>It is not about Elixir.</p>
<p>Elixir just happens to be a language, ecosystem and community that suits my values and priorities. That is no small thing. It feels like home.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Building with PETAL 4 - Authentication</title>
      <link>https://underjord.io/livestream-building-with-petal-4.html</link>
      <pubDate>Thu, 29 Jul 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-building-with-petal-4.html</guid>
      <description>In this stream of the PETAL Photo project we work on authentication. We build a way to log in with a magic link through email.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.   </description>
      <content:encoded><![CDATA[ <p>In this stream of the PETAL Photo project we work on authentication. We build a way to log in with a magic link through email.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-23-petal-photo-4.mkv-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-23-petal-photo-4.mkv-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-23-petal-photo-4.mkv-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-23-petal-photo-4.mkv-480.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-23-petal-photo-4.mkv.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-23-petal-photo-4.mkv-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Building with PETAL 1 - Getting Started</title>
      <link>https://underjord.io/livestream-building-with-petal-1.html</link>
      <pubDate>Fri, 16 Jul 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-building-with-petal-1.html</guid>
      <description>It begins. For my vacation work-days, a concept to explain soon, I wanted to try to scratch an itch that I feel have product potential. So I decided to do parts of the work on the stream. This is the first part.
To address the vacation work-days thing. I&amp;rsquo;m not being terrible at vacationing, or at least not as far as I can tell. We have a young child in the house, so some time on my own is quite nice and I have established some routines I really enjoy that I didn&amp;rsquo;t want a break from this summer staycation.</description>
      <content:encoded><![CDATA[ <p>It begins. For my vacation work-days, a concept to explain soon, I wanted to try to scratch an itch that I feel have product potential. So I decided to do parts of the work on the stream. This is the first part.</p>
<p>To address the vacation work-days thing. I&rsquo;m not being terrible at vacationing, or at least not as far as I can tell. We have a young child in the house, so some time on my own is quite nice and I have established some routines I really enjoy that I didn&rsquo;t want a break from this summer staycation. Specifically the newsletter and the livestreaming which I both do weekly on fridays. I also really enjoy sitting down with code for a solid session. So spending 1 day per week on this feels pretty good.</p>
<p>Alright. This video covers setting up the project, basing it on another project I&rsquo;ve made and getting started.</p>
<p>The product idea is about sharing media and parts of life with your loved ones and friends without the social mix of sharing your wild friends with your stodgy relatives, or sharing your wild relatives with your stodgy friends. A small-scale, mindful way of keeping in touch and not needing to work with half a dozen message apps to share a picture of your kid. My kid actually. She kicks ass.</p>
<p>If you want to reach about about anything, email or Twitter is good. <a href="mailto:lars@underjord.io">lars@underjord.io</a> and <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> respectively.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-01-petal-photo-1-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-01-petal-photo-1-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-01-petal-photo-1-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-01-petal-photo-1-480.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-01-petal-photo-1.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-01-petal-photo-1-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Building with PETAL 2 - Mostly Tailwind</title>
      <link>https://underjord.io/livestream-building-with-petal-2.html</link>
      <pubDate>Fri, 16 Jul 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-building-with-petal-2.html</guid>
      <description>It continues. After some off-stream work fixing the upload issues from last stream we are now mostly heading into making this visually sensible. We are using the PETAL stack and that means a lot of Tailwind CSS.
This is the second installment, consider watching Part 1 before or after this.
So if you enjoy watching people turn blank white nothing into something more visual, this might be for you, or if you want to see someone that is not a Tailwind expert wield it, also accurate.</description>
      <content:encoded><![CDATA[ <p>It continues. After some off-stream work fixing the upload issues from last stream we are now mostly heading into making this visually sensible. We are using the <a href="https://changelog.com/posts/petal-the-end-to-end-web-stack">PETAL stack</a> and that means a lot of Tailwind CSS.</p>
<p>This is the second installment, consider watching <a href="/livestream-building-with-petal-1.html">Part 1</a> before or after this.</p>
<p>So if you enjoy watching people turn blank white nothing into something more visual, this might be for you, or if you want to see someone that is not a Tailwind expert wield it, also accurate.</p>
<p>Let me know what you think at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter at <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. If you want more of what I do, I suggest the newsletter, signup is at the bottom of the page.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-09-petal-photo-2-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-09-petal-photo-2-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-09-petal-photo-2-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-09-petal-photo-2-480.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-09-petal-photo-2.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-09-petal-photo-2-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video: Building with PETAL 3 - Thumbnails and Transcoding</title>
      <link>https://underjord.io/livestream-building-with-petal-3.html</link>
      <pubDate>Fri, 16 Jul 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-building-with-petal-3.html</guid>
      <description>Getting a bit technical in this one we get into thumbnails and video transcoding in the middle of a heatwave. Watch me sweat .. the details with FFMPEG.
This is the third installment Part 2 and Part 1. I don&amp;rsquo;t think they are necessary context but it probably helps. There has also been a few hours of work before each stream so the system moves forward off screen a bit.</description>
      <content:encoded><![CDATA[ <p>Getting a bit technical in this one we get into thumbnails and video transcoding in the middle of a heatwave. Watch me sweat .. the details with FFMPEG.</p>
<p>This is the third installment <a href="/livestream-building-with-petal-2.html">Part 2</a> and <a href="/livestream-building-with-petal-3.html">Part 1</a>. I don&rsquo;t think they are necessary context but it probably helps. There has also been a few hours of work before each stream so the system moves forward off screen a bit.</p>
<p>I&rsquo;ll say we had very few actual problems in this one, that was nice.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-16-petal-photo-3-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-16-petal-photo-3-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-16-petal-photo-3-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-16-petal-photo-3-480.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-16-petal-photo-3.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-07-16-petal-photo-3-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Trust in Software, an All Time Low</title>
      <link>https://underjord.io/trust-in-software-an-all-time-low.html</link>
      <pubDate>Fri, 09 Jul 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/trust-in-software-an-all-time-low.html</guid>
      <description>I don&amp;rsquo;t think I&amp;rsquo;ve ever had more distrust and as a consequence distate for software than in recent years. I don&amp;rsquo;t think its just me as a tech-nerd with artisanal tech-carpentry aspirations. I want people to build well, treat their users right and generally exercise some actual restraint. I see it very clearly and I react more viscerally than anyone non-technical in my surroundings. However, I see the frustrations and the consequences everywhere.</description>
      <content:encoded><![CDATA[ <p>I don&rsquo;t think I&rsquo;ve ever had more distrust and as a consequence distate for software than in recent years. I don&rsquo;t think its just me as a tech-nerd with artisanal tech-carpentry aspirations. I want people to build well, treat their users right and generally exercise some actual restraint. I see it very clearly and I react more viscerally than anyone non-technical in my surroundings. However, I see the frustrations and the consequences everywhere.</p>
<h2 id="windows-is-an-adversary-of-the-desktop-user">Windows is an Adversary of the Desktop User</h2>
<p>Most computer users I know don&rsquo;t particularly want a Microsoft Account. Microsoft wants them to have one though. None of the people I&rsquo;ve set up with computers have asked for OneDrive, Microsoft really wants them to know about it though. No one I&rsquo;ve ever heard of uses Cortana but you bet there&rsquo;s stuff about it in your toolbars. The experience of installing, setting up and using the operating systems is an exercise in finding new novel ways to decline things or simply capitulating to see if it makes the nagging go away.</p>
<p>And then there&rsquo;s the ads. I mean, personalized experiences. Smart Cloud Somethings. It is at a foundational level completely unacceptable to me that a commercial operating system that you&rsquo;ve paid for, or that was included on the computer you paid for, will show you advertising in your primary modes of navigation. Read, the Start menu. It just looks like an app you might have that you never installed. But it&rsquo;ll take you to the store. It&rsquo;ll take my mom to the store. It&rsquo;ll mean she thinks twice about everything her OS shows her, or capitulate entirely and just accept that her computer is not really hers and isn&rsquo;t knowable to her.</p>
<p>I find it infuriating. I don&rsquo;t hate Windows. I don&rsquo;t love using it but I use it for gaming and I help friends and loved ones with it. I&rsquo;m not precious about using it when it makes sense. There are things about Windows that are immensely impressive. My father-in-law&rsquo;s old desktop invoicing program still runs, more than a decade later without updates. I respect that. That&rsquo;s worthy of acknowledgment.</p>
<p>Windows 10, which I liked as much as Windows 7 at the start, has slowly rotted on me, or just ground me down to where I find it reprehensible. Where every new notification, interruption, thing changing under my feet or update that bricks my entire computer, just pokes straight at the raw exposed nerve ending that used to be a thick skin.</p>
<p>Beyond that they&rsquo;ve not reconciled their old and new control panels which just makes the OS numerous layers of confusion, there are endless screens of paragraphs of text, toggle switches that only barely try to do what you want and any time you actually need something done you need to find a link to Advanced or whatever takes you to the old UI that actually does the damn thing. How does that belong here? Well it isn&rsquo;t really learnable anymore. The paradigms and concepts are barely cohesive anymore.</p>
<p>And Microsoft has with help from the rest of the industry turned the word &ldquo;telemetry&rdquo; into something that makes entire sections of the tech world recoil and rev up the privacy monologue engines. I don&rsquo;t want to care too much about that but when every goddamn app feels the need to get that sweet data it starts to get old. And that&rsquo;s not even the tracking stuff, that&rsquo;s later.</p>
<p>Windows is an amazing platform in many ways. It is commerically successful and viable. It doesn&rsquo;t operate under Microsoft&rsquo;s thumb in the way the Apple ecosystem does which there is currently war about. Windows could be the most reasonable operating system. They could just stop all the bullshit. They absolutely don&rsquo;t need it. Telemetry, maybe they need some of that. But ads? Nah. Upsell? Nah. They could just choose to be better.</p>
<p>Also, people liked Skype once and hate Teams. So with Windows 11 and the pandemic we are building it into the OS. Skype? No. Of course not. Teams! More Teams!</p>
<p>I really hope Windows 11 manages to improve the UI cohesion. I have absolutely zero hopes that the rest will be less egregious. Also, they are partnering with trusted friends Amazon for bringing Android apps to the Microsoft Store. That just gives me all sorts of warm and fuzzy creeps. I like the feature of being able to run Android apps. Great! Cool! But Amazon?</p>
<h2 id="the-smartphone-era">The Smartphone Era</h2>
<p>As the smartphone arrived everyone started building apps. Small plucky software packages that did things previously untenable on a phone. Pretty cool. And there was money to be made.</p>
<p>The friction to building, publishing and selling software went from significant to negligable and the potential audience went from large to pretty much every single person. With this came the easy-money-chasers, the scammers, the thieves, the corporate giants with the half-assed ideas. Pretty much everyone. Among them were the gems, the hard-fought stalwarts and a bunch of pretty decent software. But the deluge is more noticeable.</p>
<p>I&rsquo;m pretty sure the &ldquo;your device starts randomly asking for shit&rdquo; thing wasn&rsquo;t introduced with the smartphone. I feel like OEM bullshit driver software had that capability way back. It did however become ubiquitous. Now my mother has the pleasure of calling me because her phone asked her for something weird. Why? Samsung, maker of the phone, decided they should have access to her Google Drive or something. It&rsquo;s probably harmless. But we can&rsquo;t know that. And Samsung isn&rsquo;t so good at bundled software that I&rsquo;d necessarily trust them.</p>
<p>If you want to find any simple utility app you have no chance to find a reasonably priced, high quality one without tons of up front research. Because the roulette of an app store search will get you an undiscernable mess. And you know you have to be careful, mindful. Because most apps are traps, trackers or hustles.</p>
<p>Smartphone apps taught us about security in the least satisfying way. It showed us that you can&rsquo;t trust the software and you need to very, very mindful to not get fucked.</p>
<p>So many I know stick with the big actors. They wouldn&rsquo;t pick up small indie apps because it is hopeless to know a good small app from an evil small app up front. This is especially true on Android which doesn&rsquo;t have a cultural ecosystem of app devs that are worth trusting (at least I don&rsquo;t know of it). iOS in that sense has a bit of a reputation system and a large enthusiast sphere which helps with curation. The App Store is of course still a shitshow. I mean, carefully reviewed and curated.</p>
<p>Right, so the big actors. They have again and again been shown to be worthy of no real trust. Facebook standing head and shoulders above everyone else in being creepy and worthy of all skepticism and distrust. Google has a sort of elemental honesty in being unapologetic trackers. It was always their business and I don&rsquo;t feel like they were every quite so psychotic as Facebook. That said, we know they are in everything and we absolutely are being tracked by them.</p>
<p>So while the big actors are slightly less likely to completely screw you on security, software quality or straight money. They are pretty much guaranteed to screw you on privacy. As an EU citizen the GDPR does make them disclose and ask me to opt in and limit how much they can require from me. It really does make it more noticeable. But with near-monopolies, network effects and plenty of money to throw at these problems they do what they can to entice, inconvenience and generally prod you towards surrender. You&rsquo;ll get much better smart somethings.</p>
<h2 id="hostile-devices">Hostile Devices</h2>
<p>I&rsquo;ve mostly wrangled my devices into a semblance of being under control. But I don&rsquo;t trust them, I only like some of them and I don&rsquo;t trust any of them very far. The one I trust furthest is my Linux workstation. That one is finicky only in being a bit rough here and there, it isn&rsquo;t hostile or under the control of some alien software. To my knowledge. Maybe System76 will brick Pop or unveil their new ads. I doubt it and if they did I&rsquo;d fall back to Debian.</p>
<p>Everything else is asking for your permission to do the thing you don&rsquo;t want them to do. Everything is asking for more than they need. There&rsquo;s a very cool trick you can pull to avoid showing a cookie notice for your site, don&rsquo;t use unnecessary cookies. Cookie for login? Pretty much fine with the law. GDPR? If you aren&rsquo;t collecting personal data you don&rsquo;t need to ask complicated questions.</p>
<p>Want to improve the UX of your application or SaaS? Don&rsquo;t do things that require users to opt in to things they don&rsquo;t want to do. Free UX!</p>
<p>But oh, the metrics and analytics, we so very need them. I don&rsquo;t think we do. Sure, they can be useful but the most actionable and useful information is already very legal and available. Did people visit the thing? Did people buy the thing? Did they complain about it afterwards? I&rsquo;m aware that there is signal that is hard to get. But I really don&rsquo;t think grinding away at the foundations of human-computer interactions is a very good investment.</p>
<p>We are spending our time deciding whether to fight dark patterns or just give up. They are everywhere. Every convenience, every delightful offering comes with a side of &ldquo;just consent to everything&rdquo;.</p>
<p>Doing the opposite of this, respecting privacy, providing user control, minding the user&rsquo;s agency, these are things that only small operations can do today. And it is the differentiator that we can offer. We can build things that don&rsquo;t suck. Any sufficiently large company is required to start sucking at some point. It&rsquo;s probably a law.</p>
<p>Each megacorp can grab a shred of something real and important and claim that they do that thing oh so well. Apple with privacy. Google with .. the heck kind of good thing should I be associating with Google these days? I don&rsquo;t even know. They&rsquo;ve been crap at marketing themselves for ages now. Facebook with .. political manipulation? Connecting people? Amazon with .. exploitation.. Microsoft with Open Source and bringing Linux to the desktop? Regardless, they can push a very limited set of virtues and we can just assume everything else is corporately mandated to be morally bankrupt.</p>
<h2 id="in-conclusion">In Conclusion</h2>
<p>The background buzz of permission dialogues is deafening, deadening. Cookie notices, consent forms, allow/reject. Should I trust this? Can I install that? Do you want to try OneDrive? Dropbox needs to update again. You need to restart your browser. Are you sure you don&rsquo;t want to try Edge? You really shouldn&rsquo;t be installing untrusted dangerous software. This trusted approved software wants to know your location always. Allow/Decline?</p>
<p>Your privacy is very important to us. We would like to know what you are doing at all times. Accept / Ask me again later.</p>
<p>Yeah. I couldn&rsquo;t give a good reason why anyone should trust, or like, software the way it typically works these days.</p>
<p>Ask me again later. Not right now. More information. Are you sure?</p>
<hr>
<p>If you want more of my thoughts and writing I suggest <a href="#newsletter-push">the newsletter</a>
.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Deploying with k3s and ArgoCD</title>
      <link>https://underjord.io/k3s-argocd-livestream.html</link>
      <pubDate>Thu, 01 Jul 2021 10:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/k3s-argocd-livestream.html</guid>
      <description>I&amp;rsquo;m pretty skeptical of Kubernetes as a choice for small teams, small projects and any less complicated operation. Gerhard Lazu is quite enthusiastic about it on the other hand. After having me on his show Ship It!, a Changelog.com production, I expressed some curiosity about k3s, a lightweight variant of k8s. He offered to come on the livestream and show me how good life can be. Hope you enjoy the video.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;m pretty skeptical of Kubernetes as a choice for small teams, small projects and any less complicated operation. Gerhard Lazu is quite enthusiastic about it on the other hand. After having me on his show Ship It!, a Changelog.com production, I expressed some curiosity about k3s, a lightweight variant of k8s. He offered to come on the livestream and show me how good life can be. Hope you enjoy the video.</p>
<p>After this show I primarily have more nuanced feelings about the whole thing. I see advantages to this approach but I would say I still see to much mystery and magic in it for my taste. Things are doing stuff and I have no idea what&rsquo;s what.</p>
<p>Regardless where you fall on the spectrum I think Gerhard showed us some very thoughtful approaches to using k3s, some absolutely over the top nice uses of <code>make</code> and scratched the surface of ArgoCD.</p>
<p>The video is also <a href="https://www.youtube.com/watch?v=v7_Ebpkazis">available on YouTube</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-18-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-18-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-18-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-18-480.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-18.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-18-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Systems Design &amp; Architecture livestream</title>
      <link>https://underjord.io/livestream-systems-design-architecture.html</link>
      <pubDate>Thu, 01 Jul 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/livestream-systems-design-architecture.html</guid>
      <description>For this livestream I sat down with pen and (i)Pad and tried to hash out some of my design thinking for a product idea I&amp;rsquo;ve been turning over in my head.
Let me know if this type of thinky-talky streams are of interest and I just might do some more of them. The video is also on the YouTube channel if you prefer.
 function switch_video(element) { var src = element.</description>
      <content:encoded><![CDATA[ <p>For this livestream I sat down with pen and (i)Pad and tried to hash out some of my design thinking for a product idea I&rsquo;ve been turning over in my head.</p>
<p>Let me know if this type of thinky-talky streams are of interest and I just might do some more of them. The video is also on the YouTube channel if you prefer.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-11-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-11-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-11-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-11-480.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-11.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-11-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video Companion - Teaching Elixir - Pipe Operator</title>
      <link>https://underjord.io/video-companion-teaching-elixir-pipe-operator.html</link>
      <pubDate>Thu, 01 Jul 2021 04:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-pipe-operator.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover the pipe operator |&amp;gt;.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
The video is also available on YouTube.
 function switch_video(element) { var src = element.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover the <a href="https://elixirschool.com/en/lessons/basics/pipe-operator/">pipe operator</a> |&gt;.</p>
<p>You can find the very simplistic Livebook version of the Elixir school content on <a href="https://github.com/lawik/elixirschool/blob/master/en/lessons/basics/pipe-operator.livemd">my forked Elixir School repo</a> for now.</p>
<p>The video is also available on <a href="https://www.youtube.com/watch?v=UJN1cDQEfsY">YouTube</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-7-pipe-operator-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-7-pipe-operator-1080p.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-7-pipe-operator-720p.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-7-pipe-operator-480.mp4" onclick="return switch_video(this);" target="_blank">low</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-7-pipe-operator.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-7-pipe-operator-4k.mp4" type="video/mp4"/>
        Sorry, your browser does not support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Onboarding to Elixir</title>
      <link>https://underjord.io/onboarding-to-elixir.html</link>
      <pubDate>Tue, 22 Jun 2021 05:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/onboarding-to-elixir.html</guid>
      <description>I&amp;rsquo;ve worked with a number of clients on Elixir projects and I&amp;rsquo;ve onboarded myself, I&amp;rsquo;ve been onboarded and I&amp;rsquo;ve onboarded others. And compared to my experiences with PHP/Python/Javascript and my limited experience with C#/.Net I have experienced quite a difference.
Elixir projects tend to be very consistently laid out. Especially Phoenix-based web projects. The basic Phoenix generators provide a fair number of opinions on where things go. Even without that the general shape of an Elixir project is surprisingly stable.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;ve worked with a number of clients on Elixir projects and I&rsquo;ve onboarded myself, I&rsquo;ve been onboarded and I&rsquo;ve onboarded others. And compared to my experiences with PHP/Python/Javascript and my limited experience with C#/.Net I have experienced quite a difference.</p>
<p>Elixir projects tend to be very consistently laid out. Especially Phoenix-based web projects. The basic Phoenix generators provide a fair number of opinions on where things go. Even without that the general shape of an Elixir project is surprisingly stable.</p>
<p>So across a few different clients I&rsquo;ve seen a number of Elixir projecs that have a few years on them and while there were variations such as not actually utilizing the module hierarchy and putting all of it in the widest possible namespace or overriding some common conventions, overall even the old projects were very reasonable to explore. They tend to flow from the mix.exs projects file and from there you can logically follow what parts of the system are started as you run the application. That will let you figure out what routes it exposes, what work it does in the background and much more. The Elixir supervision tree (inherited from Erlang) makes the shape of the application more clear to you as a developer.</p>
<p>My first onboarding onto an Elixir project was a project that essentially predated Phoenix having a stable release and so it was built right on top of Cowboy and WebSockets. I was pretty familiar with Elixir already. The guy who introduced me to the system and product on the other hand, was doing some Elixir, but was primarily doing other work. I estimated some work for this client, around a 100 hours, for the initial engagement of adding some API endpoints to provide some new functionality for external parties. And then I built it. Knowing Elixir but with no experience of this particular system and without the strong conventions of Phoenix I successfully over-delivered with some added niceties at a couple of hours shy of the estimate. I like to think I&rsquo;m competent at what I do but I&rsquo;m not convinced I would have that turnaround on a similarly sized Python or PHP project. The compounding complexity tends to be much higher.</p>
<p>Comparatively the same work in a PHP client&rsquo;s code base would likely have taken me around twice the time. Why? Mostly due to clarity. Elixir is a functional programming language with a very strong pragmatic streak. It makes it easy to write clear code, rather than clever code. Comparing two codebases is of course anecdotal but it lines up with all my experiences of dynamic OOP code bases vs Elixir code bases. The consequences of making changes are much clearer, the data you have access to and how it gets passed around is plain to see. It removes a lot of guess-work.</p>
<p>Same Elixir code base, a number of months later I&rsquo;m the person who will onboard a new developer. He has been doing PHP for a long time, no Elixir experience. No real issues, he certainly spent some time delving in and picking up the syntax and concepts of the language. He is at the time of writing productively the main maintainer of the code base. I think we had two video pairing sessions to introduce the concepts and how the thing was set up. It really didn&rsquo;t take more.</p>
<p>Another client, a significant fintech code base in Elixir. A few different services, some with Phoenix, some without. I was brought on, given help to get things set up and started. Due to the strong conventions around how Elixir and Phoenix is used that mostly entailed giving me credentials and answering some questions on specifics. I believe I had my first commit the same week. My first major contribution was probably the next week and then we were off to the races.</p>
<p>So bringing on people that know Elixir tends to go well in my experience and I&rsquo;ve even had good luck bringing on an experienced PHP dev that didn&rsquo;t know Elixir. How about less experienced folks?</p>
<p>I&rsquo;ve introduced at least three coding school students to Elixir during their internships or part time work with me. They typically knew some PHP, some Python and some Node.js at that point but hadn&rsquo;t really used any of them in anger. One is presently working professionally with a production Elixir application and another is currently working with me as an assistant developer and doing well using Elixir on client work.</p>
<p>I haven&rsquo;t found much difficulty in teaching Elixir or training people into the language. There are some Functional Programming concepts to work through and some habits to break. Thankfully Elixir does not look or feel incredibly different from your typical OOP languages. Most of it will be familiar to any developer. It simply brings some very different powers to the table.</p>
<p>One of the things I find makes onboarding easier is that you can effectively approach Elixir both bottom-up and top-down depending on what you are trying to do. If you want to get a foundational overview of what the application is and how it hangs together, look at the supervision tree, the dependencies and you can absorb the shape of the system. And the moment you need to implement a small change or add something, you can figure out the place where those things happen in the system and work there, in relative isolation and you don&rsquo;t have to know everything about the surrounding system to be productive. Immutability and no shared state makes your system easier to reason about in separate parts. And that means you need to know less to make useful changes.</p>
<p>I&rsquo;ve picked up another client&rsquo;s Phoenix project without any guidance or real introduction to the code and been able to provide changes on the same day. Just by knowing my way around the conventions. Certainly this will vary by person and experience but it isn&rsquo;t just due to some stroke of brilliance on my part. Strong conventions and a clear language where code is easy to reason about.</p>
<p>All in all, I&rsquo;ve found onboarding in Elixir to be ridiculously straight-forward compared to a lot of other projects and I think that&rsquo;s an important factor and I believe it says something about the long-term ease of development as well. A lot of Elixir developers tend to describe &ldquo;boring&rdquo; Elixir projects that just keep working and are easy to maintain.</p>
<p>Boring is a rare and powerful property in software systems. Especially when they can achieve exciting outcomes such as near real-time, strong concurrency and parallelism with distributed computing just baked in. Nothing quite like it.</p>
<hr>
<p>If you want to hear me (and others!) go on about Elixir in further detail I&rsquo;d suggest <a href="https://beamrad.io">BEAM Radio</a> but if you&rsquo;d rather get more variety in topics I&rsquo;d suggest the <a href="https://regprog.com">Regular Programming</a> podcast or my <a href="/newsletter.html">newsletter</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video companion - Teaching Elixir - Functions</title>
      <link>https://underjord.io/video-companion-teaching-elixir-functions.html</link>
      <pubDate>Tue, 15 Jun 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-functions.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover functions.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
The video is also available on YouTube.
Sorry, your browser doesn&#39;t support embedded videos.   If you enjoy this or have questions you can reach me via email as lars@underjord.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover <a href="https://elixirschool.com/en/lessons/basics/functions/">functions</a>.</p>
<p>You can find the very simplistic Livebook version of the Elixir school content on <a href="https://github.com/lawik/elixirschool/blob/master/en/lessons/basics/functions.livemd">my forked Elixir School repo</a> for now.</p>
<p>The video is also available on <a href="https://www.youtube.com/watch?v=xEe5wHtv75o">YouTube</a>.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-6-functions.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>If you enjoy this or have questions you can reach me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Setting up Prometheus and Grafana with Elixir</title>
      <link>https://underjord.io/promex-livestream.html</link>
      <pubDate>Mon, 07 Jun 2021 10:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/promex-livestream.html</guid>
      <description>Alex Koutmos, creator of the PromEx library and buddy from the radio show gave us some of his time to help set up Grafana and Prometheus metrics for our application Noted. This was an extremely tight path to a lot of observability. PromEx is very kind to you as a developer. Leveraging the common telemetry API to make something really compelling. And then Alex has spent the effort so you get a whole bunch of useful default dashboards rather than needing to decide up front what you want.</description>
      <content:encoded><![CDATA[ <p>Alex Koutmos, creator of the PromEx library and buddy from the radio show gave us some of his time to help set up Grafana and Prometheus metrics for our application Noted. This was an extremely tight path to a lot of observability. PromEx is very kind to you as a developer. Leveraging the common telemetry API to make something really compelling. And then Alex has spent the effort so you get a whole bunch of useful default dashboards rather than needing to decide up front what you want. I thought this was quite the well-packed hour. Thank you Alex for coming on the stream to do this. And for preparing!</p>
<p>The branch is on Github as <a href="https://github.com/lawik/noted/tree/prom_ex">lawik/noted:prom_ex</a> and the video is below.</p>
<p>Also available on <a href="https://www.youtube.com/watch?v=QPTwhH75T5M">the YouTube channel</a>.</p>
<p>You can find more by Alex at <a href="https://akoutmos.com/">akoutmos.com</a>, <a href="https://beamrad.io">beamrad.io</a> and you can tell him how you really feel <a href="https://twitter.com/akoutmos">@akoutmos on Twitter</a>. PromEx is <a href="https://hex.pm/packages/prom_ex">prom_ex on Hex</a> and that&rsquo;ll lead you to docs and repo.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/thumbnail-promex.jpg">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-06-04.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>Enjoy the things I do? Sign up for <a href="https://underjord.io/newsletter.html">the newsletter</a>, I write more about Elixir, my projects and developer experiences there.</p>
<p>Want more voice? I talk about Elixir on <a href="https://beamrad.io">Beam Radio</a> and more about programming in general on <a href="https://regprog.com">Regular Programming</a>.</p>
<p>If you want to discuss it with me, you can find me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video companion - Teaching Elixir - Control Structures</title>
      <link>https://underjord.io/video-companion-teaching-elixir-control-structures.html</link>
      <pubDate>Mon, 07 Jun 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-control-structures.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover control structures.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
The video is also available on YouTube.
Sorry, your browser doesn&#39;t support embedded videos.   If you enjoy this or have questions you can reach me via email as lars@underjord.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover <a href="https://elixirschool.com/en/lessons/basics/control-structures/">control structures</a>.</p>
<p>You can find the very simplistic Livebook version of the Elixir school content on <a href="https://github.com/lawik/elixirschool/blob/master/en/lessons/basics/control-structures.livemd">my forked Elixir School repo</a> for now.</p>
<p>The video is also available on <a href="https://www.youtube.com/watch?v=a1Y4F-2HczE">YouTube</a>.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-5-control-structures.mkv" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>If you enjoy this or have questions you can reach me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Improving the crawler</title>
      <link>https://underjord.io/webcrawler-livestream-2.html</link>
      <pubDate>Fri, 04 Jun 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/webcrawler-livestream-2.html</guid>
      <description>A good jam session on improving the crawler. A bunch of basic ETS usage, some bug hunting and fixing and in the end I think it is better than it was :)
The project repo I built up during the stream is on Github as lawik/caterpillar and the video is below.
Also available on the YouTube channel.
Sorry, your browser doesn&#39;t support embedded videos.   Enjoy the things I do?</description>
      <content:encoded><![CDATA[ <p>A good jam session on improving the crawler. A bunch of basic ETS usage, some bug hunting and fixing and in the end I think it is better than it was :)</p>
<p>The project repo I built up during the stream is on Github as  <a href="https://github.com/lawik/caterpillar">lawik/caterpillar</a> and the video is below.</p>
<p>Also available on <a href="https://www.youtube.com/watch?v=y8Rr5G2Pu8M">the YouTube channel</a>.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-05-28.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>Enjoy the things I do? Sign up for <a href="https://underjord.io/newsletter.html">the newsletter</a>, I write more about Elixir, my projects and developer experiences there.</p>
<p>Want more voice? I talk about Elixir on <a href="https://beamrad.io">Beam Radio</a> and more about programming in general on <a href="https://regprog.com">Regular Programming</a>.</p>
<p>If you want to discuss it with me, you can find me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video companion - Teaching Elixir - Pattern Matching</title>
      <link>https://underjord.io/video-companion-teaching-elixir-pattern-matching.html</link>
      <pubDate>Mon, 31 May 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-pattern-matching.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover pattern matching.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
Today&amp;rsquo;s goof is to call the whole thing Learning Elixir at the start. Also we had to start over once because I screwed up the entire Livebook.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover <a href="https://elixirschool.com/en/lessons/basics/pattern-matching/">pattern matching</a>.</p>
<p>You can find the very simplistic Livebook version of the Elixir school content on <a href="https://github.com/lawik/elixirschool/blob/master/en/lessons/basics/pattern-matching.livemd">my forked Elixir School repo</a> for now.</p>
<p>Today&rsquo;s goof is to call the whole thing Learning Elixir at the start. Also we had to start over once because I screwed up the entire Livebook. But you don&rsquo;t have to see that. It wasn&rsquo;t funny enough.</p>
<p>The video is also available on <a href="https://www.youtube.com/watch?v=z6jF0s7jzRk">YouTube</a>.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-4-pattern-matching.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>If you enjoy this or have questions you can reach me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Building a web crawler</title>
      <link>https://underjord.io/webcrawler-livestream-1.html</link>
      <pubDate>Fri, 21 May 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/webcrawler-livestream-1.html</guid>
      <description>Had some great fun building on this webcrawler on the stream. Plan to continue with this in coming livestreams, so watch this to get up to speed on what we are doing.
We grab an HTTP client, and HTML parser and then we go hog wild spawning Task processes and fetching web pages.
The project repo I built up during the stream is on Github as lawik/caterpillar and the video is below.</description>
      <content:encoded><![CDATA[ <p>Had some great fun building on this webcrawler on the stream. Plan to continue with this in coming livestreams, so watch this to get up to speed on what we are doing.</p>
<p>We grab an HTTP client, and HTML parser and then we go hog wild spawning Task processes and fetching web pages.</p>
<p>The project repo I built up during the stream is on Github as  <a href="https://github.com/lawik/caterpillar">lawik/caterpillar</a> and the video is below.</p>
<p>Also available on <a href="https://youtu.be/CkNhZkXK060">the YouTube channel</a>.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-05-21.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>Enjoy the things I do? Sign up for <a href="https://underjord.io/newsletter.html">the newsletter</a>, I write more about Elixir, my projects and developer experiences there.</p>
<p>Want more voice? I talk about Elixir on <a href="https://beamrad.io">Beam Radio</a> and more about programming in general on <a href="https://regprog.com">Regular Programming</a>.</p>
<p>If you want to discuss it with me, you can find me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video companion - Teaching Elixir - Enums</title>
      <link>https://underjord.io/video-companion-teaching-elixir-enum.html</link>
      <pubDate>Mon, 17 May 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-enum.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover enum.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
There was an audio issue while recording that couldn&amp;rsquo;t be isolated and fixed where my friend was quite a bit lower in volume than I was.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover <a href="https://elixirschool.com/en/lessons/basics/enum/">enum</a>.</p>
<p>You can find the very simplistic Livebook version of the Elixir school content on <a href="https://github.com/lawik/elixirschool/blob/master/en/lessons/basics/enum.livemd">my forked Elixir School repo</a> for now.</p>
<p>There was an audio issue while recording that couldn&rsquo;t be isolated and fixed where my friend was quite a bit lower in volume than I was. I&rsquo;ll be looking at making sure those are separate tracks so I can fix those issues in the future. I&rsquo;ve tried to with some audio-mangling so compared to other episodes the audio might be a bit rubbish. Very sorry about that.</p>
<p>The video is also available on <a href="https://youtu.be/HSnnfEurbZ8">YouTube</a>.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/teaching-elixir-part-3-enum-fixed-audio.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>If you enjoy this or have questions you can reach me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Native UI with Elixir and wxWidgets</title>
      <link>https://underjord.io/native-ui-wx-elixir.html</link>
      <pubDate>Fri, 14 May 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/native-ui-wx-elixir.html</guid>
      <description>This livestream turned out well. We got the Erlang wx module to work as intended in Elixir. We built a window, we made some controls, we made them interact. Good fun.
This stream connects to some previous thoughts I&amp;rsquo;ve expressed about taking shortcuts in software or prioritizing developer experience over end-user experience, efficiency, consistency and so on. I wrote about that as the idea of artisanal software. wxWidgets is a bit more pragmatic than that lofty post.</description>
      <content:encoded><![CDATA[ <p>This livestream turned out well. We got the Erlang wx module to work as intended in Elixir. We built a window, we made some controls, we made them interact. Good fun.</p>
<p>This stream connects to some previous thoughts I&rsquo;ve expressed about taking shortcuts in software or prioritizing developer experience over end-user experience, efficiency, consistency and so on. I wrote about that as the idea of <a href="https://underjord.io/artisanal-software-beyond-pragmatism.html">artisanal software</a>. wxWidgets is a bit more pragmatic than that lofty post. But I&rsquo;ve been curious to do more with it for ages.</p>
<p>The project repo I built up during the stream is <a href="https://github.com/lawik/native-ui-sample">available here</a> and the video is below.</p>
<p>Also available on <a href="https://youtu.be/CkNhZkXK060">the YouTube channel</a>.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-05-14.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>Enjoy the things I do? Sign up for <a href="https://underjord.io/newsletter.html">the newsletter</a>, I write more about Elixir, my projects and developer experiences there.</p>
<p>If you want to discuss it with me, you can find me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - PETAL Stack Setup</title>
      <link>https://underjord.io/petal-stack-setup-stream-vod.html</link>
      <pubDate>Tue, 11 May 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/petal-stack-setup-stream-vod.html</guid>
      <description>Last weeks livestream was spent mostly on setting up the PETAL stack basics for a project. So if you want the video version of setting up PETAL with my guide this is for you.
Then we also spent some time setting up Exqlite so we can use Sqlite instead of Postgres. That worked nicely, we tested it with phx.gen.auth. I will try to find some time to make a post on this later.</description>
      <content:encoded><![CDATA[ <p>Last weeks livestream was spent mostly on setting up the PETAL stack basics for a project. So if you want the video version of <a href="https://underjord.io/getting-started-with-petal.html">setting up PETAL with my guide</a> this is for you.</p>
<p>Then we also spent some time setting up Exqlite so we can use Sqlite instead of Postgres. That worked nicely, we tested it with phx.gen.auth. I will try to find some time to make a post on this later. For now you can mostly look at <a href="https://github.com/lawik/noted">lawik/noted</a> which has a branch and commit for Sqlite support.</p>
<p>And finally we started looking at setting up SiteEncrypt. We didn&rsquo;t finish that part, so maybe we do that in the future sometime.</p>
<p>Also available on <a href="https://www.youtube.com/watch?v=YixXqPyASGE">the YouTube channel</a>.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-05-10.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>Enjoy the things I do? Sign up for <a href="https://underjord.io/newsletter.html">the newsletter</a>, I write more about Elixir, my projects and developer life there.</p>
<p>If you want to discuss it with me, you can find me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video companion - Teaching Elixir - Collections</title>
      <link>https://underjord.io/video-companion-teaching-elixir-collections.html</link>
      <pubDate>Mon, 10 May 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-collections.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover collections.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
The video is also available on YouTube.
Sorry, your browser doesn&#39;t support embedded videos.   If you enjoy this or have questions you can reach me via email as lars@underjord.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover <a href="https://elixirschool.com/en/lessons/basics/collections/">collections</a>.</p>
<p>You can find the very simplistic Livebook version of the Elixir school content on <a href="https://github.com/lawik/elixirschool/blob/master/en/lessons/basics/collections.livemd">my forked Elixir School repo</a> for now.</p>
<p>The video is also available on <a href="https://youtu.be/L2KjZkeqzh8">YouTube</a>.</p>
<div class="fancy-video-container">
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-teaching-elixir-2-collections.webm" type="video/webm; codecs=vp9,vorbis" />
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-teaching-elixir-2-collections.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>If you enjoy this or have questions you can reach me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video companion - Teaching Elixir - Basics</title>
      <link>https://underjord.io/video-companion-teaching-elixir-basics.html</link>
      <pubDate>Mon, 03 May 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/video-companion-teaching-elixir-basics.html</guid>
      <description>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover the very basics.
This is the first episode and we&amp;rsquo;ll definitely polish our process as we go. They will be much shorter and probably simpler than the live stream videos. The goal is to work through Elixir School and get my friend properly started with Elixir.</description>
      <content:encoded><![CDATA[ <p>Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover the very <a href="https://elixirschool.com/en/lessons/basics/basics/">basics</a>.</p>
<p>This is the first episode and we&rsquo;ll definitely polish our process as we go. They will be much shorter and probably simpler than the live stream videos. The goal is to work through <a href="https://elixirschool.com/">Elixir School</a> and get my friend properly started with Elixir. So it will be a mix of going through the content and any derailments and asides we end up going through.</p>
<p>You can find the very simplistic Livebook version of the Elixir school content on <a href="https://github.com/lawik/elixirschool/blob/master/en/lessons/basics/basics.livemd">my forked Elixir School repo</a> for now.</p>
<p>The video is also available on <a href="https://youtu.be/U7PR-cI89ag">YouTube</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
</script>
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-teaching-elixir-1-basics.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>If you enjoy this or have questions you can reach me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Stream Overlay with LiveView</title>
      <link>https://underjord.io/stream-overlay-liveview-stream-vod.html</link>
      <pubDate>Fri, 30 Apr 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/stream-overlay-liveview-stream-vod.html</guid>
      <description>A livestream where I created some parts of an overlay for my stream. In this case it shows some indication of what we do during a coding stream by showing lines-of-code stats as we go. This is the view-at-your-leisure archival footage of that. This time in 1080.
Also available on the YouTube.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.</description>
      <content:encoded><![CDATA[ <p>A livestream where I created some parts of an overlay for my stream. In this case it shows some indication of what we do during a coding stream by showing lines-of-code stats as we go. This is the view-at-your-leisure archival footage of that. This time in 1080.</p>
<p>Also available on <a href="https://www.youtube.com/watch?v=Q1Y-abW3DNs">the YouTube</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
</script>
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-stream-vod-liveview-overlay-2021-04-30.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
<p>If you want to discuss it with me, you can find me via email as <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Membrane Media Processing &amp; LiveView</title>
      <link>https://underjord.io/membrane-media-processing-and-liveview.html</link>
      <pubDate>Mon, 26 Apr 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/membrane-media-processing-and-liveview.html</guid>
      <description>Membrane Framework allows processing media streams in a very high-level compelling way with Elixir. I&amp;rsquo;ve been wanting to work with it for a while and finally did a small thing with it around my most recent livestream. When we did the live stream we didn&amp;rsquo;t get it all the way to where I wanted. This resolves that.
At the end of the post there is also a video showing you what the thing does.</description>
      <content:encoded><![CDATA[ <p><a href="https://membraneframework.org">Membrane Framework</a> allows processing media streams in a very high-level compelling way with Elixir. I&rsquo;ve been wanting to work with it for a while and finally did a small thing with it around my most recent livestream. When we did the live stream we didn&rsquo;t get it all the way to where I wanted. This resolves that.</p>
<p>At the end of the post there is also a video showing you what the thing does. The idea was to do something better for the visuals. I ended up also fixing an overflowing buffer thing.</p>
<p>The project along with commits and my experimental branch for fixing it without actually having a microphone are available at <a href="https://github.com/lawik/media">the lawik/media repo</a>.</p>
<h2 id="the-first-pipeline-iteration">The first pipeline iteration</h2>
<p>With <a href="https://github.com/lawik/media/tree/33c2b75304861760ed148db1462bb45be91c5fef">this commit</a> I had an <a href="https://github.com/lawik/media/blob/33c2b75304861760ed148db1462bb45be91c5fef/lib/media/stream_to_file.ex">initial working pipeline</a>, this was my baseline going into the live stream.</p>
<p>Let&rsquo;s see it:</p>

  <div class="code  elixir "  data-file="lib/media/stream_to_file.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/media/stream_to_file.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Media.StreamToFile</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">Membrane.Pipeline</span>

  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Membrane</span><span style="color:#f92672">.</span>{<span style="color:#a6e22e">File</span>, <span style="color:#a6e22e">FFmpeg</span>, <span style="color:#a6e22e">MP3.MAD</span>, <span style="color:#a6e22e">MP3.Lame</span>, <span style="color:#a6e22e">PortAudio</span>, <span style="color:#a6e22e">Time</span>}
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Membrane.Element.Tee</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Membrane.Caps.Audio.Raw</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_init(output_directory) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Elixir.File</span><span style="color:#f92672">.</span>mkdir_p!(output_directory)

    children <span style="color:#f92672">=</span> [
      <span style="color:#e6db74">mic_input</span>: <span style="color:#a6e22e">PortAudio.Source</span>,
      <span style="color:#e6db74">converter</span>: %<span style="color:#a6e22e">FFmpeg.SWResample.Converter</span>{
        <span style="color:#e6db74">input_caps</span>: %<span style="color:#a6e22e">Raw</span>{<span style="color:#e6db74">channels</span>: <span style="color:#ae81ff">2</span>, <span style="color:#e6db74">format</span>: <span style="color:#e6db74">:s16le</span>, <span style="color:#e6db74">sample_rate</span>: <span style="color:#ae81ff">48_000</span>},
        <span style="color:#e6db74">output_caps</span>: %<span style="color:#a6e22e">Raw</span>{<span style="color:#e6db74">channels</span>: <span style="color:#ae81ff">2</span>, <span style="color:#e6db74">format</span>: <span style="color:#e6db74">:s32le</span>, <span style="color:#e6db74">sample_rate</span>: <span style="color:#ae81ff">44_100</span>}
      },
      <span style="color:#e6db74">splitter</span>: <span style="color:#a6e22e">Tee.Master</span>,
      <span style="color:#e6db74">encoder</span>: <span style="color:#a6e22e">Lame.Encoder</span>,
      <span style="color:#e6db74">raw_output</span>: %<span style="color:#a6e22e">File.Sink</span>{<span style="color:#e6db74">location</span>: <span style="color:#a6e22e">Path</span><span style="color:#f92672">.</span>join(output_directory, <span style="color:#e6db74">&#34;out.raw&#34;</span>)},
      <span style="color:#e6db74">encoded_output</span>: %<span style="color:#a6e22e">File.Sink</span>{<span style="color:#e6db74">location</span>: <span style="color:#a6e22e">Path</span><span style="color:#f92672">.</span>join(output_directory, <span style="color:#e6db74">&#34;out.mp3&#34;</span>)}
    ]

    links <span style="color:#f92672">=</span> [
      link(<span style="color:#e6db74">:mic_input</span>) <span style="color:#f92672">|&gt;</span> to(<span style="color:#e6db74">:converter</span>) <span style="color:#f92672">|&gt;</span> to(<span style="color:#e6db74">:splitter</span>),
      link(<span style="color:#e6db74">:splitter</span>) <span style="color:#f92672">|&gt;</span> via_out(<span style="color:#e6db74">:master</span>) <span style="color:#f92672">|&gt;</span> to(<span style="color:#e6db74">:raw_output</span>),
      link(<span style="color:#e6db74">:splitter</span>) <span style="color:#f92672">|&gt;</span> via_out(<span style="color:#e6db74">:copy</span>) <span style="color:#f92672">|&gt;</span> to(<span style="color:#e6db74">:encoder</span>) <span style="color:#f92672">|&gt;</span> to(<span style="color:#e6db74">:encoded_output</span>)
    ]

    {{<span style="color:#e6db74">:ok</span>, <span style="color:#e6db74">spec</span>: %<span style="color:#a6e22e">ParentSpec</span>{<span style="color:#e6db74">children</span>: children, <span style="color:#e6db74">links</span>: links}}, %{}}
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>What it do you say? What it do indeed. It will use the cross-platform audio device wrangler portaudio and grab the microphone input as the first stage. It produces a stream of media, so that&rsquo;s a source. It will throw it at a converter based on FFMPEG to convert it into a format that later stage can accept. Then it will duplicate the stream to a master copy and a .. copy .. copy. It will feed the master to a raw file output and the copy? The copy goes into the lame encoder to be turned into an MP3 and then on to a file. The files consume a stream as an endpoint, those are sinks. Source and sink.</p>
<p>So we define the different stages as children and then we connect them using the links. Then we can run this. The Media module shows how that can be done. <code>start_link/1</code> tells me this very much is a <code>GenServer</code> style process and can be handled with your average OTP approaches. I&rsquo;ll add that to the application for automatic running later.</p>

  <div class="code  elixir "  data-file="lib/media.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/media.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Media</span> <span style="color:#66d9ef">do</span>
  <span style="color:#66d9ef">def</span> record_to_file(path) <span style="color:#66d9ef">do</span>
    {<span style="color:#e6db74">:ok</span>, pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Media.StreamToFile</span><span style="color:#f92672">.</span>start_link(path)
    <span style="color:#a6e22e">Media.StreamToFile</span><span style="color:#f92672">.</span>play(pid)
    {<span style="color:#e6db74">:ok</span>, pid}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> stop_to_file(pid) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Media.StreamToFile</span><span style="color:#f92672">.</span>stop_and_terminate(pid)
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>If you call <code>Media.record_to_file/1</code> this it will:</p>
<ul>
<li>Grab your default input audio device</li>
<li>Resample it for encoding</li>
<li>Duplicate it to two streams</li>
<li>Write a raw PCM format file from the stream</li>
<li>Encode an MP3 of the audio</li>
<li>Write the encoded MP3 file</li>
</ul>
<p>Neat thing, turns out MP3s are quite the space saver, 9-10 Mb of audio turned into ~600 Kb if I recall correctly. The <a href="https://underjord.io/membrane-framework-stream-vod.html">stream VOD</a> likely has the exact numbers.</p>
<h2 id="the-second-pipeline">The second pipeline</h2>
<p>Alright. Then I wanted to do more cool stuff, such as acting on the live data as it came in and showing something of it in LiveView. So I ended up ripping most of this out to get a better working example. Including stopping all that file writing as my poor machine was trying to encode video at the time.</p>
<p>So this code was changed into a <code>mic_input</code>, an <code>audiometer</code> and a fake <code>sink</code>. It also introduces <code>handle_notification/4</code> to deal with messages sent by the pipeline as part of the audiometer.</p>

  <div class="code  elixir "  data-file="lib/media/stream_to_file.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/media/stream_to_file.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Media.StreamToFile</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">Membrane.Pipeline</span>

  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Membrane.PortAudio</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Membrane.Audiometer.Peakmeter</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Membrane.Element.Fake</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_init(output_directory) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Elixir.File</span><span style="color:#f92672">.</span>mkdir_p!(output_directory)
    <span style="color:#a6e22e">Process</span><span style="color:#f92672">.</span>register(self(), <span style="color:#e6db74">:default_stream</span>)

    children <span style="color:#f92672">=</span> [
      <span style="color:#e6db74">mic_input</span>: <span style="color:#a6e22e">PortAudio.Source</span>,
      <span style="color:#e6db74">audiometer</span>: %<span style="color:#a6e22e">Peakmeter</span>{<span style="color:#e6db74">interval</span>: <span style="color:#a6e22e">Membrane.Time</span><span style="color:#f92672">.</span>milliseconds(<span style="color:#ae81ff">50</span>)},
      <span style="color:#e6db74">sink</span>: <span style="color:#a6e22e">Fake.Sink.Buffers</span>
    ]

    links <span style="color:#f92672">=</span> [
      link(<span style="color:#e6db74">:mic_input</span>) <span style="color:#f92672">|&gt;</span> to(<span style="color:#e6db74">:audiometer</span>) <span style="color:#f92672">|&gt;</span> to(<span style="color:#e6db74">:sink</span>)
    ]

    {{<span style="color:#e6db74">:ok</span>, <span style="color:#e6db74">spec</span>: %<span style="color:#a6e22e">ParentSpec</span>{<span style="color:#e6db74">children</span>: children, <span style="color:#e6db74">links</span>: links}}, %{}}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_notification({<span style="color:#e6db74">:amplitudes</span>, channels}, _element, _context, state) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">IO</span><span style="color:#f92672">.</span>inspect(channels, <span style="color:#e6db74">label</span>: <span style="color:#e6db74">&#34;amplitude&#34;</span>)
    <span style="color:#a6e22e">Phoenix.PubSub</span><span style="color:#f92672">.</span>broadcast!(<span style="color:#a6e22e">Media.PubSub</span>, <span style="color:#e6db74">&#34;audio&#34;</span>, {<span style="color:#e6db74">:amplitudes</span>, channels})
    {<span style="color:#e6db74">:ok</span>, state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> handle_notification(_any, _element, _context, state) <span style="color:#66d9ef">do</span>
    {<span style="color:#e6db74">:ok</span>, state}
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>The updated module is above and was current as of <a href="https://github.com/lawik/media/tree/f80bf1ad66b1e090fb07044d287829dab6bcfbb6">this commit</a>.</p>
<p>The LiveView is modified from calling <code>mix phx.gen media --live</code> and editing <code>lib/media_web/live/page_live.ex</code> like any good quick hack.</p>

  <div class="code  elixir "  data-file="lib/media_web/live/page_live.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/media_web/live/page_live.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MediaWeb.PageLive</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">MediaWeb</span>, <span style="color:#e6db74">:live_view</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> mount(_params, _session, socket) <span style="color:#66d9ef">do</span>
    <span style="color:#66d9ef">if</span> connected?(socket) <span style="color:#66d9ef">do</span>
      <span style="color:#a6e22e">Phoenix.PubSub</span><span style="color:#f92672">.</span>subscribe(<span style="color:#a6e22e">Media.PubSub</span>, <span style="color:#e6db74">&#34;audio&#34;</span>)
      <span style="color:#a6e22e">Media</span><span style="color:#f92672">.</span>ensure_playing()
    <span style="color:#66d9ef">end</span>

    {<span style="color:#e6db74">:ok</span>, assign(socket, <span style="color:#e6db74">channels</span>: [])}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> handle_info({<span style="color:#e6db74">:amplitudes</span>, channels}, socket) <span style="color:#66d9ef">do</span>
    channels <span style="color:#f92672">=</span>
      <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>map(channels, <span style="color:#66d9ef">fn</span> negative_db <span style="color:#f92672">-&gt;</span>
        <span style="color:#66d9ef">case</span> negative_db <span style="color:#66d9ef">do</span>
          <span style="color:#e6db74">:infinity</span> <span style="color:#f92672">-&gt;</span>
            <span style="color:#ae81ff">0</span>

          num <span style="color:#f92672">-&gt;</span>
            <span style="color:#ae81ff">100</span> <span style="color:#f92672">-</span> round(num <span style="color:#f92672">*</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1.0</span>)
        <span style="color:#66d9ef">end</span>
      <span style="color:#66d9ef">end</span>)

    {<span style="color:#e6db74">:noreply</span>, assign(socket, <span style="color:#e6db74">channels</span>: channels)}
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>It subscribes to the events we send in the pipeline. It mangles the numbers and it updates the state. I love a good <code>handle_info/2</code>, screw events, who needs em. Users shouldn&rsquo;t interact with my stuff, my stuff interacts with users! I kid, but my most interesting uses of LiveView rarely require the user to trigger the behavior, stuff just goes. I find it super satisfying.</p>
<p>The template side is very dense and simplistic, the video at the end covers my slight style tweaking.</p>

  <div class="code  leex "  data-file="lib/media_web/live/page_live.html.leex" >
    <div class="meta">
      <span class="language">leex</span>
      <span class="filename">lib/media_web/live/page_live.html.leex</span>
    </div>
    <pre tabindex="0"><code class="language-leex" data-lang="leex">    &lt;%= for amplitude &lt;- @channels do %&gt;
    &lt;div class=&#34;amp-bar&#34; style=&#34;width: &lt;%= amplitude %&gt;%;&#34;&gt;
    &lt;/div&gt;
    &lt;% end %&gt;</code></pre>
  </div>

<p>That&rsquo;s it essentially. There is some additional plumbing to just make sure it is started and running, so clone and run the project to try it.</p>
<p>A brief video of how it looks and how I solve problems under unexpected circumstances is available below, and also <a href="https://www.youtube.com/watch?v=573t5ZHReiw">on my YouTube channel</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
</script>
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-quick-lab-membran-2021-04-26.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Membrane Framework, trying it out</title>
      <link>https://underjord.io/membrane-framework-stream-vod.html</link>
      <pubDate>Fri, 23 Apr 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/membrane-framework-stream-vod.html</guid>
      <description>Another live stream. I&amp;rsquo;ve been very curious to try Membrane and I finally got to screw around with it some. Thanks to Marcel Fahle for being a patient and helpful guest.
The post about making a Twitch clone that was referenced is this one.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  Sorry, your browser doesn&#39;t support embedded videos.</description>
      <content:encoded><![CDATA[ <p>Another live stream. I&rsquo;ve been very curious to try <a href="https://membraneframework.org">Membrane</a> and I finally got to screw around with it some. Thanks to Marcel Fahle for being a patient and helpful guest.</p>
<p>The post about making a Twitch clone that was referenced is <a href="https://blog.swmansion.com/live-video-streaming-in-elixir-made-simple-with-membrane-fc5b2083982d">this one</a>.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
</script>
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-04-23.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Video - Livebook, trying it out</title>
      <link>https://underjord.io/trying-out-livebook-stream-vod.html</link>
      <pubDate>Mon, 19 Apr 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/trying-out-livebook-stream-vod.html</guid>
      <description>Last friday I did my second live stream. A lot of nice people stopped by and I spent the time showing and getting more familiar with the newly released [Livebook](https://dashbit.co/blog/announcing-livebook). Wasn&#39;t planning on providing a VOD (Video-On-Demand) of it but a bunch of people asked so I did. But this means it is only available in 720p. I&#39;ll be experimenting with this going forward. Video below.
 function switch_video(element) { var src = element.</description>
      <content:encoded><![CDATA[ <p>Last friday I did my second live stream. A lot of nice people stopped by and I spent the time showing and getting more familiar with the newly released [Livebook](https://dashbit.co/blog/announcing-livebook). Wasn't planning on providing a VOD (Video-On-Demand) of it but a bunch of people asked so I did. But this means it is only available in 720p. I'll be experimenting with this going forward. Video below.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
</script>
<video class="fancy" controls="" poster="">
    <source src="https://underjord-video.eu-central-1.linodeobjects.com/underjord-livestream-archive-2021-04-16.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Lumen - Statically compiled Erlang for x86</title>
      <link>https://underjord.io/lumen-statically-compiled-erlang-for-x86.html</link>
      <pubDate>Thu, 08 Apr 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/lumen-statically-compiled-erlang-for-x86.html</guid>
      <description>The Lumen Project is an ambitious compiler development effort to create a complimentary set of compilers and tools that allow developers to get the power of the Erlang VM, The BEAM, in places it does not traditionally fit. Such as the browser. Currently the project is at an early released stage as covered in this talk. It does not yet implement all of Erlang OTP and as such won&amp;rsquo;t handle most Erlang/Elixir you could throw at it.</description>
      <content:encoded><![CDATA[ <p>The <a href="https://getfirefly.org/">Lumen Project</a> is an ambitious compiler development effort to create a complimentary set of compilers and tools that allow developers to get the power of the Erlang VM, The BEAM, in places it does not traditionally fit. Such as the browser. Currently the project is at an early released stage as <a href="https://www.youtube.com/watch?v=-381kMYvCA8">covered in this talk</a>. It does not yet implement all of Erlang OTP and as such won&rsquo;t handle most Erlang/Elixir you could throw at it. I want to show something that it does do. And while the project is a complicated compiler project you do not need to know that stuff to try it out. This should be achievable for most developers and to ensure that I wasn&rsquo;t talking out of my rear I had my assistant developer, Elin Olsson, follow the instructions without my input and it worked out well.</p>
<p>So while the big goal is the WebAssembly (WASM) target the compiler is built on top of LLVM and could also be great for making static binaries for x86 and friends. There are still some missing facilities in WASM for handling the kind of light-weight processes and scheduling we enjoy in Erlang land. The team is working with the WASM crowd to make those things happen. While waiting we get an x86 target to play with.</p>
<p>So, to get started we follow <a href="https://github.com/lumen/lumen">the instructions at the Lumen repo</a>. This post will get outdated. The project README is more likely to stay up to date. We&rsquo;ll cover the steps here anyway.</p>
<p>The basic requirements are the following:</p>
<ul>
<li>CMake</li>
<li>Ninja (recommended)</li>
<li>CCache (recommended)</li>
</ul>
<p>Installing these on MacOS is done like so (assuming homebrew is installed):</p>

  <div class="code  shell "  data-file="~" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">brew install cmake ninja ccache</code></pre></div>
  </div>

<p>Now, let&rsquo;s get the Lumen repo from Github and <code>cd</code> into it.</p>

  <div class="code  shell "  data-file="~" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">git clone git@github.com:lumen/lumen.git
cd lumen</code></pre></div>
  </div>

<p>First, we need a modified version of LLVM. Thankfully a precompiled version is <a href="https://github.com/lumen/lumen#installing-prebuilt-distributions-recommended">provided for Linux and x86 Mac</a>. Below we provide the instructions for MacOS, for Linux, use the previous link.
The instructions reference <code>$XDG_DATA_HOME</code> as an environment variable, it is recommended to export XDG variables in general, but if you have not, just replace the usages of <code>$XDG_DATA_HOME</code> below with <code>$HOME/.local/share</code>, which is the usual default for this XDG variable.</p>

  <div class="code  shell "  data-file="~/lumen" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~/lumen</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mkdir -p $XDG_DATA_HOME/llvm/
cd $XDG_DATA_HOME/llvm/
wget https://github.com/lumen/llvm-project/releases/download/lumen-12.0.0-dev_2020-10-22/clang+llvm-12.0.0-x86_64-apple-darwin19.5.0.tar.gz
tar -xzf clang+llvm-12.0.0-x86_64-apple-darwin19.5.0.tar.gz
rm clang+llvm-12.0.0-x86_64-apple-darwin19.5.0.tar.gz
mv clang+llvm-12.0.0-x86_64-apple-darwin19.5.0 lumen
cd -</code></pre></div>
  </div>

<p>Lumen and related tooling such as Eir is built with Rust. So you need <code>rustup</code> which is a Rust installer. Lumen builds off of the Rust nightly due to some features, such as WebAssembly, requiring nightly features. Then we use the Rust package manager <code>cargo</code> to install some additional requirements.</p>

  <div class="code  shell "  data-file="~/lumen" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~/lumen</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#75715e"># Install rustup by running this script and follow the onscreen instructions:</span>
curl --proto <span style="color:#e6db74">&#39;=https&#39;</span> --tlsv1.2 -sSf https://sh.rustup.rs | sh

<span style="color:#75715e"># Set default Rust version to match Lumen&#39;s CI:</span>
rustup default nightly-2021-01-29

<span style="color:#75715e"># Install the wasm32 targets for the toolchain</span>
rustup target add wasm32-unknown-unknown --toolchain nightly-2021-01-29

<span style="color:#75715e"># Install cargo-make to run build tasks in project</span>
cargo install cargo-make</code></pre></div>
  </div>

<p>Then we need to build our compiler. Replace <code>$XDG_DATA_HOME</code> with <code>$HOME/.local/share</code> as above if needed.</p>

  <div class="code  shell "  data-file="~/lumen" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~/lumen</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">LLVM_PREFIX<span style="color:#f92672">=</span>$XDG_DATA_HOME/llvm/lumen cargo make</code></pre></div>
  </div>

<p>At this stage we encountered a problem. The build failed with a rustup error. This was solved by hard coding the toolchain version in <code>Makefile.toml</code>, line 76.</p>

  <div class="code  shell "  data-file="~/lumen/Makefile.toml" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~/lumen/Makefile.toml</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">CARGO_MAKE_TOOLCHAIN <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;nightly-2021-01-29&#34;</span></code></pre></div>
  </div>

<p>With this fix the build passed, and we have a compiler. Let&rsquo;s make sure it runs at all by calling its help (from project root).</p>

  <div class="code  shell "  data-file="~/lumen" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~/lumen</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">bin/lumen --help</code></pre></div>
  </div>

<p>Let&rsquo;s create some of our own code, to start we just set up separate directory:</p>

  <div class="code  shell "  data-file="~/lumen" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~/lumen</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">cd ..
mkdir our_code
cd our_code</code></pre></div>
  </div>

<p>To actually try things out we need to write some code. The process for Elixir code is less convenient at the moment so we&rsquo;ll go with an erlang example. This is our hello world in Erlang:</p>

  <div class="code  erlang "  data-file="~/our_code/hello.erl" >
    <div class="meta">
      <span class="language">erlang</span>
      <span class="filename">~/our_code/hello.erl</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-erlang" data-lang="erlang">-module(init).
-export([start<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>]).
-import(erlang, [display<span style="color:#f92672">/</span><span style="color:#ae81ff">1</span>]).
-spec start() <span style="color:#f92672">-&gt;</span> ok | error.
<span style="color:#a6e22e">start</span>() <span style="color:#f92672">-&gt;</span>
   display(<span style="color:#e6db74">&#34;Hello World!&#34;</span>).</code></pre></div>
  </div>

<p>So now we can compile that:</p>

  <div class="code  shell "  data-file="~/our_code" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~/our_code</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">../lumen/bin/lumen compile --output-dir . hello.erl</code></pre></div>
  </div>

<p>That generates a binary for us called <code>hello.out</code>. We can run it. Usually you have to set the executable bit the first time:</p>

  <div class="code  shell "  data-file="~/our_code" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~/our_code</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">chmod +x hello.out
./hello.out</code></pre></div>
  </div>

<p>To make things a bit more interesting we can make it do multiple things. Lets spawn a couple of processes that will output things as well as output from the initial process.</p>

  <div class="code  erlang "  data-file="~/our_code/spawn.erl" >
    <div class="meta">
      <span class="language">erlang</span>
      <span class="filename">~/our_code/spawn.erl</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-erlang" data-lang="erlang">-module(init).

-export([start<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>]).

-import(erlang, [display<span style="color:#f92672">/</span><span style="color:#ae81ff">1</span>]).

-spec start() <span style="color:#f92672">-&gt;</span> ok | error.
<span style="color:#a6e22e">start</span>() <span style="color:#f92672">-&gt;</span>
  spawn(<span style="color:#66d9ef">fun</span>() <span style="color:#f92672">-&gt;</span> server(<span style="color:#e6db74">&#34;Foo&#34;</span>) <span style="color:#66d9ef">end</span>),
  spawn(<span style="color:#66d9ef">fun</span>() <span style="color:#f92672">-&gt;</span> server(<span style="color:#e6db74">&#34;Bar&#34;</span>) <span style="color:#66d9ef">end</span>).

<span style="color:#a6e22e">server</span>(Message) <span style="color:#f92672">-&gt;</span>
  display(Message).</code></pre></div>
  </div>

<p>A single static binary running from Erlang code, compiled via Rust. <a href="https://github.com/bake-bake-bake/bakeware">Bakeware</a> achieves something similar, as do certain options in <a href="https://github.com/bitwalker/distillery">Distillery</a> I believe, but that ships the entire Erlang runtime and has a decompression step on the first run due to this. With Lumen the runtime is compiled in. At later points, due to removing hot code updates, there will be optimizations such as dead code elimination and simply not shipping the entirety of Erlang and OTP with each binary. I think that&rsquo;s quite a compelling concept. <em>update: this is actually already a thing for NIFs, they aren&rsquo;t linked in if they aren&rsquo;t used, I really should run these things by the Lumen team :)</em></p>
<p>The tradeoffs with the BEAM being a VM are generally size and startup time. And it gives us immense flexibility with absurd capabilities like hot code updates. The downside is only for some very particular use-cases where size and startup time matter more. The web is one of these but far from the only one. I would expect Lumen binaries, even when the project has matured a bit, to still be larger than something like Rust and Go binaries but I honestly don&rsquo;t know. I would also expect them to start a lot faster than your average BEAM-based release. So I&rsquo;m keen on using Lumen for two things in the future. Elixir-based CLI and web frontend code. With WASM the world does open up quite wide with WASM poised to being a general target for anything that needs to be sandboxable and fast. It is being considered as a container replacement, edge compute, serverless functions, embedded, so many things.</p>
<p>So that&rsquo;s a preview of the Lumen compiler. I tried to do some <code>timer:sleep/1</code> stuff but found out that wasn&rsquo;t implemented yet. If you are curious to see what you do have I think <a href="https://github.com/lumen/lumen/tree/develop/native_implemented/otp/src">this gave a decent view</a> if you know your Erlang module names. So going into the <code>timer</code> folder there I can see what functions do exist and there isn&rsquo;t presently a <code>sleep_1.rs</code>.</p>
<p>So this example outputs x86 binaries. The next step is WASM and while I&rsquo;ve been out of the loop for a bit a bird just told me that there&rsquo;s a big PR rolling in that should get us to WASM output or thereabouts. If you are keen on helping Lumen move forward, check the issues on <a href="https://github.com/lumen/lumen">the lumen repo</a> and you can help implement more of Erlang/OTP. Maybe get me that sleep function? You can find <a href="https://github.com/lumen/lumen/issues?q=is%3Aopen+is%3Aissue+label%3Aruntime%3ABIFs">the runtime BIFs label in the issues</a>.</p>
<p>Big props to Elin for helping me work through this. She deserves a 75% credit on this blog post. All the annoying work was done by her and I&rsquo;m very happy to know that you can be unfamiliar with the project and get from start to finish. Also thanks to Luke and Brian for correcting me on some important details.</p>
<p>If you have thoughts or question, don&rsquo;t hesitate to let me know. I tend to both read and respond via email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> and on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>A Telegram Bot in Elixir feat. LiveView</title>
      <link>https://underjord.io/a-telegram-bot-in-elixir.html</link>
      <pubDate>Wed, 24 Feb 2021 07:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/a-telegram-bot-in-elixir.html</guid>
      <description>I asked my network on Twitter about noting ideas quickly and got a lot of good responses. One mentioned saving them in Telegram. I don&amp;rsquo;t think I want to do specifically that but I do want a minimum friction way of noting ideas for later review and refinement. And sending them to a Telegram chat would be quite nice. So I started on the path of something like a note-taking system using Telegram for ingesting quick notes.</description>
      <content:encoded><![CDATA[ <p>I <a href="https://twitter.com/lawik/status/1357226010215911425">asked my network on Twitter</a> about noting ideas quickly and got a lot of good responses. One mentioned saving them in Telegram. I don&rsquo;t think I want to do specifically that but I do want a minimum friction way of noting ideas for later review and refinement. And sending them to a Telegram chat would be quite nice. So I started on the path of something like a note-taking system using Telegram for ingesting quick notes. And I want to share the satisfaction I felt with seeing the near real-time way that it works.</p>
<p>I&rsquo;ll show some of the process I went through so you can repeat it for your own needs but the repo is open if you want to see where I&rsquo;ve taken it since. You can find it as <a href="https://github.com/lawik/noted">lawik/noted</a> on GitHub. This guide recreates a simpler approach based off of what I learnt setting that up.</p>
<h2 id="setting-up-your-project">Setting up your project</h2>
<p>I&rsquo;m using Elixir and Phoenix with LiveView. You need to have the Phoenix project generator installed to follow along, you can follow the Phoenix installation instructions to get it.</p>

  <div class="code  shell "  data-file="/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix phx.new --live noted</code></pre></div>
  </div>

<p>It should create your project, you can certainly name it something different than <code>noted</code>. I left Ecto in because you&rsquo;ll likely want it at some point though I won&rsquo;t use it in this post.</p>
<h2 id="some-initial-setup">Some initial setup</h2>
<p>In <code>lib/noted/application.ex</code> I comment out the <code>Noted.Repo</code> because we&rsquo;re not using the DB.</p>
<p>In <code>mix.exs</code> for deps I add:</p>

  <div class="code  elixir "  data-file="mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">    <span style="color:#f92672">..</span>
      {<span style="color:#e6db74">:telegram</span>, <span style="color:#e6db74">git</span>: <span style="color:#e6db74">&#34;https://github.com/visciang/telegram.git&#34;</span>, <span style="color:#e6db74">tag</span>: <span style="color:#e6db74">&#34;0.7.0&#34;</span>},
      <span style="color:#75715e"># Gun mismatch with telegram and cowboy</span>
      {<span style="color:#e6db74">:cowlib</span>, <span style="color:#e6db74">&#34;~&gt; 2.7&#34;</span>, <span style="color:#e6db74">override</span>: <span style="color:#66d9ef">true</span>}
    <span style="color:#f92672">..</span></code></pre></div>
  </div>

<p>The cowlib thing has to do with some weirdness in the telegram library deps. But adding this should allow you to do <code>mix deps.get</code> and have it work.</p>
<h2 id="get-yourself-a-telegram-bot">Get yourself a Telegram bot</h2>
<p>So get situated with a Telegram account and add <a href="https://t.me/BotFather">The Botfather (Telegram link)</a> as someone to talk to and that&rsquo;s Telegrams bot for creating bots. Very meta. Pretty convenient.</p>
<p>You write <code>/newbot</code> to him and he&rsquo;ll guide you through the rest. You&rsquo;ll receive a secret that you should squirrel away and we&rsquo;ll use it as a an API key for running our bot.</p>
<p>You&rsquo;re going to want a convenient client to test things as well. I&rsquo;m using the MacOS desktop client on this machine, it is nice and native.</p>
<h2 id="minimum-viable-bot">Minimum Viable Bot</h2>
<p>It needs to be self-aware. So let&rsquo;s make that happen. To protect the bot token I put it in a file in my home dir called <code>.mybotcredentials</code> or something similar. This file is just:</p>

  <div class="code  shell "  data-file="~/.mybotcredentials" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">~/.mybotcredentials</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">export TELEGRAM_BOT_SECRET<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;my_secret_goes_here&#34;</span></code></pre></div>
  </div>

<p>And before running the server I do: <code>source ~/.mybotcredentials</code></p>
<p>This prevents me from accidentally committing the damned things.</p>
<p>Okay, time to create this bot. Create a file in the project named <code>lib/noted/bot.ex</code>. In this file we do this:</p>

  <div class="code  elixir "  data-file="lib/noted/bot.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/noted/bot.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Noted.Bot</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">GenServer</span>
  <span style="color:#f92672">require</span> <span style="color:#a6e22e">Logger</span>

  <span style="color:#66d9ef">def</span> start_link(opts) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>start_link(__MODULE__, opts, opts)
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#a6e22e">GenServer</span>
  <span style="color:#66d9ef">def</span> init(opts) <span style="color:#66d9ef">do</span>
    {key, _opts} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>pop!(opts, <span style="color:#e6db74">:bot_key</span>)

    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Telegram.Api</span><span style="color:#f92672">.</span>request(key, <span style="color:#e6db74">&#34;getMe&#34;</span>) <span style="color:#66d9ef">do</span>
      {<span style="color:#e6db74">:ok</span>, me} <span style="color:#f92672">-&gt;</span>
        <span style="color:#a6e22e">Logger</span><span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Bot successfully self-identified: </span><span style="color:#e6db74">#{</span>me[<span style="color:#e6db74">&#34;username&#34;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)

        state <span style="color:#f92672">=</span> %{
          <span style="color:#e6db74">bot_key</span>: key,
          <span style="color:#e6db74">me</span>: me,
          <span style="color:#e6db74">last_seen</span>: <span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>
        }

        {<span style="color:#e6db74">:ok</span>, state}

      error <span style="color:#f92672">-&gt;</span>
        <span style="color:#a6e22e">Logger</span><span style="color:#f92672">.</span>error(<span style="color:#e6db74">&#34;Bot failed to self-identify: </span><span style="color:#e6db74">#{</span>inspect(error)<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
        <span style="color:#e6db74">:error</span>
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>This is a GenServer and we want to add it to run when our application starts. So below your Noted.Endpoint, inside the list of children in <code>lib/noted/application.ex</code> add this:</p>

  <div class="code  elixir "  data-file="lib/noted/application.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/noted/application.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">..</span>
{<span style="color:#a6e22e">Noted.Bot</span>, <span style="color:#e6db74">bot_key</span>: <span style="color:#a6e22e">System</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">&#34;TELEGRAM_BOT_SECRET&#34;</span>)}
<span style="color:#f92672">..</span></code></pre></div>
  </div>

<p>Now you can make sure you&rsquo;ve run <code>mix deps.get</code> and then run <code>mix phx.server</code> to try it out. Check your logs, the happy message should be there.</p>
<h2 id="set-yourself-up-to-receive-updates">Set yourself up to receive updates</h2>
<p>So it still doesn&rsquo;t really do anything. We will be using long polling which is a neat way of getting updates from a Telegram bot without publishing the server on the web on some real domain and in some systems it might be a bit of a pain. In Elixir, on a GenServer it is comparatively trivial. At least if you know your GenServers.</p>
<p>Just after we set up our state and before we return <code>{:ok, state}</code> in the bot we need to add a call to <code>next_loop()</code>. So the your <code>init/1</code> looks like this:</p>

  <div class="code  elixir "  data-file="lib/noted/bot.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/noted/bot.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">..</span>
  <span style="color:#66d9ef">def</span> init(opts) <span style="color:#66d9ef">do</span>
    {key, _opts} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>pop!(opts, <span style="color:#e6db74">:bot_key</span>)

    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Telegram.Api</span><span style="color:#f92672">.</span>request(key, <span style="color:#e6db74">&#34;getMe&#34;</span>) <span style="color:#66d9ef">do</span>
      {<span style="color:#e6db74">:ok</span>, me} <span style="color:#f92672">-&gt;</span>
        <span style="color:#a6e22e">Logger</span><span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Bot successfully self-identified: </span><span style="color:#e6db74">#{</span>me[<span style="color:#e6db74">&#34;username&#34;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)

        state <span style="color:#f92672">=</span> %{
          <span style="color:#e6db74">bot_key</span>: key,
          <span style="color:#e6db74">me</span>: me,
          <span style="color:#e6db74">last_seen</span>: <span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>
        }
        next_loop()

        {<span style="color:#e6db74">:ok</span>, state}

      error <span style="color:#f92672">-&gt;</span>
        <span style="color:#a6e22e">Logger</span><span style="color:#f92672">.</span>error(<span style="color:#e6db74">&#34;Bot failed to self-identify: </span><span style="color:#e6db74">#{</span>inspect(error)<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
        <span style="color:#e6db74">:error</span>
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span>
<span style="color:#f92672">..</span></code></pre></div>
  </div>

<p>Now to add the rest. This GenServer will send itself a message saying &ldquo;ey, check for updates&rdquo; and then act on that message until the activity is done and then call <code>next_loop/0</code> again to trigger another message. We&rsquo;ll start with acting on the message with a <code>handle_info/2</code> callback. In your bot GenServer module:</p>

  <div class="code  elixir "  data-file="lib/noted/bot.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/noted/bot.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">..</span>
  <span style="color:#a6e22e">@impl</span> <span style="color:#a6e22e">GenServer</span>
  <span style="color:#66d9ef">def</span> handle_info(<span style="color:#e6db74">:check</span>, %{<span style="color:#e6db74">bot_key</span>: key, <span style="color:#e6db74">last_seen</span>: last_seen} <span style="color:#f92672">=</span> state) <span style="color:#66d9ef">do</span>
    state <span style="color:#f92672">=</span>
      key
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Telegram.Api</span><span style="color:#f92672">.</span>request(<span style="color:#e6db74">&#34;getUpdates&#34;</span>, <span style="color:#e6db74">offset</span>: last_seen <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, <span style="color:#e6db74">timeout</span>: <span style="color:#ae81ff">30</span>)
      <span style="color:#f92672">|&gt;</span> <span style="color:#66d9ef">case</span> <span style="color:#66d9ef">do</span>
        <span style="color:#75715e"># Empty, typically a timeout. State returned unchanged.</span>
        {<span style="color:#e6db74">:ok</span>, []} <span style="color:#f92672">-&gt;</span>
          state

        <span style="color:#75715e"># A response with content, exciting!</span>
        {<span style="color:#e6db74">:ok</span>, updates} <span style="color:#f92672">-&gt;</span>
          <span style="color:#75715e"># Process our updates and return the latest update ID</span>
          last_seen <span style="color:#f92672">=</span> handle_updates(updates, last_seen)

          <span style="color:#75715e"># Update the last_seen state so we only get new updates on the</span>
          <span style="color:#75715e"># next check</span>
          %{state <span style="color:#f92672">|</span> <span style="color:#e6db74">last_seen</span>: last_seen}
      <span style="color:#66d9ef">end</span>

    <span style="color:#75715e"># Re-trigger the looping behavior</span>
    next_loop()
    {<span style="color:#e6db74">:noreply</span>, state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> handle_updates(updates, last_seen) <span style="color:#66d9ef">do</span>
    updates
    <span style="color:#75715e"># Process our updates</span>
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>map(<span style="color:#66d9ef">fn</span> update <span style="color:#f92672">-&gt;</span>
      <span style="color:#a6e22e">Logger</span><span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Update received: </span><span style="color:#e6db74">#{</span>inspect(update)<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
      <span style="color:#75715e"># Offload the updates to whoever they may concern</span>
      broadcast(update)

      <span style="color:#75715e"># Return the update ID so we can boil it down to a new last_seen</span>
      update[<span style="color:#e6db74">&#34;update_id&#34;</span>]
    <span style="color:#66d9ef">end</span>)
    <span style="color:#75715e"># Get the highest seen id from the new updates or fall back to last_seen</span>
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>max(<span style="color:#66d9ef">fn</span> <span style="color:#f92672">-&gt;</span> last_seen <span style="color:#66d9ef">end</span>)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> broadcast(update) <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># Send each update to a topic for others to listen to.</span>
    <span style="color:#a6e22e">Phoenix.PubSub</span><span style="color:#f92672">.</span>broadcast!(<span style="color:#a6e22e">Noted.PubSub</span>, <span style="color:#e6db74">&#34;bot_update&#34;</span>, {<span style="color:#e6db74">:update</span>, update})
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> next_loop <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Process</span><span style="color:#f92672">.</span>send_after(self(), <span style="color:#e6db74">:check</span>, <span style="color:#ae81ff">0</span>)
  <span style="color:#66d9ef">end</span>
<span style="color:#f92672">..</span></code></pre></div>
  </div>

<p>Try it out, it should log messages as they are sent to your bot.</p>
<h2 id="getting-real-time-with-liveview">Getting real-time with LiveView</h2>
<p>We can repurpose the existing LiveView that the Phoenix generator gives us. So just open <code>lib/noted_web/live/page_live.ex</code> and its sibling the template <code>lib/noted_web/live/page_live.html.leex</code>. In the <code>page_live.ex</code> file we change it to this:</p>

  <div class="code  elixir "  data-file="lib/noted_web/live/page_live.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/noted_web/live/page_live.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">NotedWeb.PageLive</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">NotedWeb</span>, <span style="color:#e6db74">:live_view</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> mount(_params, _session, socket) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Phoenix.PubSub</span><span style="color:#f92672">.</span>subscribe(<span style="color:#a6e22e">Noted.PubSub</span>, <span style="color:#e6db74">&#34;bot_update&#34;</span>)
    {<span style="color:#e6db74">:ok</span>, assign(socket, <span style="color:#e6db74">messages</span>: [])}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_info({<span style="color:#e6db74">:update</span>, update}, socket) <span style="color:#66d9ef">do</span>
    {<span style="color:#e6db74">:noreply</span>, assign(socket, <span style="color:#e6db74">messages</span>: [to_message(update) <span style="color:#f92672">|</span> socket<span style="color:#f92672">.</span>assigns<span style="color:#f92672">.</span>messages])}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> to_message(%{<span style="color:#e6db74">&#34;message&#34;</span> <span style="color:#f92672">=&gt;</span> message} <span style="color:#f92672">=</span> _update) <span style="color:#66d9ef">do</span>
    firstname <span style="color:#f92672">=</span> get_in(message, [<span style="color:#e6db74">&#34;from&#34;</span>, <span style="color:#e6db74">&#34;first_name&#34;</span>])
    lastname <span style="color:#f92672">=</span> get_in(message, [<span style="color:#e6db74">&#34;from&#34;</span>, <span style="color:#e6db74">&#34;last_name&#34;</span>])
    username <span style="color:#f92672">=</span> get_in(message, [<span style="color:#e6db74">&#34;from&#34;</span>, <span style="color:#e6db74">&#34;username&#34;</span>])

    from <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">case</span> {firstname, lastname, username} <span style="color:#66d9ef">do</span>
        {<span style="color:#66d9ef">nil</span>, _, username} <span style="color:#f92672">-&gt;</span> username
        {firstname, <span style="color:#66d9ef">nil</span>, _} <span style="color:#f92672">-&gt;</span> firstname
        {firstname, lastname, _} <span style="color:#f92672">-&gt;</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">#{</span>firstname<span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">#{</span>lastname<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
      <span style="color:#66d9ef">end</span>

    text <span style="color:#f92672">=</span> get_in(message, [<span style="color:#e6db74">&#34;text&#34;</span>])
    %{<span style="color:#e6db74">from</span>: from, <span style="color:#e6db74">text</span>: text}
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>

<p>And we change the <code>page_live.html.leex</code> template file to:</p>

  <div class="code  html "  data-file="lib/noted_web/live/page_live.html.leex" >
    <div class="meta">
      <span class="language">html</span>
      <span class="filename">lib/noted_web/live/page_live.html.leex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html">&lt;<span style="color:#f92672">section</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;phx-hero&#34;</span>&gt;
  &lt;<span style="color:#f92672">h1</span>&gt;<span style="color:#960050;background-color:#1e0010">&lt;</span>%= gettext &#34;Welcome to my Bot!&#34; %&gt;&lt;/<span style="color:#f92672">h1</span>&gt;

  <span style="color:#960050;background-color:#1e0010">&lt;</span>%= for message &lt;<span style="color:#f92672">-</span> <span style="color:#a6e22e">Enum</span><span style="color:#960050;background-color:#1e0010">.</span><span style="color:#a6e22e">reverse</span><span style="color:#960050;background-color:#1e0010">(@</span><span style="color:#a6e22e">messages</span><span style="color:#960050;background-color:#1e0010">)</span> <span style="color:#a6e22e">do</span> <span style="color:#960050;background-color:#1e0010">%</span>&gt;
  &lt;<span style="color:#f92672">p</span>&gt;&lt;<span style="color:#f92672">strong</span>&gt;<span style="color:#960050;background-color:#1e0010">&lt;</span>%= message.from %&gt;: &lt;/<span style="color:#f92672">strong</span>&gt;<span style="color:#960050;background-color:#1e0010">&lt;</span>%= message.text %&gt;&lt;/<span style="color:#f92672">p</span>&gt;
  <span style="color:#960050;background-color:#1e0010">&lt;</span>% end %&gt;
&lt;/<span style="color:#f92672">section</span>&gt;</code></pre></div>
  </div>

<p>Run this and you should see any message to type in the Telegram bot show up. I find it very gratifying just how instantaneous it can be.</p>
<h2 id="in-closing">In closing</h2>
<p>This is just a start and in the <a href="https://github.com/lawik/noted">noted repo</a> you will find some of what I&rsquo;ve done to push it further, structure the bot a little bit more. Add some responses. You can just try it out by cloning and setting it up.</p>
<p>I implemented some authentication. Currently this means that notes are separated by user. It also means that Noted allows new users by default if they find your bot :D</p>
<p>I implemented the database backing to save notes and tags. I added a bunch more LiveView code. I separated the polling for getUpdates from the handling of the updates a bit more and added a Supervisor to the pairing of GenServers.</p>
<p>Currently on my todo-list is to receive files and media in a nice way and integrate those with the markdown support. If you want practice with Tailwind CSS I&rsquo;d love for someone to make it look better.</p>
<p>But this particular project aside I find the realtime nature of chat bots very satisfying. Telegram has a well-working API, nice native clients and a hilariously large feature set. And with how fast you can get this up and running when it isn&rsquo;t your first time I really recommend trying it for things. Heck, you could use it for sending you notices about your app catching fire or occasional status updates from your system. There are so many fun options.</p>
<p>Slightly timely update, especially if you are curious about LiveView or want more of me. Me and a wild gang of Elixir community folks just put our new podcast out there. It is called <a href="https://beamrad.io">BEAM Radio</a> and you find it at <a href="https://beamrad.io">beamrad.io</a>.</p>
<p>I hope you&rsquo;ve enjoyed the post. Let me know if this is something you would like to see more of as I have quite a few things I could go deeper into from this project. You can let me know via email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter where I&rsquo;m <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Small update, full content RSS</title>
      <link>https://underjord.io/small-update-full-content-rss.html</link>
      <pubDate>Mon, 22 Feb 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/small-update-full-content-rss.html</guid>
      <description>This is a small update to let you know that the RSS feed contains full content now.
It also lets me verify that the feed has updated correctly and works fine. If it comes out weird in your feed reader, please let me know.
If you aren&amp;rsquo;t familiar with RSS. Consider it the open standard that drives podcasting and an exceptionally useful way of reading blogs, news, webcomics in a more intentional way via a dedicated, nice, newsreader application or web UI.</description>
      <content:encoded><![CDATA[ <p>This is a small update to let you know that the RSS feed contains full content now.</p>
<p>It also lets me verify that the feed has updated correctly and works fine. If it comes out weird in your feed reader, please let me know.</p>
<p>If you aren&rsquo;t familiar with RSS. Consider it the open standard that drives podcasting and an exceptionally useful way of reading blogs, news, webcomics in a more intentional way via a dedicated, nice, newsreader application or web UI. It lets you avoid attention-grabbing or remembering to check but it doesn&rsquo;t have to go &ldquo;PING!&rdquo; or anything. More things should be like RSS.</p>
<p>I blame Martin Gausby for reminding me that full articles in RSS is a better experience than needing to click through. So in the interest of pampering my audience with the best text experiences possible. There you go ;)</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Post-mortem: 10 years in the vertical - Part 3</title>
      <link>https://underjord.io/10-years-in-the-vertical-part-3.html</link>
      <pubDate>Fri, 12 Feb 2021 07:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/10-years-in-the-vertical-part-3.html</guid>
      <description>Content warning: Contains poor technical decisions, inexperience and stories of a developer just starting out and up to roughly present day. Be kind to who I was, he gets enough shit from me.
This continues our dive from Part 2. Part 1 can be found here.
The better system 2.0 The big embarrassment of the system we&amp;rsquo;d previously built was performance, it wasn&amp;rsquo;t scaling with a growing customer base and was slowing down.</description>
      <content:encoded><![CDATA[ <p><strong>Content warning:</strong> Contains poor technical decisions, inexperience and stories of a developer just starting out and up to roughly present day. Be kind to who I was, he gets enough shit from me.</p>
<p>This continues our dive from <a href="/10-years-in-the-vertical-part-2.html">Part 2</a>. Part 1 can be found <a href="/10-years-in-the-vertical-part-1.html">here</a>.</p>
<h2 id="the-better-system-20">The better system 2.0</h2>
<p>The big embarrassment of the system we&rsquo;d previously built was performance, it wasn&rsquo;t scaling with a growing customer base and was slowing down. When discussions started for a proper rewrite of the system &ldquo;scaling&rdquo; was probably the central word. There was another parallel product in the company that was also experiencing performance issues. This put performance front and center but since it was attributed to growth the focus was more on scalability than specifically efficient performance.</p>
<p>We were tasked with finding a plan. This was when microservices was heavily on the rise and they promised to let us scale horizontally rather than vertically. In what felt like a stroke of pragmatism and wisdom we leaned on the experience of others. We read the blog post <a href="https://engineering.atspotify.com/2013/02/in-praise-of-boring-technology/">In praise of &ldquo;boring&rdquo; technology</a> by Spotify and basically followed it. Making plenty of decisions along the way since it is fairly high-level.</p>
<p>I rather like the core of that post still. It basically reinforces the idea that you don&rsquo;t need to get fancy. You can use well-known, well-proven tools and build a series of simple things that achieve something complex and they are likely to be more reliable than more new-fangled and fancy solutions. Reliable, maintainable and thus, potentially sustainable.</p>
<p>I don&rsquo;t think leaning on the general idea was a bad call. I have some reservations about leaning on the details of it that we should get into. The basic philosophy is still pretty close to what I subscribe to.</p>
<p>The second priority was to create something that could be re-used for when the other product and any number of future products would be created. That is, re-use what makes sense. Build some things to be general. Sounded good at the time. The impact of this and the way we divided our microservices had an enormous impact down the line.</p>
<p>Meanwhile the product designer and some domain experts got busy with focus groups and exploring what the system should be to take what we&rsquo;d learnt from the existing version and push that to the next level.</p>
<h2 id="product-team-life">Product team life</h2>
<p>I really like the team we had. Good people throughout. Overall I ended up having most of the technical responsibility because I was the only one with a 100% time commitment to this product. Or mostly 100%, it varied over time but during this green field development it was certainly most of my time. We were 2-3 devs off and on. One product designer, UX, sometimes JS person that was learning programming as we went, a pretty hardcore journey and an interesting story on its own.</p>
<p>I really liked being on a team with only the product work to focus on. Agency life was always a balance between serving many masters and delivering enough stuff on time. Here we more or less embarked on grand voyage to build The Better Thing.</p>
<p>In the end we also had deadlines and limitations, so the first part of the work was the thoroughly considered and enjoyable part and then there was a significant period of forced march to get it past the goalposts. Not the best but not the worst I&rsquo;ve had either.</p>
<h2 id="this-time-well-do-it-right">This Time We&rsquo;ll Do It Right</h2>
<p>So the technical stack was:</p>
<ul>
<li>Python
<ul>
<li>Django
<ul>
<li>Django Rest Framework v2 (for our product-specific API)</li>
<li>OAuth2 toolkit (for our generalized account service)</li>
</ul>
</li>
<li>Flask (for our smaller public APIs)</li>
</ul>
</li>
<li>ZeroMQ (for RPC, Queue/Worker, PubSub communication)</li>
<li>Protocol Buffers (for message formats/contracts)</li>
<li>PostgreSQL (main datastore)</li>
<li>Redis (cache)</li>
<li>Elasticsearch (as needed, for some statistics)</li>
<li>Bind (not used for DNS, only service discovery)</li>
<li>AngularJS (web frontend, 1.x series, SPA-style app)</li>
<li>Android app, thin web wrapper</li>
<li>iOS app, thin web wrapper</li>
<li>Ionic (1.x, later limited featureset, better UX app)</li>
</ul>
<p>I don&rsquo;t really regret the specific technical building blocks. There was a lot of implementation detail that was not right and several architectural turns I wouldn&rsquo;t take today. And some of these things I wouldn&rsquo;t pick up now (Angular 1 and Ionic 1 were both evolutionary dead-ends apparently). Many of these things could have been done easier with slightly different tools. But I&rsquo;m happy with the selection and would definitely consider using a bunch of these tools again. ZeroMQ specifically isn&rsquo;t something I expect to use again but I learned tons from getting to grips with it. The <a href="https://zguide.zeromq.org/">ZeroMQ online guide</a> was a good read, if a bit hyperbolic at times.</p>
<p>So the microservices thing. Turns out how you divide your services matters a lot. We divided a lot to get that sweet, sweet scalability. We broke it apart into something like this:</p>
<p><strong>The general services</strong></p>
<ul>
<li>users (user data, primarily for auth, password hashes, resetting passwords, confirming email, extra security concerns)</li>
<li>account (OAuth, SSO, public login UI, profile information and public APIs for anything concerning the user)</li>
<li>relations (hierarchies, organizational units, user roles)</li>
<li>files (public endpoints and internal, file storage, hooks for access checking, video transcoding)</li>
<li>media (image sets, embeds, extra data such as captions, files)</li>
<li>notifications (queueing, separate workers for GCM, Apple Push Notifications and email)</li>
</ul>
<p><strong>Product-specific services</strong></p>
<ul>
<li>public API</li>
<li>core domain functionality</li>
<li>calendar functionality</li>
<li>messaging functionality</li>
<li>export functionality</li>
<li>access (used by files API and public API for authorization)</li>
</ul>
<p>Independent parts of the domain were separated which was generally fine. One slow killer was that we generalized out the organizational structure, and user roles to a service, user information to a different service and user access checking to a third service. Over time I ended up building so much caching around this to make it faster. So much caching.</p>
<p>Managing files and media in a different service worked pretty well and that did need to scale separately as it turned out. Now we didn&rsquo;t need to make it two services, the line between media and the underlying files did not end up being important and the general case implementation added a lot of overhead that caused frustration anytime anyone wanted to understand how the system uploads a picture.</p>
<p>ZeroMQ and Protocol Buffers was much more hardcore than we needed. The multitude of git repositories (one per service, one per frontend client) were sometimes painful and a monorepo would probably have been easier to keep moving in good order. We should have used python packages or just some scripting for some things where we used git submodules. Git submodules have never been the right choice for me.</p>
<h2 id="what-did-it-do-well">What did it do well?</h2>
<p>Well it worked. And it could scale horizontally and did so whenever that turned out to be the bottleneck. It did a good job separating concerns between services sometimes.</p>
<p>Once the very painful migration was done and most early bugs worked through customers generally liked the system. Overall it was an improvement. It was definitely visually more professional which some loved and some hated.</p>
<p>It was built to match the government requirements placed on preschools much more closely and I believe it did that well. Including having good support for showing the curriculum and tagging documentation according to the curriculum.</p>
<p>It also did well in separating frontend development and backend development a fair bit. Unfortunately the win was limited there as in the long term all development was me and other full-stackish people. It did allow releasing frontend updates without making a bunch of backend changes. That was good.</p>
<h2 id="what-technical-flaws-contributed-to-its-end">What technical flaws contributed to its end?</h2>
<p>The weight of the architecture was crushing. It was built on ideas from a much larger company with much larger teams. I spent long periods of time alone in running and developing the system. I got to be pretty fast at it. But it was an incredibly poor choice for a small lean team to spend time updating protobufs for service communication and worrying about scaling calendar and messages separately when both were at trivial usage.</p>
<p>The cost of the number of hosts we spread across was so much more than should have been necessary for a product at this scale.</p>
<p>YAGNI. You Ain&rsquo;t Gonna Need It. We ended up building one prototype product beyond the preschool product on top of this ecosystem. It was abandoned quite quickly. The idea of sharing services was abandoned but this product bore the weight of that generalization until the very end. Especially with an unecessarily generalized and complex system for handling the customer org structure which affected access checks, authorization, querying for common pieces of data. This was why I needed to add a lot of caching, so many checks. I started efforts to bring this closer to the core and remove the generalization but never had enough time.</p>
<p>No circuit breakers or controls to prevent services just murdering each other during high load. This lead to some quite pathological problems whenever something hit a performance snag or reached a breaking point and it could take a bit for the system to recover. We also had very spikey usage. Everyone posted things around lunch and every parent and guardian would get notifications and check around the same time during their lunch.</p>
<p>Wrapped web apps on mobile. So much futzing with video and bad web view feature sets. This was on the old iOS web view and mostly on the old Android one as well. The later Ionic stuff worked great. Until we hadn&rsquo;t updated the app in a while and our dependencies rotted and shipping an update turned into NPM dependency hell.</p>
<h2 id="what-did-you-learn-from-working-on-it">What did you learn from working on it?</h2>
<p>So much, so very much. Reading up on ZeroMQ was great fun learning to wield it was wild. Building the request/response protocol we used as well as queue/worker and pub/sub patterns were great learning experiences.</p>
<p>Lots of Ansible. So much Ansible. Our playbooks weren&rsquo;t perfect but the Ansible part of deployment was generally great. Our vagrant dev setup was, eh, not great but worked.</p>
<p>I learned to dislike the GIL in Python as we actually tried to use threads and later green threads for a bunch of things.</p>
<p>APM can be incredibly useful. We used New Relic which was costly but did so much for letting me figure out bottlenecks and fix things.</p>
<p>I don&rsquo;t disagree with all our choices at all. They were wrong for the product and the team. But the choices themselves were mostly fine. But with this particular combination it mostly caused resource starvation which wasn&rsquo;t particularly fun. So I think this has given me a better sense of what can be heavy and what can be light and when one or the other is appropriate.</p>
<p>Invent fewer things if you can. We did too many things basically from scratch. Super fun to build. Could have done something simpler. Thrift would probably have given us all we needed rather than ZMQ. But even so, I don&rsquo;t think the microservice approach was necessary at all for this product.</p>
<p>Don&rsquo;t work counter to your tools. We really did not work in alignment with Django for our uses. Our biggest Django application didn&rsquo;t have a database, it just called out to services. So we lost most of the conveniences offered via Django models. It was still more featureful in some ways than Flask but it wasn&rsquo;t pretty.</p>
<p>We could have just built on Django, Rails or Laravel and done all of it. We couldn&rsquo;t have used Phoenix because it didn&rsquo;t exist at the time. I don&rsquo;t think Elixir existed when we started, I&rsquo;d never heard of it at least.</p>
<p>I got to work with a bunch of the tricky parts of large amounts of files and how tricky performant IO can be, before object storage was standard practice.</p>
<p>So I learned a lot of tech throughout this but I probably learned more about tradeoffs during the creation and the subsequent operation of this system.</p>
<p>In reflecting on this system I&rsquo;m glad for the experience overall. Each iteration pushed me along in building my skillset and challenged me immensely. I&rsquo;m continuously both proud for the product we provided and how appreciated it was while also being very frustrated that we didn&rsquo;t make better choices so we could have provided something much simpler and much better. The power of hindsight.</p>
<p>A big take-away is that before you&rsquo;ve done something it is hard to know what running the system is going to be like. And before you&rsquo;ve lived the experience or have seen enough of something from afar it is difficult to really determine trade-offs. My current thinking is that keeping things lean and simple works very far. It both scales and performs. I have so far found it easier to break things apart when required rather than needing to shove things back together after the fact.</p>
<p>This ends the saga. For now. I think I&rsquo;ll write up an epilogue on the end of the product (it isn&rsquo;t around anymore) and my attempt at creating a spiritual successor.</p>
<p>If you have questions or thoughts on this post feel free to contact me at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Post-mortem: 10 years in the vertical - Part 2</title>
      <link>https://underjord.io/10-years-in-the-vertical-part-2.html</link>
      <pubDate>Fri, 29 Jan 2021 07:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/10-years-in-the-vertical-part-2.html</guid>
      <description>This continues our dive from part 1.
Content warning: Contains poor technical decisions, inexperience and stories of a developer just starting out and up to roughly present day. Be kind to who I was, he gets enough shit from me.
The better system The team that had formed inside our little division of this larger organisation ended up breaking out to run an agency startup. Apps and web development would be our game.</description>
      <content:encoded><![CDATA[ <p>This continues our dive <a href="/10-years-in-the-vertical-part-1.html">from part 1</a>.</p>
<p><strong>Content warning:</strong> Contains poor technical decisions, inexperience and stories of a developer just starting out and up to roughly present day. Be kind to who I was, he gets enough shit from me.</p>
<h2 id="the-better-system">The better system</h2>
<p>The team that had formed inside our little division of this larger organisation ended up breaking out to run an agency startup. Apps and web development would be our game. And it was. I did not quite know what a REST API was, every problem could be solved with Drupal. But damn if I wasn&rsquo;t on the right side of the curve to not know how much I did not know.</p>
<p>So I was the tech lead and most senior web dev. I had my trusty guy from the cheese factory by my side. We had a young, enthusiastic designer who was still a goddamn delight of a person last time I saw him. We had a somewhat grizzled veteran of solo entrepreneurship running the ship and doing all the sales, project management, marketing. A lot.</p>
<p>We also brought on a newly schooled app developer who worked on apps for this system, remotely.</p>
<p>There was a bunch of things going on and a lot of craziness throughout. But to the product point, we got tasked with building a modernized version of the system we had built, with mobile apps and video support. This time for a tech-focused company in the education sector.</p>
<p>I am not sure I have ever pulled a more finicky tooth of technology than the Video Field module that was available for Drupal 6. FFMPEG conversion commands, presets that didn&rsquo;t work, cron-jobs and JS libraries for display. Abstraction layers for days. It was a whole thing. Today I think I&rsquo;d pick that thing up quite quickly. But at the time it was absurdly complex.</p>
<p>During this work we started learning how to build things The Drupal Way, there were a lot of interesting flavors of Kool Aid. I won&rsquo;t dismiss all of The Drupal Way, it has some very interesting properties. But I do not want to revisit that tech. It is all about managing whatever configuration and state you end up having in your database. Because anything that isn&rsquo;t code ends up in the database.</p>
<p>If you know any Drupal from this era, this was CCK, Views, CTools, Pages. The ecosystem is honestly fascinating and powerful in an extremely odd way that barely transfers to anything else.</p>
<h2 id="agency-life">Agency life</h2>
<p>As you might imagine from our relative inexperience in building services and being <strong>very</strong> new at building apps and APIs the process was quite fraught. Our fledgling company had trouble balancing different client projects and a bunch of delays and complications lead to a hefty backlog, overtime, expanding the team and the company swelling like a balloon. The system had releases but keeping it moving forward and keeping the client happy was a rough process.</p>
<p>The client was honestly very reasonable and patient. But as a company we were struggling. Mostly because we couldn&rsquo;t deliver and close things down. This was again largely due to our inexperience but also had do to with scoping projects, pitching with enough margin and generally saying no to things.</p>
<p>It was an extreme learning process for everyone involved. The product was launched, updated and put to use. Bugs and continuous delays aside it was a good product. It had great market fit. It was the first commercial system anyone ever heard of for the preschool segment in Sweden. It had charming design, it had native apps that allowed smartphones to be used for publishing to the system. Parents could see what their children were doing and receive push notifications.</p>
<p>We could never quite finish up our commitments to the client for this product, there was always something else. Always one more bug, always one more promised feature that hadn&rsquo;t quite landed. Around this time our company folded because of the rapidly expanding payroll and continued limited cashflow.</p>
<p>You could say the way this business was run heavily influences how restrictive I am with my commitments when running Underjord. I am very particular about not committing to more than I can deliver on and my aim has always been to grow organically, in a sustainable fashion, if I am to grow it at all.</p>
<p>I quit just before the company folded actually. Not that it mattered. I got in writing that if I wished I could work for our client that ordered the product because I&rsquo;d spent some time at their office towards the end and it seemed like a good team, good offices, good company.</p>
<h2 id="maintenance-hell">Maintenance hell</h2>
<p>Oh yes, the product was successful. Probably not profitable, I was not privy to those details at the time. But it was popular, sold well, was appreciated. It was also getting dog slow.</p>
<p>The &ldquo;node&rdquo; table grew huge. We had to raise the integer size on the primary key. And we did not have <strong>that</strong> many customers. In the Drupal 6 days this was the table which gathered every piece of content. In the end, most data aside from user profiles ended up in there.</p>
<p>I ended up freelancing for the company and working on this product, massaging away bottlenecks, figuring out performance issues. Finding a new solution to file storage which was getting wild. I&rsquo;ll have you know we stored Terabytes of media!</p>
<p>The system was well-liked overall but was often slow at this point. It was hard to maintain, run and develop. Drupal 6 with heaps of custom code and configuration on top of it did not give us a clean path forward. Meanwhile the company had another product in Drupal 7 which also had performance issues and a lot of unwanted complexity. So our team at this company turned our eyes to the horizon. It was time for a new system.</p>
<p>But first. Lets review.</p>
<h2 id="what-did-it-do-well">What did it do well?</h2>
<p>It delivered something the customers definitely wanted and were excited about. It delivered on an organizational requirement they had that was handed down to them by the government. It mandated a certain systematic documentation of activities in preschool education. It also served a number of practical needs and desires. Such as sending information out to guardians.</p>
<p>In some sense it was built in the simplest way we knew. It was never over-engineered.</p>
<p>It was simple and easy to use. It was an incredibly common piece of feedback about the system. The customers understood it. It modeled their organizations well, it made sense. It largely acted as expected. It was simple and that was the single best thing about it. We had untrained preschool teachers working as effective admins for their orgs. It mostly worked well.</p>
<h2 id="what-technical-flaws-contributed-to-its-end">What technical flaws contributed to its end?</h2>
<p>So many. File management was done through Drupal 6 and PHP, including a maddening video conversion cron-job that was incredibly flaky. It all ran on one server, a physical one. Not bad in itself but scalability was definitely a concern. After a while files were moved to a mounted file server when adding disks became untenable.</p>
<p>VPS:es were a thing, we didn&rsquo;t know much about them when we started and there was precious little breathing room for making transitions and non-critical work.</p>
<p>Performance, caching, page loading performance, file serving. So much performance-related stuff. PHP opcode caching was one thing that helped. But there was so much complexity, global state gone wild and so much weirdness. We used a lot of Views and Pages which are Drupal modules that do rather cool things in some of the most complex ways I can imagine. The are incredibly flexible, powerful and hard to not shoot yourself in the foot with. Its an odd ecosystem. I was really into it at the time. But also falling out of it rather quickly.</p>
<p>Our users were change-sensitive. I think we did manage a fairly successful overhaul of the admin backend. But in general, changing anything related to finding your way through the system was heavily disliked. I think that was a good take-away honestly. Don&rsquo;t change things unless there is good reason.</p>
<p>The LAMP stack wasn&rsquo;t the problem, Drupal 6 wasn&rsquo;t the problem, file storage wasn&rsquo;t the problem. But I will say that our pressure-cooked code-base made by inexperienced and stressed people in this combination of tools was an absolute liability. But it was also a successful and much loved product. Go figure.</p>
<h2 id="what-did-you-learn-from-working-on-it">What did you learn from working on it?</h2>
<p><strong>It don&rsquo;t gotta be fancy.</strong></p>
<p>We were early on the app side of things. We were fairly old-school on the web side, a major version back which in Drupal terms, that&rsquo;s like a major version of Debian back (or at least how Debian used to be, glacial).</p>
<p><strong>A good fit for the customer matters a lot.</strong></p>
<p>People genuinely loved this product. It was heart-warming. They could be pissed at it and about things we did. But we had so much positive feedback.</p>
<p><strong>Friendly and helpful customer support is golden.</strong></p>
<p>Our product was largely represented by one person. An upbeat guy with patience for days and a real enthusiasm for helping people figure things out. And when he couldn&rsquo;t wrangle the issue I&rsquo;d usually be involved, or one of the other occasional team members.</p>
<p><strong>Fit your purpose and know your people.</strong></p>
<p>I think this was an underserved group. Actually, I know it was. It still is to some extent. And they rallied around their solution. The product was only for preschools. It only prioritized preschools. It didn&rsquo;t aspire to be anything for primary school or above. The preschools in Sweden run quite differently from the stages of schooling that come after it. They loved the platform for this. It was theirs.</p>
<p>On more personal skillsets. I learned how to run and not run projects. How to get things done under intense pressures as well as under more reasonable work-life balances. I learned how much more productive I can be when I&rsquo;m not splitting myself over a large number of priorities.</p>
<p>On a technical level I picked up a lot of Apache, Nginx, PHP, Drupal 6, MySQL, some Redis and plenty of Linux admin. All the PHP optimization tips and tricks. I did weird things with .htaccess files. FFMPEG and video conversion.
AJAX, which was hot back then and often really bad to work with in Drupal (AHAH, I remember you).
I had learned Python in parallel but didn&rsquo;t get a chance to use it.
RabbitMQ for moving away from the video cronjob was a thing. So much random stuff.</p>
<p>I&rsquo;m not certain it matters how I feel about the total experience. Because it has been incredibly influential on my work and what I do. It is base facts and part of the groundwork for everything I know now. And I have a warm place in my heart for the product and its users, a touch of shame for some of the technical outcomes and plenty of understanding for why we ended up where we did.</p>
<p>Next, we knew we needed to scale&hellip;</p>
<p>Part 3 is slated to arrive in two weeks. So join the newsletter or subscribe to the RSS and you&rsquo;ll find out when it happens.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Getting started with PETAL</title>
      <link>https://underjord.io/getting-started-with-petal.html</link>
      <pubDate>Mon, 18 Jan 2021 13:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/getting-started-with-petal.html</guid>
      <description>I recently wrote about the PETAL stack on Changelog.com and as I haven&amp;rsquo;t really had a chance to really get my hands dirty with it I decided that my next lab project should use it. So I set it up as a public repo and added the missing components manually by following some guides. You can see the commits or follow along.
So I used Phoenix 1.5.6 that I had already installed.</description>
      <content:encoded><![CDATA[ <p>I recently <a href="https://changelog.com/posts/petal-the-end-to-end-web-stack">wrote about the PETAL stack</a> on Changelog.com and as I haven&rsquo;t really had a chance to really get my hands dirty with it I decided that my next lab project should use it. So I set it up as a public repo and added the missing components manually by following some guides. You can <a href="https://github.com/lawik/pubd/commits/master">see the commits</a> or follow along.</p>
<p>So I used Phoenix 1.5.6 that I had already installed. The Phoenix docs should cover installation for you. My projects is called pubd, we&rsquo;ll see if I get it go anywhere in that case that&rsquo;ll be separate posts. I ran <code>mix phx.new pubd --live</code> to set up the Phoenix boilerplate project and that lead to <a href="https://github.com/lawik/pubd/commit/859d61827ed4d049863aeb4a2d1d726eb382ace0">this commit</a>. The <code>--live</code> flag indicates that it should give us LiveView.</p>
<p>The next part was based on <a href="https://dockyard.com/blog/2020/12/21/optimizing-user-experience-with-liveview">Chris McCord&rsquo;s article about Alpine</a> on Dockyard.</p>
<p>So I ran these commands to install Alpine.js:</p>

  <div class="code  bash " >
    <div class="meta">
      <span class="language">bash</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">cd assets
npm install alpinejs</code></pre></div>
  </div>


  <div class="code  javascript "  data-file="assets/js/app.js" >
    <div class="meta">
      <span class="language">javascript</span>
      <span class="filename">assets/js/app.js</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#75715e">// .. after the app.scss import
</span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Alpine</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;alpinejs&#34;</span>;
<span style="color:#75715e">// .. in the let liveSocket configuration, after params
</span><span style="color:#75715e"></span><span style="color:#a6e22e">dom</span><span style="color:#f92672">:</span> {
    <span style="color:#a6e22e">onBeforeElUpdated</span>(<span style="color:#a6e22e">from</span>, <span style="color:#a6e22e">to</span>) {
        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">from</span>.<span style="color:#a6e22e">__x</span>) {
        <span style="color:#a6e22e">Alpine</span>.<span style="color:#a6e22e">clone</span>(<span style="color:#a6e22e">from</span>.<span style="color:#a6e22e">__x</span>, <span style="color:#a6e22e">to</span>);
        }
    },
},</code></pre></div>
  </div>

<p>It lead to <a href="https://github.com/lawik/pubd/commit/f32030522b9c9ad22ec7464b9603a71a1100f61f">this Alpine installation commit</a> and afterwards I decided to verify that I could successfully invoke Alpine and added <a href="https://github.com/lawik/pubd/commit/918be370917b8d8813bb78b519836d4ed0853b9c">this other Alpine verification commit</a>.</p>
<p>Alpine was fairly straight-forward. Tailwind CSS was a bit more involved as that has much more dealings with preprocessing and the JS tooling. I followed the Phoenix 1.5 parts from <a href="https://pragmaticstudio.com/tutorials/adding-tailwind-css-to-phoenix">this guide by Pragmatic Studio</a>.</p>
<p>Rather than restate their very nice tutorial here which feels rude I want to offer you two options. Either you work through the above tutorial and gain all that related context they so kindly included. Or you work through <a href="https://github.com/lawik/pubd/commit/812c99bd94403800bac656bbb016c893d5d5ae81">my commit diff</a>. Either way you should end up with a working Tailwind CSS install.</p>
<p>My approach included removing the default Phoenix styles which of course made the default application quite ugly. I couldn&rsquo;t be bothered to fix that and instead I just wanted to make sure Tailwind was doing its thing. A commit for my test is <a href="https://github.com/lawik/pubd/commit/de8eeee5244506f9c323508da39568beb7577a58">also available</a>.</p>
<p>Now with these installed you should be able to get going with working in this stack. Assuming you know a bit about Elixir and Phoenix you should be quite capable of learning LiveView. And when that won&rsquo;t suffice you have Alpine.js. If you need visual tools, you should have good use out of Tailwind, or so I hear. I&rsquo;ll link their docs here:</p>
<ul>
<li><a href="https://elixir-lang.org/getting-started/introduction.html">The Elixir language guide</a></li>
<li><a href="https://hexdocs.pm/elixir/Kernel.html">The Elixir documentation on Hex</a></li>
<li><a href="https://hexdocs.pm/phoenix/1.5.6/Phoenix.html">The Phoenix documentation on Hex</a></li>
<li><a href="https://github.com/alpinejs/alpine">Alpine JS GitHub docs</a></li>
<li><a href="https://tailwindcss.com/docs">Tailwind CSS docs</a></li>
</ul>
<p>I genuinely look forward to digging into more work with this stack. I&rsquo;ve heard plenty of hype for Tailwind CSS and the way they use classes looks utterly wrong to me so far. So it should be a novel challenge for my sensibilities. Maybe I&rsquo;ll hate it? Alpine truly seems to follow the Tailwind concept, so that&rsquo;s similarly noisy. Maybe that&rsquo;s all good pragmatism. I hope to find out.</p>
<p>Anyway, I hadn&rsquo;t seen a guide for setting up the PETAL stack so I figured I&rsquo;d put one out there. Bare-bones as it is.</p>
<p>In case you&rsquo;d like to know more about what I do, sign up for <a href="https://underjord.io/newsletter.html">the newsletter</a>. I write more about Elixir, my projects and developer life there.</p>
<p>If you have thoughts, questions or want to otherwise get in touch, reach out via email <a href="mailto:lars@underjord.io">lars@underjord.io</a> or <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. If you want more of my writing, consider <a href="/newsletter.html">my newsletter</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Post-mortem: 10 years in the vertical - Part 1</title>
      <link>https://underjord.io/10-years-in-the-vertical-part-1.html</link>
      <pubDate>Fri, 15 Jan 2021 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/10-years-in-the-vertical-part-1.html</guid>
      <description>Before I did the independent thing I was a developer at a few different companies. I worked on multiple things but one of the main things I worked on was a series of products in the preschool education segment here in Sweden, that&amp;rsquo;s the titular vertical. The products have since been shut down. This series of posts will cover my trajectory as a professional web developer as well as the evolution of these systems.</description>
      <content:encoded><![CDATA[ <p>Before I did the independent thing I was a developer at a few different companies. I worked on multiple things but one of the main things I worked on was a series of products in the preschool education segment here in Sweden, that&rsquo;s the titular vertical. The products have since been shut down. This series of posts will cover my trajectory as a professional web developer as well as the evolution of these systems. The series has three parts (they are already written) and will likely receive an addendum.</p>
<p><strong>Content warning:</strong> Contains poor technical decisions, inexperience and stories of a developer just starting out and up to roughly present day. Be kind to who I was, he gets enough shit from me.</p>
<p>I will cover some different iterations of the same kind of product/system along the way and I will go back to a few questions as we consider each system.</p>
<ul>
<li>What did it do well?</li>
<li>What technical flaws contributed to its end?</li>
<li>What did you learn from working on it?</li>
</ul>
<h2 id="the-first-system-2009">The first system (2009)</h2>
<p>During my first job as a professional web developer, not as my first project, a few projects in, I ended up technically responsible for building a web system to support the preschools in an entire municipality. I was a very cheap contractor. I think I pulled my weight overall.</p>
<p>It was a fairly tight budget project, I was at a stage of my work where I had three hammers in my belt, they were named Joomla, WordPress and Drupal. I liked Joomla because it had a lot of GUI for admin and looked sharp by default. I liked WordPress for simpler, bloggier sites. I had just started looking into Drupal and found the way of Drupal 6 a lot clearer than all this intricate MVC-stuff I&rsquo;d never seen before.</p>
<p>So for building this rather custom system I knew WordPress was a bad fit, Joomla would only work if there was a ready-made extension or something for Swedish preschool systems. I picked Drupal. From this point and for several years to follow Drupal would be the hammer to most nails I encountered. I have feelings about this. But at least its funny to me now.</p>
<p>I started the work on this. We brought in another developer for the project. I recruited him by recommendation from a conversation in a locker room at the Muay Thai club I trained at then. He left a not-so-lucrative gig at a cheese factory to try his hand at actually working with the computer stuff he knew. Great guy. I never regretted bringing him on and I hope he doesn&rsquo;t miss the cheese because he is still in tech.</p>
<p>I think we delivered pretty well. It was all very exciting. To our advantage, the needs were quite basic. Each preschool needs a blog/newsfeed. Each preschool needs to be able to write portfolio posts for the different children. All posts could have pictures attached. Teachers could set up reminders in the system to remind parents about things.
This was fairly simple in Drupal and if we had more experience it could&rsquo;ve been quite clean. It was not that, but it worked fine and did well enough. There were no apps, no videos, maybe some mail-notifications, I forget. iPhones were not a thing in education and iPads (2012) hadn&rsquo;t even been conceived of yet.</p>
<p>I don&rsquo;t think there were any additional development efforts on the system, I don&rsquo;t recall much beyond some post-launch fixing. But it did introduce me to the audience of preschools. Preschool teachers at the time were not tech savvy. We got a lot of praise for our system being simple to use. Not too complicated. This would be a thing going forward. Customers that were generally pleased and a system that strived for user simplicity.</p>
<h2 id="what-did-it-do-well">What did it do well?</h2>
<p>It was pitched to provide a blog-style newsfeed and portfolios for the kids and their parents. It did. It worked. They could be exported. It did what it should and was very straight-forward. Simple design, simple execution, probably not particularly performant. I couldn&rsquo;t really say from here. But I know I didn&rsquo;t know enough to make Drupal fast at the time.</p>
<h2 id="what-technical-flaws-contributed-to-its-end">What technical flaws contributed to its end?</h2>
<p>Not really any as far as I know. It was mostly a bunch of custom modules in Drupal along with a bunch of configuration. It ran its course and was probably never updated as the smartphone-age hit. I know the municipality was later a customer in the next version of the system.</p>
<h2 id="what-did-you-learn-from-working-on-it">What did you learn from working on it?</h2>
<p>Tons but all of it somewhat basic. A lot about basic Drupal development, a bunch of polish on the ol' PHP skills (this would be PHP 4 I think, definitely LAMP). Lots of CSS-fighting to get a decent cross-browser look according to the pixel-perfect cartoony design.</p>
<hr>
<p>Thank you for reading. I expect to publish the next part of this in two weeks. While you wait, if you want more of my writing, my newsletter contains things that the blog does not. You can sign up for that at the bottom of the page. I also provide an RSS feed for this blog. If you have questions or want to get in touch you can reach me via email at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Podcasts that supported my independence</title>
      <link>https://underjord.io/podcasts-that-supported-my-independence.html</link>
      <pubDate>Mon, 11 Jan 2021 13:30:00 +0000</pubDate>
      
      <guid>https://underjord.io/podcasts-that-supported-my-independence.html</guid>
      <description>I want to shine a light on some podcasts that I really appreciated as I made my bid for independence and went out to contract, consult and strap boots. Because in some aspects running your own business can be lonely. It suits me well but I also immensely appreciate having some voices that share my concerns, contribute to my thinking and sometimes just keep me company.
Underjord is my business and most of the work is me doing things.</description>
      <content:encoded><![CDATA[ <p>I want to shine a light on some podcasts that I really appreciated as I made my bid for independence and went out to contract, consult and strap boots. Because in some aspects running your own business can be lonely. It suits me well but I also immensely appreciate having some voices that share my concerns, contribute to my thinking and sometimes just keep me company.</p>
<p>Underjord is my business and most of the work is me doing things. My wife does the books as that&rsquo;s conveniently her profession and at times I&rsquo;ve brought in people I&rsquo;ve worked with when the budget allows. Now this isn&rsquo;t the first time I&rsquo;ve been self-employed but it is the first time I&rsquo;ve continued to run my business, worked multiple clients, established separate revenue streams and generally buckled in for the long term. The other times I ended up an employee in a year or two after settling in with one large client. That was fine then. Now I&rsquo;ve been going for two and a half years, I&rsquo;ve had a number of different clients, some very brief contracts and consultations, some longer term engagements. I&rsquo;ve weathered most of a pandemic, the birth of our child and moving to a house. So it feels solid. I planned to go independent and I have stayed that course.</p>
<p>Since my wife does work with businesses in her accounting work she gets the world of business very well. And since she and I share our lives and this business is an integral part of our personal finances she&rsquo;s definitely invested. But even with her special incentives this sometimes frustrating thing holds true. No one cares about your business as much as you do. It&rsquo;s not weird exactly but it can become painfully obvious when you try to talk about it with people who aren&rsquo;t interested in business. Most people don&rsquo;t even care about running a small business in the abstract. Even less about whatever your deal is. That&rsquo;s fine. But it takes up a significant chunk of my mind and business owners I&rsquo;ve spoken to share the sentiment. Being able to really get into it with someone who gets it is fantastic.</p>
<p>A good second best is listening to people that talk about it. And thats what I want to push for here in the form of podcasts. If you follow my newsletter I&rsquo;ve covered a bundle of these there already. If you are curious about the newsletter you can sign up at the bottom of the site.</p>
<h2 id="focusedhttpswwwrelayfmfocused-formerly-free-agents"><a href="https://www.relay.fm/focused">Focused</a> (formerly Free Agents)</h2>
<p>So this podcast has transformed from being about working as a free agent into a productivity podcast. It is a pretty decent productivity podcast but I&rsquo;ll admit to missing the Free Agents and preferring the olden times.</p>
<p>If you want to run your own business and want to listen to people that do, go back to the older episodes and pick some that sound good to you. They hit on some good topics and I think it was quite helpful to verify and adjust my thinking on a number of things early on.</p>
<h2 id="hurry-slowlyhttpshurryslowlyco"><a href="https://hurryslowly.co/">Hurry Slowly</a></h2>
<p>This podcast is magnificent in a very understated way. It is strangely serene and lovingly crafted podcast. It sits in the productivity space. But for me this was very useful when starting my business as I have a brain that wants to go fast. And this podcast is all about pacing yourself. Spending the time to slow down, give my brain some time to work things through, consider some things carefully. The things I&rsquo;ve done most deliberately are the things that have paid off the best down the road. I credit this podcast with a lot of that.</p>
<p>I would suggest starting from the beginning and then picking topics you like as you go a bit. It&rsquo;s fine to skip an episode you don&rsquo;t care about. Further along as show host Jocelyn K. Glei opens up more it can get a bit uncomfortable if you are a very skeptical person as she opens up about alternative interests. I think it has remained mostly good and while I&rsquo;m quite skeptical of most of this stuff myself I value her perspective and I think listening to the less out there stuff in the beginning is valuable to anyone but I don&rsquo;t expect my audience would appreciate the recent stuff without getting to know the more concrete episodes from early on.</p>
<p>There is especially one on transformative gatherings that I found incredibly useful. Because I know people that do these things well and I want to understand how to do it myself.</p>
<h2 id="cortexhttpswwwrelayfmcortex"><a href="https://www.relay.fm/cortex">Cortex</a></h2>
<p>Very private YouTube creator CGP Grey and fairly public podcast network founder Myke Hurley talk productivity, apps and all that stuff. They get into business and independent creation quite a bit and I&rsquo;ve found their thoughts quite useful. It also helps that the two are quite different and I don&rsquo;t feel like I align fully with either of them which gives me a good spectrum to consider on many topics.</p>
<p>Lots of Apple as per usual with Relay FM. If you hate Apple you might want to steer clear but while I run a very mixed setup I&rsquo;m quite curious about the Apple enthusiast space so it suits me fine.</p>
<h2 id="ditching-hourlyhttpspodcastditchinghourlycom"><a href="https://podcast.ditchinghourly.com/">Ditching Hourly</a></h2>
<p>Jonathan Stark has a thing about not billing hourly. I think he has some points, especially for experienced techies there. Beyond that, this podcast repeatedly gets into your relationship with your clients. How you market, sell and execute. How you promise, negotiate, figure out what clients want and all that. There&rsquo;s lots of good stuff. Unfortunately the podcast occasionally becomes very mostly a vehicle for fairly cheap questions and answers and after a while you&rsquo;ve heard a lot of what he has to say. But I think the value before you hit that point is quite high. And I still check in on it.</p>
<h2 id="the-business-of-authorityhttpsthebusinessofauthoritycom"><a href="https://thebusinessofauthority.com/">The Business of Authority</a></h2>
<p>Now this is Jonathan Stark again but now with Rachelle Moulton and better topics. So this gets into developing a business in the consulting space. I think its incredibly valuable for programmers that want to be more than just a pair of hired hands.</p>
<h2 id="the-art-of-producthttpsartofproductpodcastcom"><a href="https://artofproductpodcast.com/">The Art of Product</a></h2>
<p>This goes into the space of trying to start a product business and I think that&rsquo;s in mind for a lot of independent developers since generally you have the skillset to do the core building. This podcast covers both that and all the other stuff that goes into running a business. I think they cover it well and they don&rsquo;t overstay their welcome. Strongly recommended if you think product might be for you.</p>
<hr>
<p>I will cut this list there and call it short and sweet. I hope this ends up useful to you. Considering they are podcasts you are likely hours away from finding out still, even if any of these speak to you. For me these sorts of podcasts have kept me good company, contributed greatly to my thinking around my business and in general been very entertaining. I hope they serve many of my readers just as well.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Elixir businesses doing well</title>
      <link>https://underjord.io/elixir-businesses-doing-well.html</link>
      <pubDate>Wed, 09 Dec 2020 13:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/elixir-businesses-doing-well.html</guid>
      <description>I generally don&amp;rsquo;t track startup news and financing rounds closely but it filters in because I enjoy tech. And these two Elixir-based companies made a blip on my radar recently with their successful rounds. So why not. Let&amp;rsquo;s be a little bit business.
So I don&amp;rsquo;t particularly love the VC-backed model of building businesses. It sort of makes sense for some types of endeavors but generally I&amp;rsquo;m an organic growth and small teams kind of person.</description>
      <content:encoded><![CDATA[ <p>I generally don&rsquo;t track startup news and financing rounds closely but it filters in because I enjoy tech. And these two Elixir-based companies made a blip on my radar recently with their successful rounds. So why not. Let&rsquo;s be a little bit business.</p>
<p>So I don&rsquo;t particularly love the VC-backed model of building businesses. It sort of makes sense for some types of endeavors but generally I&rsquo;m an organic growth and small teams kind of person. I&rsquo;ve been in a fast-growing small startup and have the scars to prove it. I&rsquo;ve also seen good and bad examples of how these kinds of startups can work. Startups that can work at a sustainable pace basically mostly feel like any other company and are fine. Startups that are under fire or in financial panic are generally rough to work inside of. Not my type of business to run and own these days but highly relevant to our sector and Elixir is quite popular in the startup space, as it took on the mantle of Ruby&rsquo;s successor.</p>
<p>I don&rsquo;t know either of these companies intimately. I basically just found out about <a href="https://www.joinblvd.com/">Boulevard</a>, I might have heard the name before but not sure. <a href="https://discord.com/">Discord</a> I know as an occasional gamer and community participant, they&rsquo;re huge of course. These companies are at different scales and with different levels of name recognition I dare say. But they both build on Elixir and have done so since fairly early on. They both also seem to be killing it as they say in the biz. I think, I&rsquo;m only tangentially in any biz.</p>
<ul>
<li><a href="https://techcrunch.com/2020/11/24/discord-is-close-to-closing-a-round-that-would-value-the-company-at-up-to-7b">Discord is close to closing a round with a $7B valuation</a></li>
<li><a href="https://www.joinblvd.com/blog/boulevard-series-b-financing">Boulevard closed a $27 million Series B</a></li>
</ul>
<p>Both of these are serious money. Different levels of serious but either way, quite serious. I&rsquo;ve written a little bit about <a href="/elixir-as-competitive-advantage.html">Elixir as a competitive advantage</a> and this just adds some good sauce on top of that.</p>
<p>And I do believe the technology choice has given these companies a genuine advantage. Let&rsquo;s pontificate.</p>
<p>Discord&rsquo;s use-case is so very closely mapped to telecom needs that it&rsquo;s almost absurd. They do calls and chat, presence and soft real-time. So building on something with the backing of Erlang and the BEAM, straight out of Ericsson, probably fit pretty much perfectly.</p>
<p>Boulevard&rsquo;s case isn&rsquo;t as clear to me. I think they are closer to the startup that would have defaulted to Rails a bunch of years back. So what this tells me (my sample size is like 6+) is that whatever Rails brought to the table Elixir seems to be matching. They actually had a Sean Stavropoulos on the Elixir Talk podcast that just resurfaced to talk. And he called out the lower memory usage on Heroku and a bunch of other sweet stuff. Good listen I thought. Their use-case is not as clear because they aren&rsquo;t necessarily connecting calls and sending audio, doing lots of real-time stuff and all that. They might be but their business model would likely not demand it. To me this strengthens the image of Phoenix and Elixir covering the 80-90% use-case. Which is even more important.</p>
<p>As I&rsquo;ve been prone to claiming. I think Elixir allows you to build better solutions. It gives you an exceedingly powerful baseline if you treat it like any other high-level language with a web framework. But you have enormous headroom to build systems that are a nightmare to build in languages with a similar expressiveness. The trade-offs you currently make with Elixir are things that place the language and runtime squarely in the space of building distributed services. I wouldn&rsquo;t use it for a CLI application because of startup time (though Lumen might fix that), I wouldn&rsquo;t use it for a mobile app because it doesn&rsquo;t really fit (Lumen might help there, but I don&rsquo;t expect it to). But I mostly build services. And there it just allows me to do more with less. Less hardware, less dependencies, less complexity. And it gives more. More guarantees, more clarity, more consistency and more reliability.</p>
<p>If you have thoughts, feel free to reach out at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter at <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. If you want more of my writing I have a newsletter where I write on similar topics but perhaps more ephemerally and more experimental stuff. You&rsquo;ll find a signup thing in the footer.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Wisps, a touch of whimsy</title>
      <link>https://underjord.io/wisps-a-touch-of-whimsy.html</link>
      <pubDate>Thu, 03 Dec 2020 07:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/wisps-a-touch-of-whimsy.html</guid>
      <description>This has been the most fun I&amp;rsquo;ve had with JavaScript in some time. A spiritual successor to the stupid solution which allowed both me and my visitors to see the number of concurrent readers. It is also a piece of whimsy I&amp;rsquo;ve wanted to do for a while to add more magic and life to the site. The animations default to off to preserve CPU but do turn on the magic switch in the top right if you can and want to see the visuals.</description>
      <content:encoded><![CDATA[ <p>This has been the most fun I&rsquo;ve had with JavaScript in some time. A spiritual successor to the <a href="/live-server-push-without-js.html">stupid solution</a> which allowed both me and my visitors to see the number of concurrent readers. It is also a piece of whimsy I&rsquo;ve wanted to do for a while to add more magic and life to the site. The animations default to off to preserve CPU but do turn on the magic switch in the top right if you can and want to see the visuals. This is arguably less stupid.</p>
<p>What is it? Well, a live number of my readership for the entire site for one. If you turn on the magic you should also see the rough positions of other readers on the site that are on the same page as a small hovering geometric shape. I call them wisps.</p>
<p>It&rsquo;s not often that you can see the footfalls of other people on the web unless they write a comment or something. I don&rsquo;t want to expose my readers to one another in any privacy-sensitive way or in any fashion that requires moderation. This simply gives a rough indication of activity on the site and whether people are reading the same thing as you. And it moves around in fun ways.</p>
<p>Anyway, I enjoy it and I hope you will too. So how is this done? A small backend in Phoenix using Channels (basically just WebSockets), a small amount of Javascript to render new elements and update positions based on the state updates from the backend. The idle hovering of the symbols is CSS Animation and moving from area to area as the reader reads on is done via CSS Transitions.</p>
<p>I was hoping to default to on, but as I&rsquo;ve found out with my sweet animated gradient lines, CSS animation can get CPU intensive. Right now my gradient lines pause after a period of inactivity and only run if there is activity. They hit 12% or so on my CPU which is dumb. A better fix to make them more lightweight is on the way, the idle-handling is a mitigation. So for these I wanted to avoid a big performance hit. They perform okay right now from my tests, they use the cheap-to-animate transforms so there&rsquo;s nothing too heavy. But also, there&rsquo;s not a known upper bound on the potential number of wisps active at the same time and if that scales poorly on some browsers I don&rsquo;t want to default to setting someone&rsquo;s phone on fire.</p>
<p>The number is available regardless since that doesn&rsquo;t animate. I think showing this sort of thing is a fair way of both measuring concurrent visitors and sharing that with the visitor. I can see it, you can see it. I&rsquo;ve also been considering a 90&rsquo;s web visitor counter that is actually live. Could be fun. Just gotta find the right aesthetic.</p>
<p>I like this way of extending my site. I guess in a JAMStack fashion. The site is static but I can build small interactive parts and back them with minimal backends which for stateful applications Elixir does exceedingly well. If these backends go down the site will still work. All the important stuff is there. The fun stuff degrades to a connection error in the console.</p>
<p>Let me know how you like it, I do get and read responses to my posts and I appreciate when y&rsquo;all reach out so try that via <a href="mailto:lars@underjord.io">lars@underjord.io</a> or @ me on Twitter <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. If you want more writing, sign up for the newsletter further down the page.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Asking a tech recruiter</title>
      <link>https://underjord.io/asking-a-tech-recruiter.html</link>
      <pubDate>Wed, 25 Nov 2020 07:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/asking-a-tech-recruiter.html</guid>
      <description>Since I left my comfy job as the tech lead for a SaaS product and went into running my own business I took a closer look at my relationship with recruiters. While working I mostly found the attention of recruiters slightly reassuring but often annoying. I think that annoyance is fairly common, usually built up from countless LinkedIn drive-by attempts from unreading keyword-hunting recruiters. I thought that now, out on my own, maybe this legion of recruiters can be my sales department.</description>
      <content:encoded><![CDATA[ <p>Since I left my comfy job as the tech lead for a SaaS product and went into running my own business I took a closer look at my relationship with recruiters. While working I mostly found the attention of recruiters slightly reassuring but often annoying. I think that annoyance is fairly common, usually built up from countless LinkedIn drive-by attempts from unreading keyword-hunting recruiters. I thought that now, out on my own, maybe this legion of recruiters can be my sales department. And they have been, to an extent.</p>
<p>During my first few days as a free agent I did reach out to one recruiter in particular. This was the one that had been closest to dislodging me from my previous position and I had a feeling he was a sharp one. I had also thrown my cousin at him and he had helped him land his first real ops gig. When I got in touch this recruiter quite swiftly landed me my first client. In parallel I started to accept more recruiter connections and had a lot more conversations with assorted recruitment agencies. It has netted a fair bit of work. But I dare say the hit-rate is mostly low.</p>
<p>The recruiters that I’ve found to give the best results also give recurring results. They are the people that follow up, consider your needs, balance them with client needs and make things happen. It is my feeling that there remains a large cultural gap between the majority of recruiters and developers. I’ve been thinking about how to usefully bridge that. I don&rsquo;t particularly need it right now but I want to help junior developers find their way into work and more experienced developers find their way to what they actually want. I think recruiters could help there. But I think we&rsquo;re still quite far off from that.</p>
<p>I reached out  about this to my network on LinkedIn (where the recruiters live). I got a response from Emy Wennerberg Kristoffersson who was willing to take a chance and reach some new developers. Emy works mostly in Sweden around Gothenburg and Helsingborg, so while she might not work in your particular area I think the information and exchange is widely applicable. We figured a good first step is to tackle some of the common skepticisms that developers tend to have around recruiters and recruitment. I hope this will be helpful. The post is not sponsored, I asked her to answer a bunch of uncomfortable and nuanced questions which I think she does gracefully. Let&rsquo;s get into it.</p>
<p><strong>For some background, can you introduce yourself and tell us a little bit about your professional experience?</strong></p>
<p>Emy: My name is Emy Wennerberg Kristoffersson. I was born and raised in Helsingborg (south of Sweden), but moved to Gothenburg back in 2016. I am passionate about tech, human beings and business development. I settled on tech-recruitment because it gives me the opportunity to combine all of these areas. For the last three years, I have been working in the recruitment industry. I work for Bonsai Consulting, a Gothenburg-based company that specializes in tech recruitment.</p>
<p>I have always had a huge tech-interest. Though, this wasn’t something that I seized back in my younger years, at least not to a greater extent (apart from when loved ones encountered technical problems and I wanted to impress – hah!). My father has always been in the IT sector so I’m quite sure that his tech skills have influenced me. I am a people-person at heart, so I eventually decided to study Human Resources in Gothenburg. In time, I got in touch with Bonsai Consulting whereupon I started to work as a researcher, and my main task was to build a network of candidates who were open to new opportunities. After a couple of months, I leveled up to a position as a recruiter and got a bigger responsibility within the company. Back then we worked broadly in recruitment and recruited to many different industries, but due to my tech-interest, the positions that related to IT and tech always ended up on my desk. One and a half years ago, we decided to work exclusively with tech recruitment due to the enormous demand within the industry.</p>
<p>One of the most interesting things in my profession is the potential for improvement in the recruitment industry. Today, I am aware that there is a lot of frustration against the recruitment profession and I do think that this is a misconception. Many jobseekers consider recruiters as an annoying part of the job search. Generally speaking, we have a pretty bad reputation (let’s talk more about this later). But the thing is, in fact, that we are an asset in a candidate’s job search and in a company’s recruiting process. My vision is to get fewer people out there to see us as an annoying piece of the puzzle, and instead see the value of taking our help as a job coach.</p>
<p><strong>Finding and hiring experienced developers has been a challenging proposition for a while due simply to enormous demand, how does this affect your job?</strong></p>
<p>Emy: The first thing that comes to my mind, is the challenge of getting the companies to understand the market and the developers’ situation. It is a bitter pill to swallow for many recruiters and companies, but today many developers have at least 4-5 opportunities available for him or her. Unfortunately, not all companies understand how coveted many developers are, and therefore they don’t understand the necessity of offering a great deal to potential employees. Not just the salary has been rising during the last years, other requirements have changed considerably as well. Today, many developers expect to be able to work remotely, having flexibility in their working day, good opportunities to develop within the company and to be able to develop their own skills (and so on…). Outstanding developers know their value on the market, and if a company’s position doesn’t sound interesting or profitable, they will go on to their next available opportunity. Many companies lack the understanding of how many offers a developer can have on their table and are therefore unable (or even unwilling) to match their needs. This is a tough nut to crack.</p>
<p>Another thing that comes top of mind is the art of standing out as a recruiter. Due to the enormous demand, many developers are likely to get contacted by a countless number of recruiters every day. The old-fashioned way of sending an email to a developer saying “Hi, here’s a job I’d like you to consider” doesn’t work today. Why? Because that developer has probably received multiple requests from other recruiters already, and my message is likely to disappear somewhere in all that noise. Over the years that I have worked as a recruiter, I have come to understand the importance of understanding the developers needs and desires before sending them multiple job descriptions, preferably even before I contact him or her. It is my duty, as a recruiter, to do my research before I expect a developer to take his or her time to talk with me. For example, If I check their Github I may find out that this developer prefers back-end development in C#/.Net, then I know that it won’t be necessary for me to contact him or her in order to talk about a front-end position where your main focus is in React and Typescript. If I don’t do my research, I’m likely to waste the person’s time. If I don’t find anything on Github or similar, then I think it is pure decency of me to first of all ask if they are interested in having a conversation with me and if they are, I can’t just throw a job description in their face without first understanding what this person is interested in.</p>
<p><strong>Has everything changed with the pandemic? Is development work hard to find now?</strong></p>
<p>Emy: A lot has changed with the pandemic. From my experience, I think that the biggest challenge for recruiters right now is that developers in general are unwilling to take on a new job, even though they might know that their current position isn’t exactly what they want. I think it’s a result of the uncertainty with the pandemic, that no one knows how it will develop and what will happen next. Since the pandemic seriously shook the market during spring and summer, many developers are worried that it will put them in a situation where they’ve left a permanent employment and the safety that it entails, to be the “last man/woman in, first out”.</p>
<p>In the beginning of the pandemic the market was disastrous, from March until September it was clear that even the IT-industry (despite the great demand) suffered from the pandemic. Many start-ups had to end their businesses and bigger companies were prohibited from hiring, many were even forced to dismiss employees in order to survive. Since August until today it has eased, and more companies dare to hire today. With that said though, companies take precautions when hiring and the processes might include more steps than normally in order to be really sure that it’s a good fit for the position.</p>
<p>I’d say that there are many opportunities on the market by now, but of course we are far from “normal”. Unfortunately, many companies demand more senior developers today, in order to fill the positions that they dismissed during spring. So, for junior developers it may still be a challenge to find their first or next position. Many companies can hire junior developers as a short-term consultant-assignment, so it is advantageous to be open to these opportunities as a junior developer.</p>
<p><strong>Is the poor reputation of the recruitment profession in tech among developers deserved or overstated?</strong></p>
<p>Emy: Sadly, I do think that it is deserved. I think that many recruiters have the wrong approach when recruiting for developer-positions. I have talked to many, many, many developers about this, and my understanding of the situation is that developers experience that recruiters don’t understand them nor their industry. And above all, many developers think that recruiters are a bit ignorant and uninterested in understanding it.</p>
<p>Recruiters and developers communicate differently, which is natural due to very different professions. Let’s face it, most tech recruiters lack programming skills. That&rsquo;s understandable but since I am the recruiter and in most cases I am the one who expects them to talk to me, it is in my responsibility to educate myself to the extent so that I get a basic understanding of the developer profession and am able to communicate better with them. As a recruiter, and especially as a tech recruiter specializing in IT recruitment, you must have interest in getting to know the IT industry. I am no expert in IT and definitely not in development, but I quickly realized that I at least had to have a basic understanding in order to be able to communicate with the people whom I am trying to reach out to. Therefore I have spent my free time on reading books about IT and I took some online basic courses and bootcamps in development, just in order to understand the technical language. If I don’t have this basic understanding, I will appear frivolous and ignorant in my professional position, and then why would anyone spend their time talking to me?</p>
<p>There will probably always be a gap in the communication between developers and recruiters, recruiters will always be at disadvantage in terms of technology, but I hope that more recruiters understand the problem and start to educate themselves so that in the long run we can earn a better reputation among developers.</p>
<p><strong>What are the major challenges you and your colleagues face as recruiters?</strong></p>
<p>Emy: From a candidate perspective, the major challenge is what we talked about earlier, regarding the poor reputation of the recruiter profession among developers. Since many developers have preconceptions of recruiters even before we reach out to them, it’s always a challenge to turn it around. If developers have had some bad experiences with recruiters before, it is often quite hard to even get to the point where we have a fair chance to prove that not all recruiters are the same. Nothing makes me happier than being the one who disproves the preconceptions or being the one who gives a developer a better experience – but to do that I must succeed with the first part, to break through the noise of bad experiences to the extent that the developer is willing to have a conversation with me.</p>
<p>Another major challenge is also one that I’ve already mentioned. The one which refers to understanding the IT-industry and the developer profession. I once read that “never hire anyone to do a job until you’ve tried to do it yourself first”. This is, of course, difficult in a recruiter’s position since it is impossible to master every language or skill, but I think that the mindset is very wise. It may not be necessary for a recruiter to be familiar with every detail of all programming skills, but I need to understand everything that is stated in a job description and in a developers CV in order to be successful in my profession.</p>
<p>In terms of challenges with clients, one of our biggest challenges is convincing companies to consider junior developers to fill their positions. Many companies ignore the potential of candidates, and stare blindly into the number of years of experience in their resume. In my opinion, the number of years doesn’t say it all. Many CEOs can testify that some of the best people that they’ve hired had very little programming experience when they took on the job, instead they had raw intelligence and knowledge that outweighed the missing experience. But unfortunately, many companies are afraid of hiring more junior developers – especially if it is a replacement recruitment of a more senior developer.</p>
<p><strong>Are developers especially tricky to recruit or are the challenges similar in other fields?</strong></p>
<p>Emy: With experience in recruiting for positions within different industries, from economics and administration to marketing, logistics/shipping, civil engineering and more, I know for sure that it’s trickier to recruit developers than any other profession. It may be due to the high demand, and the poor reputation of recruiters in addition. The shortage of talent and the infinite war over tech candidates make the job of technical recruiters challenging. When recruiting developers, a recruiter’s job doesn’t end after putting a job description on the career site, sitting back and waiting for the right candidate to drop by and say, “Hi I want this job”. It requires being active, putting effort in looking through coding-related sites and working actively with headhunting, since the person who we are looking for is likely to already have a job. I think that a huge contributing factor is that developers don’t “hang out” in the places where we traditionally look for them. Not all developers spend their time on Linkedin, which is the biggest business and employment-oriented social service available, and the number 1 social media tool for recruitment and hiring. While recruiters search for the right competence on Linkedin, developers hang out on Github, Stack Overflow, CodeProject, Hacker News and so on; i.e platforms where recruiters “don’t belong” and in some cases even are prohibited from contacting developers.</p>
<p><strong>I’ve heard it said that recruitment is a numbers game where finding and reaching out to the largest number of candidates is the way to play. Does this match up with your experience?</strong></p>
<p>Emy: I hear what you’re saying, and once again I must disappoint my fellow recruiters out there by answering yes, in some ways it matches up with my experience. I would not call it a game though. A recruiter’s career often depends on getting in touch with the right candidate. And as sad as it is, the fact remains that our reputation among developers have seen better days. And as a result of this fact, it is likely that we get 1 or 2 positive answers from contacting 30 candidates for a position. From my point of view, and from doing some research by reading and talking to other recruiters, I find that this is partly due to a lack of knowledge. I think that recruiters who give this impression, who unconsciously create this reputation, don’t understand the positions that they are recruiting for. They are fanatically searching broadly, contacting every candidate that they find at least a little bit appropriate for the position, hoping that they will find the right person among all of the people whom they are contacting. This is absolutely the wrong way of doing it, since it put us recruiters in a bad light, appearing unserious and unprofessional.</p>
<p>Then of course, it is not unthinkable that some recruiters do this on purpose in order to challenge a colleague for fun, or gain a bigger network. Many recruiters also receive high demands from managers to achieve different goals (including a number of contacted candidates) which may result in these kinds of “numbers games”. But I find it hard to believe that professional recruiters in general would aim to do this just for fun. I hope that the majority of recruiters understand that these habits destroy the rumor of our profession even more.</p>
<p><strong>I personally care a lot about finding work for junior developers as I know how hard it can be to find that first step into the business. Many are being trained but very few companies are in my experience willing to invest in levelling people up. What do you think we should be doing to improve that situation in this industry?</strong></p>
<p>Emy: First of all, I think that this is a very important question that more recruiters should work with actively. In my profession as a Tech Recruiter, I am actively working with questions about recruiting on potential. I think that it is important for companies to rethink the idea that years of experience translate to performance. Just because a developer has done a certain type of job before, doesn’t implicate that he or she is necessarily good at it. More companies should think about what’s important to their company. Hiring a developer who is highly experienced can mean that they are very skilled at their job, which most companies strive to find. When recruiting for a replacement position, many companies aim to find a developer who possesses the same skills and experience as the person who they are replacing.</p>
<p>What many companies and hiring managers forget, is that an abundance of experience might just as well come with a very high salary requirement, an inflexibility toward change and difficulties in adapting to the corporate culture. A junior developer on the other hand, is likely to offer a fresh outlook for the company and is often more motivated to gain more knowledge and experience in the company’s field. In addition, a shorter career history may indicate that this person won’t have as many negative habits as a more experienced candidate. Moreover, hiring a candidate with less experience and giving them a chance to prove themselves will drive employee loyalty which is extremely important.</p>
<p>Skills can always be taught, but motivation and ambition cannot. Instead of putting all of their focus on the hard skills, companies should take vital soft skills that establish potential, in greater consideration. I am convinced that companies and hiring managers must transition into a new mindset when it comes to talent spotting, one in which an assessment of a candidate is based not on direct experience and competencies, but on soft skills and potential.</p>
<p><strong>What advice do you have for developers to make the most of their relationships with recruiters?</strong></p>
<p>Emy: To be open-minded and to reach out in time. Even if you’re not looking for a new job as of today, it is a good thing to establish a contact. Good recruiters won’t just throw random job descriptions in your face and expect you to grab the first opportunity that comes up. Instead, a good recruiter will take time getting to know you, try to understand what your biggest ambitions are and what you want to do next, and that’s a time consuming process. Some of the best moments I have had as a tech recruiter have been when I’ve had a chance to really get to know the candidate, and thereafter, I’ve done my utmost to help him or her to land the right job, at the right company. The ideal situation isn’t when you contact a recruiter by the time that you’re already in a hurry to find another job, by the time that your previous employment has already ended. Establish a contact today, explain that you’re not interested in a new job right now but that you would like to get in touch so that, in the long run, the two of you can work together in order to find your next challenge. I think that the conclusion is that developers should consider recruiters more as a friendly job coach. Consider us as a friend, one with a very large network that can offer many valuable contacts and opportunities. A friend who can set up a meeting between you and a hiring manager at your dream company, a friend who has inside information about specific companies and positions and who can provide you with career advice that increases your chances of landing the job. Working with a recruiter will get you closer to that dream job you’ve been trying to land. But don’t just work with any recruiter who reaches out, do your research: ask what companies they usually work with, what positions they’ve successfully filled historically. If you are an IT developer, it makes no sense working with a financial recruiter who specializes in accounting. Find a recruiter who is specialized in your field. The candidate/recruiter relationship should be a relationship of understanding what the two of you can do for one another. It is, after all, your future we’re talking about.</p>
<p><strong>Finally, thanks for answering these questions. And where can developers that are curious to get in touch reach you personally and where do you work?</strong></p>
<p>Emy: Thank you for the possibility to do this Q&amp;A! I really hope that I have answered at least some of the most common questions and concerns regarding recruiters. Developers are more than welcome to contact me, either through Linkedin (<a href="https://www.linkedin.com/in/emywennerbergkristoffersson/">Emy Wennerberg Kristoffersson</a>) or by email, <a href="mailto:emy.kristoffersson@bonsai.se">emy.kristoffersson@bonsai.se</a>.</p>
<hr>
<p>So, dear reader. Was this useful or interesting to you? Would you be interested in me tracking down more of these proactive and deeply engaged recruiters that you could get in touch with?</p>
<p>Hell, I&rsquo;ll poll it and pull the result from my logs:</p>
<ul>
<li><a href="/poll-qa-id-be-interested.html">I&rsquo;d be interested</a></li>
<li><a href="/poll-qa-dont-bother.html">Don&rsquo;t bother</a></li>
</ul>
<p>Thanks for reading. As always, reach out to me on <a href="mailto:lars@underjord.io">lars@underjord.io</a> via email or on Twitter <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. I&rsquo;m very curious to hear what you think. If you want to follow my thinking and what I&rsquo;m up to you&rsquo;ll find the newsletter signup further down the page. I write different but related things there.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>The Mac is losing me</title>
      <link>https://underjord.io/the-mac-is-losing-me.html</link>
      <pubDate>Wed, 18 Nov 2020 07:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/the-mac-is-losing-me.html</guid>
      <description>I&amp;rsquo;ve been mostly happy using a Mac since I got myself my first computer earned with programmer money. I believe it was a mid 2009 15&amp;quot; MacBook Pro. That was a computer I used at least until 2016 which I consider very decent usable life. At that point I had replaced the hard-drive with an SSD, upgraded the RAM and switched a battery that was worn out. I stopped using it when it just straight died some time in 2016.</description>
      <content:encoded><![CDATA[ <p>I&rsquo;ve been mostly happy using a Mac since I got myself my first computer earned with programmer money. I believe it was a mid 2009 15&quot; MacBook Pro. That was a computer I used at least until 2016 which I consider very decent usable life. At that point I had replaced the hard-drive with an SSD, upgraded the RAM and switched a battery that was worn out. I stopped using it when it just straight died some time in 2016.</p>
<h2 id="my-history-with-the-mac">My history with the Mac</h2>
<p>So what was that computer to me? It was an extremely well-built and solid-feeling piece of aluminium. A cool keyboard backlight. The magsafe charger and a bunch of USB ports. The keyboard and touchpad were best in class. The build quality was better than any laptop I had owned before. Partly because I had only bought the cheap ones before but also because they really were a step above at the time.</p>
<p>I got it primarily on recommendation from a friendly hacker who&rsquo;s recommendations had never lead me wrong before. He spoke well of the underlying UNIX and the experience of using it. Fast, clean and visually pleasing. I think he mostly really liked the backlit keyboard, very hackerly. His recommendation held true, this computer was very good to me.</p>
<p>I&rsquo;ve also been the admin on an Xserve server. That was wild. Neat UIs but buggy and finicky as all hell.</p>
<p>Since then I&rsquo;ve used the second generation Macbook Air (I believe 2nd, first wedge version) at 11&quot;. That was a cool little machine and did what it did very nicely.</p>
<p>Hardware aside, MacOS in it&rsquo;s earlier incarnations on these computers was always a snappy and competent experience. A polished surface which did a bunch of stuff under the hood that generally made it work better than the different Ubuntu desktop environments and various Windows versions I&rsquo;ve had before. Things like Wifi and even Bluetooth felt good in a way they never had before.</p>
<p>A lot of it was the visual polish and the extremely snappy UI. But in total the experience was just great. Spotlight was glorious. I think my first upgrade was Snow Leopard which was generally a very good update as it focused on performance and stability.</p>
<p>As a developer&rsquo;s machine it was fast enough, competent enough, got out of the way and the UNIX underpinnings meant I didn&rsquo;t miss Linux at all. I&rsquo;ve never been able to really connect with the equivalent powershell stuff in Windows. I guess I just like UNIX.</p>
<p>The only bad thing I can say about my early years of MacBook Pro usage was that the trackpad and the Magic Trackpad I got eventually are probably some of the biggest culprits in some of the RSI-style hand pain I&rsquo;ve been dealing with. Trackpads are just murder on my hands and I worked loads off of that setup for a number of years. Took a while before I realized that it was trackpad-related.</p>
<p>Beyond that I&rsquo;ve had some refurb Macs for my wife and assorted family, some 13&quot; MBP for work at one point and then a 15&quot; butterfly touchbar MBP for work. I think that was when I started to feel that Apple was diverging from my preferences in the MBP line.</p>
<h2 id="my-current-experience">My current experience</h2>
<p>That&rsquo;s the same computer I have and work on now and it is.. fine? Maybe just OK. Not great. I&rsquo;ve had some keys getting stuck but fixable with canned air. I don&rsquo;t like the touchbar, it has been between useless and an actual hindrance. The TouchID power button is good though. I don&rsquo;t like living in dongle-town though I mostly like USB C in the long run.</p>
<p>The reason it has been mostly fine for me is that I keep it on tray mounted on a VESA arm, dangling dongles like a technical octopus and I use external peripherals for input.</p>
<p>It gets really hot and loud and then it performs incredibly poorly. So I guess this is one of the throttliest generations. I think I had the &ldquo;don&rsquo;t charge it on the wrong side&rdquo; problem as well. Some of the CPU shenanigans have calmed down as I installed the Turbo Boost Switcher tool to just disable the Turbo Boost, removing performance for peace and quiet.</p>
<p>As I&rsquo;ve been using these devices it has become increasingly annoying to figure out how to install &ldquo;unknown&rdquo; apps. I need some non-discoverable terminal incantation to get the option to accept installing things that are unsigned. There&rsquo;s always a new piece getting locked down. And while I think that&rsquo;s often to the benefit of the average consumer, I&rsquo;m not that. And I just get more annoyed.</p>
<p>I&rsquo;ve been frustrated about the uninspiring performance delivered for the incredible brand markup that Apple charges. I don&rsquo;t mind the computer being expensive if the experience is good and the hardware reasonable. The experience feels like it is slipping, especially for my needs and the hardware has just been getting less impressive to me.</p>
<h2 id="the-hardware">The hardware</h2>
<p>My gaming computer has a Ryzen. For a while I did my dev on that as we had just moved to our house and the office wasn&rsquo;t finalized. Woof, aside from running Windows as a dev environment which I didn&rsquo;t enjoy there was some serious upside on that machine.</p>
<p>On the Mac my options are very limited. I can&rsquo;t get a Ryzen, I can&rsquo;t get anything modern with Intel or meaningfully upgradable at all. The Mac Pro doesn&rsquo;t count. It comes underspeced at hilarious prices. I like some of the design decisions but the price-point doesn&rsquo;t make any kind of sense for me and what I do. I can&rsquo;t buy an interesting Mac from a performance standpoint.</p>
<p>Or can I? Well, they just announced the M1 chip and ARM Macs are now a fact. I think I might get one at some point. For a travel laptop I don&rsquo;t think the rest of the industry is ready to fight Apple. Battery life and good bang for buck power might actually keep a Mac in my life for that. But I feel like the general trend is away from what I want. Or I might just use my iPad Pro for that use-case.</p>
<p>I think the M1 will be quite impressive when the benchmarks roll in. I&rsquo;m sure it will suit many people for real-world use-cases as well. However, from the first presentation on it and the first batch of Macs I don&rsquo;t feel like the direction is for me. IO was heavily sacrificed. Upgradability is pretty much out the window. These things can be fine for a travel device for me where battery and weight are primary concerns. In that regard the new Air looks pretty good.</p>
<h2 id="the-software">The software</h2>
<p>Beyond that the coming OS, Big Sur, is taking MacOS in a direction I dislike. Catalina was quite messy and felt like it took steps toward walling off the Mac. Big Sur seems even more heavy-handed in that area and finally the M1 can push that even further if Apple feels like it. My trust is eroding on letting Apple set the tone for my computing life.</p>
<p>Don&rsquo;t get me wrong, I think their approach to this transition is incredibly neat with Rosetta2 and how they are using the bytecode stuff with the App Store and whatnot. And the possibility to run iOS and iPad apps natively could be very useful. But none of this really moves the needle for me.</p>
<h2 id="so-whats-next">So what&rsquo;s next?</h2>
<p>With my office in the garage as my primary work location I&rsquo;m looking to transition to a desktop computer with lots of power. It will run Linux. Marking my first major return to desktop linux as a daily driver in a bundle of years. And it will run as light a desktop environment as I can stomach. I just wanted a stupid amount of performance to offset som of the UX niceties I know I will miss or have to customize  on my own.</p>
<p>I&rsquo;m excited about exercising my development tools on a strong modern CPU rather than the throttled mess my current laptop offers. But I&rsquo;m not thrilled about this move beyond the hardware aspect. I liked MacOS but I just don&rsquo;t feel like Apple gives much of a care for the things I care about. And I feel like the software side on the Mac is slipping, consistently.</p>
<p>If they released an expandable Mac that wasn&rsquo;t ridiculously expensive they would really make me think twice. But that feels unlikely.</p>
<p>So I&rsquo;ll build myself a monstrous machine that can compensate in raw power for the potential lack of elegance and which offers unbounded flexibility rather than a poorly tended garden that someone keeps trying to wall in.</p>
<p>I&rsquo;m not happy about it. I&rsquo;ve generally enjoyed using my Macs. But when someone says &ldquo;we&rsquo;ll give you the full experience&rdquo;, settling into that requires trust. And my trust that Apple and me are in alignment keeps fading.</p>
<p>Also, I&rsquo;m developing a lot with heavily concurrent workloads. So I really look forward to exercising more cores. 2021, year of the Linux desktop (for me).</p>
<p>If you have thoughts, comments or a hell yeah you want to share about this topic or maybe you want me to cover some specific part of my transition here, let me know either via <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. My first thoughts and my build might just show up first on my newsletter, so consider signing up for that below. It don&rsquo;t track. Thanks for reading.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>The BEAM marches forward</title>
      <link>https://underjord.io/the-beam-marches-forward.html</link>
      <pubDate>Mon, 26 Oct 2020 09:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/the-beam-marches-forward.html</guid>
      <description>The BEAM is the virtual machine that Erlang and Elixir runs on. It is widely cited as a battle-tested piece of software though I don&amp;rsquo;t know in which wars it has seen action. It has definitely paid its dues in the telecom space as well as globally scaled projects such as Whatsapp and Discord. It is well suited to tackle soft-realtime distributed systems with heavy concurrency. It has been a good platform chugging along.</description>
      <content:encoded><![CDATA[ <p>The BEAM is the virtual machine that Erlang and Elixir runs on. It is widely cited as a battle-tested piece of software though I don&rsquo;t know in which wars it has seen action. It has definitely paid its dues in the telecom space as well as globally scaled projects such as Whatsapp and Discord. It is well suited to tackle soft-realtime distributed systems with heavy concurrency. It has been a good platform chugging along. And with a small team at Ericsson responsible for much of its continuing development it has been managed in a deeply pragmatic way. Erlang has always been a bit of a secret and silent success. Almost no-one uses it if you look at market shares. But among the ones that use it there seems to be a very positive consensus. And then Elixir came and caused a bit of a boom. I think the BEAM has benefited from Elixir and Elixir wouldn&rsquo;t exist without the BEAM. With that bit of background I&rsquo;d like to shine a light on some cool developments that I think makes the BEAM more interesting or even uniquely interesting in the future.</p>
<h2 id="the-jit-is-here-soon-otp-24">The JIT is here (soon, OTP 24)</h2>
<p>With OTP 24 landing sometime next year we are going to get the a JIT for the BEAM. Based on the project <a href="https://github.com/asmjit/asmjit">AsmJit</a> this will mean that some BEAM code will be translated to native instructions. It will not be the kind of warm-up-for-performance-gains JIT that I&rsquo;ve heard of in PyPy but rather significantly simpler. The goal of the project was to introduce a JIT that could give performance gains for some cases but would not cause any performance regressions. A pragmatic and laudable approach. Considering this made the Jason JSON-library (written in Elixir) beat the Jiffy JSON-library (written as a C NIF) in <strong>some</strong> tests I think this has the potential to obviate the need for some NIF implementations. Avoiding reaching out to the lower level code that is more capable but more dangerous is a good win.</p>
<p>Anyone running RabbitMQ should look forward to the update as measurements indicate 30-50% increased message throughput. Which is a nice thing to get for no code changes at all.</p>
<p>Pushing the performance of the BEAM closer to native is magnificent. To be clear the BEAM is already quite a good performer. I would put Erlang and Elixir at the abstraction level of languages like Python/Ruby/Node.js. Python and Ruby are poor performers. The Python ML stuff all goes into C++ or similar for performance. I&rsquo;ve worked a bunch with Python and the things I hear from the Ruby world makes them sound quite equivalent in performance. They are a bit slow and can only <a href="/more-than-one-thing-at-a-time.html">do one thing at a time</a>. Node.js is a bit different. It can do multiple things at a time, if you append asterisks and squint. It does it largely the same way Python + Gevent does it. This approach is incredibly susceptible to CPU-bound work causing head-of-line blocking. It becomes the single most important consideration for building a performant application &ldquo;get to IO, don&rsquo;t compute&rdquo;. V8 that Node.js runs on is heavily optimized and fast for such a dynamic language. I think the BEAM provides a better approach that can deliver comparable results without as many footguns (opportunities for shooting yourself in the foot). But getting better at the raw crunching is a big gain with this JIT implementation and I look forward to the release.</p>
<h2 id="lumen---static-compilation--wasm">Lumen - Static compilation &amp; WASM</h2>
<p>The <a href="https://getfirefly.org/">Lumen project</a> is a huge effort by a gang of open source developers and DockYard to implement a compiler (and more) that can take Elixir and Erlang into the browser. By solving that they end up solving static compilation for Erlang and Elixir as well. So this isn&rsquo;t compiling and shipping the BEAM to the browser. This is a faithful reimplementation of the BEAM functionality in a way that allows it to be compiled statically. It uses LLVM and requires quite a bit of effort both in development and in wrangling the Web Assembly work group process stuff to make sure that the standard is not entirely run by Object-Oriented Programming needs.</p>
<p>I don&rsquo;t think Lumen will replace the BEAM. The BEAM has a brilliant track record for long-running services and distributed computing that the Lumen project do not even attempt to achieve right now. Instead the Lumen project will allow Elixir and Erlang to move into spaces where the BEAM might be a bit too heavy and still provide the same guarantees. Typically I see it being good for command line tools, web frontends (super interesting to consider the Actor model going there), serverless/edge computing and potentially with WASM competing with Docker as a delivery mechanism for code in Kubernetes, using something like <a href="https://github.com/deislabs/krustlet">Krustlet</a> (<a href="https://player.fm/series/software-sessions/webassembly-on-the-server-with-krustlet">good podcast episode on WASM/Krustlet</a>). It&rsquo;s probably Cloud Native or something. Who knows.</p>
<p>What gives Lumen the potential to be a better fit in these circumstances is that it can optimize for filesize (by cutting out hot code updates) and it is likely able to start much faster. Lumen is written in Rust. Which seems to be the popular choice around Web Assembly from what I&rsquo;ve seen. Lumen is still an early release project and not fit for production. But it is beeing actively pushed forward.</p>
<h2 id="nerves---an-iot-platform-with-minimal-suck">Nerves - An IoT platform with minimal suck</h2>
<p>The <a href="https://www.nerves-project.org/">Nerves project</a> is fantastic. I&rsquo;m a hardware hobbyist, not an IoT dev but I&rsquo;ve worked a fair bit with Nerves and it is so, so good. What Nerves gives you when working with a Raspberry Pi for example is a way to let your code run all of the device. The BEAM is basically your operating system on top of a minimal Linux installation. The Linux you have is based on the solid foundation of Buildroot so it is quite feasible to modify it as you see fit. The big idea is that if you are running a Linux-level SBC already you might as well build on something that gives you the guarantees of the BEAM.</p>
<p>Beyond that the default setup encodes a lot of good embedded practices by default so that you avoid bricking devices with firmware updates, you get easy support for pushing firmware over the network or USB and much, much more.</p>
<p>There are a ton of good libraries for sensors and assorted hardware, as well as the common protocols like GPIO/SPI/I2C/UART. Networking support is well considered and has been reworked since I first started using Nerves a few years back (and it worked well then too). BLE is getting more and more good support recently.</p>
<p>The project also created <a href="https://www.nerves-hub.org/">NervesHub</a> which is a solution for managing a fleet of devices by securely providing firmware updates, allowing the switching on of a remote console on devices if that&rsquo;s a need on your product. I think the most recent stuff is a UI revamp and some serious work on binary diffed patches to minimize firmware update sizes for data-constrained deployments.</p>
<p>This is very much a production project and people are shipping hardware with Nerves. It keeps marching forward.</p>
<h2 id="the-beam-can-be-your-entire-application">The BEAM can be your entire application</h2>
<p>Saša Jurić, author of the much-acclaimed Elixir in Action book has produced a library called <a href="https://github.com/sasa1977/site_encrypt">site_encrypt</a>. It allows you to handle LetsEncrypt configuration without a separate webserver or actually using certbot.</p>
<p>Now this library is good and meaningful in its own right but the underlying idea is why I bring it up. The BEAM can be your entire application. This is something I&rsquo;ve realized over time. Where in Python you would reach for Gunicorn to run you Django app and Nginx to protect Gunicorn from the big bad world.. The BEAM is made for this. Introducing an intermediate layer of Nginx (or another HTTP server) might actually be detrimental in that you now have two things you need to configure correctly and two pools of multi-core processing workers that care about this request/response cycle and can independently screw it up.</p>
<p>The BEAM was always built for this. OTP has a lot weird corners where you find interesting libraries such as <code>wx</code> for WxWidgets (window management) and <code>ssh</code> for both SSH client and server work I believe. Because it is meant to be delivered as a full solution. It can run and manage multiple different types of work inside of it. Gracefully. It doesn&rsquo;t replace Kubernetes for the large deployment or polyglot environments. But it might very well mean you don&rsquo;t actually need to go there early. Or you can reduce how much Ops you need in your Dev. If your entire stack is Elixir or Erlang front to back I think you have empowered your developers significantly.</p>
<p>There is already a move towards this where tools are converging that give us a lot of things out of the box that we&rsquo;d otherwise need to move outside our application for. These are pragmatic 80-90% solutions. The normal solutions are still all there if you need to reach for them. But maybe you don&rsquo;t. I see these as moves in the same vein:</p>
<ul>
<li>LiveView - We can reduce the amount of frontend we need to build that isn&rsquo;t BEAM code (Elixir or Erlang), in some cases get rid of it entirely.</li>
<li>Live Dashboard - Application insights right in your application stack instead of pushing them out to another solution.</li>
<li>Phoenix PubSub - Distributed PubSub without requiring coordination via something like Redis.</li>
<li>Phoenix Channels - Distributed PubSub over WebSockets using the above PubSub to coordinate delivery.</li>
<li>Phoenix Presence - Distribute Presence. A CRDT-powered thing for maintaining information about if someone is connected to a channel or not, like chatrooms and online/offline. Using Channels.</li>
</ul>
<p>Lowering complexity by keeping the solutions in a system you understand well is potentially very powerful. At some point many projects will need to pick up external dependencies such as Nginx, Redis or whatever. But I think there is something compelling about building your application inside a system that can do all of it quite well. Elixir and Phoenix already have significant mind-share in the startup world. I wouldn&rsquo;t be surprised if this ends up being a very popular solution for startups. No frontend-specific code for the MVP, no New Relic or Mixpanel bill we make do with the Live Dashboard. Distribution is Erlang distribution + Swarm/Horde/Libcluster or something like that when we need it. And if we need &ldquo;real-time&rdquo; functionality we are well and truly covered.</p>
<p>I think this is a pragmatic approach that aligns with what Erlang has always strived to be, what Elixir has turned out to be and what many in the community want. I look forward to seeing if it grows the way I hope. We discuss this further in an upcoming episode of Elixir Mix (the podcast) with Saša Jurić. So keep an eye out for that.</p>
<p>I think this might be a future topic on its own. I am not done with this.</p>
<h2 id="as-easy-as-anything-else">As easy as anything else</h2>
<p>While the BEAM is incredibly capable, Elixir and Erlang are not difficult. There is usually a bit of a hump going from Object-oriented programming to Functional programming. But beyond that you are mostly working at the abstaction level of something like Python while not needing to be as mindful of class hierarchies. I find it easier to reason about. It isn&rsquo;t trivial. No programming is at the start. But you aren&rsquo;t working in some mad realm of math and abstract thought. At least not that you need to care about. Much like &ldquo;being good at math&rdquo; is not required for programming. You don&rsquo;t need to be some kind of hardcore whizz to get stuff done in Elixir or Erlang. Elixir looks like Ruby if that helps. Or so they say. I never did Ruby.</p>
<p>It is definitely easier to reason about than Node.js and the event loop ever was to me.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I have a strong confidence in the BEAM as a runtime/VM to build my software on. There are certainly places where it fits poorly. I primarily work in web service development and there it fits perfectly.</p>
<p>If you have thoughts, feelings or even &ldquo;more of a comment really&rdquo; feel free to reach out at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or via my Twitter <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. My newsletter is available below if you have interest in further writing.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Supervision trees, an example in Elixir</title>
      <link>https://underjord.io/elixir-supervision-trees-an-example.html</link>
      <pubDate>Wed, 21 Oct 2020 07:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/elixir-supervision-trees-an-example.html</guid>
      <description>So any time recently that I&amp;rsquo;ve gone looking for a good overview of supervision trees in Elixir I haven&amp;rsquo;t found what I want. I&amp;rsquo;m pretty sure I used to find some that covered making simple supervisors and workers without assuming you want a module for each Supervisor. I now believe those were following ye olde Supervisor.Spec which had helpers for that. How to make a module based Supervisor is in the docs so I won&amp;rsquo;t be spending time on that.</description>
      <content:encoded><![CDATA[ <p>So any time recently that I&rsquo;ve gone looking for a good overview of supervision trees in Elixir I haven&rsquo;t found what I want. I&rsquo;m pretty sure I used to find some that covered making simple supervisors and workers without assuming you want a module for each Supervisor. I now believe those were following ye olde <code>Supervisor.Spec</code> which had helpers for that. How to make a <a href="https://hexdocs.pm/elixir/Supervisor.html#module-module-based-supervisors">module based Supervisor is in the docs</a> so I won&rsquo;t be spending time on that.</p>
<p>Since that method was deprecated I figured it was time I bite the bullet and get comfortable with child specs and the way they work and figure out if I can avoid creating a module for normal use of a simple bog standard Supervisor. Spoiler: I could.</p>
<p><a href="https://github.com/lawik/supervisor_sample">This repo has all the code</a> for what I built. So if we dive into <code>lib/supervisor_sample/application.ex</code> we find the following:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#75715e"># ..</span>
  <span style="color:#66d9ef">def</span> start(_type, _args) <span style="color:#66d9ef">do</span>
    children <span style="color:#f92672">=</span> [
      worker(<span style="color:#e6db74">:root_worker</span>),
      supervisor(
        <span style="color:#e6db74">:one_for_one</span>,
        [
          worker(<span style="color:#e6db74">:worker_1</span>),
          worker(<span style="color:#e6db74">:worker_2</span>)
        ],
        <span style="color:#e6db74">name</span>: <span style="color:#e6db74">:supervisor_1</span>
      ),
      supervisor(
        <span style="color:#e6db74">:rest_for_one</span>,
        [
          worker(<span style="color:#e6db74">:worker_3</span>),
          worker(<span style="color:#e6db74">:worker_4</span>),
          worker(<span style="color:#e6db74">:worker_5</span>),
          supervisor(
            <span style="color:#e6db74">:one_for_one</span>,
            [worker(<span style="color:#e6db74">:subworker_1</span>)],
            <span style="color:#e6db74">name</span>: <span style="color:#e6db74">:subsupervisor_1</span>
          )
        ],
        <span style="color:#e6db74">name</span>: <span style="color:#e6db74">:supervisor_2</span>
      ),
      supervisor(
        <span style="color:#e6db74">:one_for_all</span>,
        [
          worker(<span style="color:#e6db74">:worker_6</span>),
          worker(<span style="color:#e6db74">:worker_7</span>),
          worker(<span style="color:#e6db74">:worker_8</span>)
        ],
        <span style="color:#e6db74">name</span>: <span style="color:#e6db74">:supervisor_3</span>
      ),
      worker(<span style="color:#e6db74">:transient_root_worker</span>, <span style="color:#e6db74">:transient</span>)
    ]

    <span style="color:#75715e"># The root of the tree is a supervisor that runs everything we defined above</span>
    opts <span style="color:#f92672">=</span> [<span style="color:#e6db74">strategy</span>: <span style="color:#e6db74">:one_for_one</span>, <span style="color:#e6db74">name</span>: <span style="color:#a6e22e">SupervisorSample.Supervisor</span>]
    <span style="color:#a6e22e">Supervisor</span><span style="color:#f92672">.</span>start_link(children, opts)
  <span style="color:#66d9ef">end</span>

  <span style="color:#75715e"># ..</span></code></pre></div>
  </div>

<p><a href="https://github.com/lawik/supervisor_sample/blob/master/lib/supervisor_sample/application.ex">GitHub link</a></p>
<p>This is my supervision tree definition. It uses utility functions <code>supervisor</code> and <code>worker</code> to make it easier to get an overview. These functions generate a <a href="https://hexdocs.pm/elixir/Supervisor.html#module-child-specification">child spec</a> with each call. Even with some experience using OTP I never really spent any time understanding child specs. I won&rsquo;t go too deep into them here, the docs above honestly cover it but I&rsquo;ll try to make it digestible in my own way.</p>
<p>This is what the supervisor child spec looks like if I call <code>supervisor(:one_for_one, [], name: :my_supervisor)</code>:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">%{
  <span style="color:#e6db74">id</span>: <span style="color:#f92672">-</span><span style="color:#ae81ff">576460752303423326</span>,
  <span style="color:#e6db74">start</span>: {<span style="color:#a6e22e">Supervisor</span>, <span style="color:#e6db74">:start_link</span>,
   [[], [<span style="color:#e6db74">strategy</span>: <span style="color:#e6db74">:one_for_one</span>, <span style="color:#e6db74">name</span>: <span style="color:#e6db74">:my_supervisor</span>]]}
}</code></pre></div>
  </div>

<p>So the <code>:id</code> is fairly arbitrary. According to my sources, I asked on Twitter, more knowledgeable people responded, it is used for restarts and whatnot. It can also be used when doing interesting things with your supervisor implementation. But it is not important aside from needing to be unique, unless you have specific plans for it.</p>
<p>The <code>:start</code> key gives what we are actually starting. The format might become familiar to you. A tuple with a module atom, a function atom and a list of args to pass into the function. This matches the signature of <code>apply/3</code>. In this case the args are a list of children and some options. Because that is what the <code>Supervisor</code> module takes for the function <code>start_link</code>.</p>
<p>That&rsquo;s all that is necessary. The child spec docs cover the other options. We can check our worker example as well:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">%{
  <span style="color:#e6db74">id</span>: <span style="color:#f92672">-</span><span style="color:#ae81ff">576460752303423294</span>,
  <span style="color:#e6db74">restart</span>: <span style="color:#e6db74">:permanent</span>,
  <span style="color:#e6db74">start</span>: {<span style="color:#a6e22e">SupervisorSample.Worker</span>, <span style="color:#e6db74">:start_link</span>,
   [[<span style="color:#e6db74">label</span>: <span style="color:#e6db74">:my_worker</span>, <span style="color:#e6db74">name</span>: <span style="color:#e6db74">:my_worker</span>]]}
}</code></pre></div>
  </div>

<p>Here we also use the <code>:restart</code> key because I have an example with <code>:transient</code> and so I set it explicitly.</p>
<p>These maps aren&rsquo;t complicated to create and they shouldn&rsquo;t be intimidating. But they are visually bulky and I think there are many ways of building the tree that could be done in a visually pleasing and less noisy way. That&rsquo;s what I use the utility functions in the sample project for.</p>
<h2 id="genserver-and-child_spec1">GenServer and child_spec/1</h2>
<p>In many cases you don&rsquo;t actually have to create the child spec yourself. Anything that is a GenServer will have a <code>child_spec/1</code> already included. So then we can reduce the above to <code>{SupervisorSample.Worker, name: :my_worker}</code>. Or without a name it could be <code>SupervisorSample.Worker</code>. Very clean. A bit of convention saving you a bunch of repetitive detail. But the <code>Supervisor</code> module doesn&rsquo;t offer <code>child_spec/1</code>. It offers  <code>child_spec/2</code> which is used to modify child_specs for module&rsquo;s that already have them. Usually because you want to override something in the default child spec. Such as the <code>:id</code>.</p>
<p>Most libraries you&rsquo;d use where you need to start an instance of them as part of your supervision tree would already provide you with a child_spec. If they don&rsquo;t, you can create one yourself quite easily just as we did for Supervisor.</p>
<p>Another thing you can do is create a module that provides a <code>child_spec/1</code> for starting a supervisor as detailed in <a href="https://elixirforum.com/t/is-there-a-reason-we-dont-have-a-supervisor-child-spec-1/35033/2">this converstation on the forum</a>. The code is partial, but I think the idea is complete. Then you could use that module instead of Supervisor.</p>
<h2 id="the-tests--strategies">The tests &amp; strategies</h2>
<p>So the supervision tree above showcases the different strategies available. It also shows that we can supervise a supervisor, that&rsquo;s how you build a bigger tree with processes that depend on one another.</p>
<p>To demonstrate how these work I&rsquo;ll direct you to <code>test/supervisor_sample_test.exs</code>. Every test looks something like this:</p>

  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#75715e"># ..</span>
  test <span style="color:#e6db74">&#34;restart root worker&#34;</span> <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Worker</span><span style="color:#f92672">.</span>stop(<span style="color:#e6db74">:root_worker</span>)

    <span style="color:#75715e"># Should restart</span>
    assert_receive {<span style="color:#e6db74">:stopped</span>, <span style="color:#e6db74">:root_worker</span>}
    assert_receive {<span style="color:#e6db74">:started</span>, <span style="color:#e6db74">:root_worker</span>}
    <span style="color:#75715e"># Shouldn&#39;t restart anything else</span>
    refute_received {<span style="color:#e6db74">:stopped</span>, _}
    refute_received {<span style="color:#e6db74">:started</span>, _}
  <span style="color:#66d9ef">end</span>
  <span style="color:#75715e"># ..</span></code></pre></div>
  </div>

<p>They follow this model of, okay, let&rsquo;s tell a worker process to stop and assert that we receive the messages we expect. I expect this one to be stopped and then restarted. These messages are sent in <code>lib/supervisor_Sample/worker.ex</code> and we register for listening in the <code>setup</code> hook in the test module.</p>
<p>The different <a href="https://hexdocs.pm/elixir/Supervisor.html#module-strategies">strategies are succinctly explained</a> in the Elixir docs. I&rsquo;ll briefly restate it here:</p>
<ul>
<li><code>:one_for_one</code>, the supervisor will restart each child separately if they terminate. Only if the supervisor goes down, does it affect the whole group.</li>
<li><code>:one_for_all</code>, if a single child process terminates the whole set of child processes will be restarted.</li>
<li><code>:rest_for_one</code>, this one is interesting. If a child process terminates all the children started after it in the list will be restarted. This might seem odd but has some uses.</li>
</ul>
<p>So you can look at the tests to see examples of these behaviors.</p>
<p>This doesn&rsquo;t really cover DynamicSupervisor at all. But that has a lot of its own considerations. It is very useful and maybe I should cover it at some point. Alex Koutmos one of my co-hosts on Elixir Mix has <a href="https://akoutmos.com/post/actor-model-genserver-app-two/#step-5-creating-a-book-process-dynamicsupervisor---a-hrefhttpsgithubcomakoutmosbook_storecommiteef747e-target_blankcommita">a good piece on DynamicSupervisor</a>.</p>
<p>I hope this is helpful to people. Thank you for your attention.</p>
<p>If you have questions, thoughts or more of a comment, really, you can find me on twitter {{ lars_twitter }} or reach me via email {{ lars_email }}.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Stupid solutions: Live server push without JS</title>
      <link>https://underjord.io/live-server-push-without-js.html</link>
      <pubDate>Fri, 25 Sep 2020 17:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/live-server-push-without-js.html</guid>
      <description>So in my post Is this evil? I covered a way of tracking users with CSS. While thinking about those weird ways of using the web I also started thinking about pushing live data to clients without JS. Or at least maintaining a connection. So WebSockets requires JS. WebRTC requires JS. Even HLS (video streaming), which would otherwise be super cool, with captions for accessibility. But no. Or rather, maybe on Apple platforms.</description>
      <content:encoded><![CDATA[ 
<p>
    So in my post <a href="is-this-evil.html">Is this evil?</a> I covered a way of tracking users with CSS. While
    thinking about those weird ways of using the web I also started thinking about pushing live data to clients
    without JS. Or at least maintaining a connection.
    So WebSockets requires JS. WebRTC requires JS. Even HLS (video streaming), which would otherwise
    be super cool, with captions for accessibility. But no. Or rather, maybe on Apple platforms. Eh. Not good enough.
</p>
<p>And then it hit me. From some old Nerves projects I'd seen, that there is a standard for just sending a stream of
    JPEG frames as a video. MJPEG. Did you know about MJPEG? Lots of people don't. It is used by lots of webcams and
    security cameras. Common option for Raspberry Pi hacks as well. MJPEG is super simple which is its big advantage.
</p>
<p>But video is what we expect. I was going for something else. So this could be used for a CI status light, showing
    any amount of visual status information. I use it for this:</p>
<div class="counter" style="text-align: center; border: 1px dashed #ff00ff;"><img alt="live visual readership indicator" loading="lazy" src="https://count.underjord.io/"/></div>
<p>That's live. Or dead if my server falls over.</p>
<p>So how does MJPEG work. Well, you take an <code>&lt;img/&gt;</code> tag and you shove an MJPEG URL into it. Done.</p>
<p>Okay, that's how you use it. Not how it works. I implemented it in Elixir, Elixir is quite good at keeping state and
    serving updates. Links are below. But basically the browser opens the connection, receives some headers and some
    chunks of data and then realizes it is dealing with MJPEG. It wil then just expect the chunks to keep coming.
    Indefinitely. Because this is live video. Frame by frame of JPEG.</p>
<p>The basic code for the MJPEG headers and chunking was lifted from a pi camera repo made by the Nerves team. It had a
    lot of Frank Hunleth and Connor Rigby in it so kudos to those guys as always. This is what I did with it: <a href="https://github.com/lawik/mjpeg/blob/master/lib/mjpeg.ex">lawik/mjpeg</a></p>
<p>My server implementation is here and uses the above code: <a href="https://github.com/lawik/mjpeg_example/blob/master/lib/mjpeg_example.ex">lawik/mjpeg_example</a></p>
<p>So I receive the connection and then that calls my MjpegExample GenServer to persist the connection and keep track of
    how to notify that connection about new data. It also triggers an update to notify everyone already connected.</p>
<p>This is not polished, it is hammered together and I'm curious to see if it falls over the next time I get a decent
    amount of traffic.</p>
<p>I really like this approach because it is a fun hack that simply happens to work across browsers and quite well at
    that. I like how it is just an img element and no frills. I added lazy loading because that works more nicely with
    things like Google Lighthouse scores and the loading experience (your browser doesn't spend a few seconds thinking
    about loading the image).</p>
<p>Unfortunately it is absolutely a poor choice to actually use aside from fun and hacky stuff. There is no good way of
    doing accessibility with it. You can update the pixels and that is it. Unless you can chunk-stream a txt-file in an
    iframe.
    Haven't tried that yet...</p>
<p>So, don't use it. But isn't it pretty neat?</p>
<p>Of course, much like the CSS tracking, this can be used for evilish things. You can absolutely keep track of how long
    someone keeps receiving your frames and use that for your analytics. I also find it neat that it lets me know
    concurrent users. I just log the number along with sending it out because I want to know at what number it breaks
    and out of curiosity but you can probably do some mildly untoward things with this. Oh.. Wait. You could serve
    rotating ads with this. You know what the last frame you sent was so if you get a click on it you can direct that
    particular user to the right thing. New title: Ad placement entirely without JS, ugh, no thanks. Moving on.</p>
<p>If you know how to make this more dirty, hacky and fun or even more useful or accessible feel free to get in touch at
    <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on
    Twitter where I'm <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Vlog - 2020-09-22 - Helping Junior Developers</title>
      <link>https://underjord.io/vlog-2020-09-22.html</link>
      <pubDate>Tue, 22 Sep 2020 15:45:00 +0000</pubDate>
      
      <guid>https://underjord.io/vlog-2020-09-22.html</guid>
      <description> Pondering one of the things I want to make an impact in: helping inexperienced developers get the traction they need to find their way into the industry.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 Sorry, your browser doesn&#39;t support embedded videos.  </description>
      <content:encoded><![CDATA[ 
<p>Pondering one of the things I want to make an impact in: helping inexperienced developers get the
    traction they need to find their way into the industry.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-22-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-22-1080.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-22-720.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-22-thumbnail.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-22-4k.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div> ]]></content:encoded>
    </item>
    
    <item>
      <title>Vlog - 2020-09-17</title>
      <link>https://underjord.io/vlog-2020-09-17.html</link>
      <pubDate>Thu, 17 Sep 2020 15:45:00 +0000</pubDate>
      
      <guid>https://underjord.io/vlog-2020-09-17.html</guid>
      <description> A brief weekly run-down of what I&#39;ve been doing.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 Sorry, your browser doesn&#39;t support embedded videos.  </description>
      <content:encoded><![CDATA[ 
<p>A brief weekly run-down of what I've been doing.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-17-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-17-1080.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-17-720.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-17-thumbnail.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-17-4k.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div> ]]></content:encoded>
    </item>
    
    <item>
      <title>Is this evil?</title>
      <link>https://underjord.io/is-this-evil.html</link>
      <pubDate>Tue, 15 Sep 2020 14:00:01 +0000</pubDate>
      
      <guid>https://underjord.io/is-this-evil.html</guid>
      <description>I try to be a friendly citizen of the web. I try to keep my site in good shape. It should load quickly, track nothing beyond your basic server logs, not hassle you about cookies, GDPR or my newsletter. I do have a newsletter but it won&#39;t pop up in your face here. I try to stay firmly on the side of friendly and a good experience in how I run this site.</description>
      <content:encoded><![CDATA[ 
<p>I try to be a friendly citizen of the web. I try to keep my site in good
   shape. It should load quickly, track nothing beyond your basic server logs,
   not hassle you about cookies, GDPR or my newsletter. I do have a newsletter
   but it won't pop up in your face here. I try to stay firmly on the side of
   friendly and a good experience in how I run this site.
</p>
<p>I do these things because I want my site to be not just bearable but an
   actually great experience in its own quiet way. Just
   as important to me as replying to email in a reasonable timeframe, doing
   what I've told people I will and following up on things that require my
   attention. I try my best and I want this to also be true for my website.
   As a consequence it also happens to have a perfect Lighthouse score most
   of the time.
</p>
<p>A while back I had an idea that smells a bit funny.
   It has the scent of some poor web ethics. But it may also be a very useful.
   I'm not the first to discover it but I also haven't seen a lot about it.
   It seems to be usable for reasonable things but it is also somewhat
   abusable. Also, it definitely breaks the principle of least astonishment
   (or least surprise) in that it makes your browser do things you do not
   expect.
</p>
<p>I'm talking about <strong>tracking users via CSS</strong>. So one of the
   potential downsides of only knowing your traffic via server logs is that
   <a href="https://plausible.io/blog/server-log-analysis#the-total-number-of-unique-visitors">they are not a very good
      indicator of readership</a>
   as outlined by the Plausible Analytics people. Lots of automated traffic
   on the web, bots, crawlers and scrapers. So if there is a way that can
   remove most of the automated traffic without loading any JS, is that a win?
</p>
<p>Consider this CSS:</p>


  <div class="code  css " >
    <div class="meta">
      <span class="language">css</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-css" data-lang="css">    <span style="color:#f92672">body</span>:<span style="color:#a6e22e">hover</span> {
        <span style="color:#66d9ef">background-image</span>: url(<span style="color:#e6db74">&#34;https://underjord.io/you-was-tracked.png&#34;</span>);
    }</code></pre></div>
  </div>


<p>There are endless ways to do it. This has a certain elegance because it
   actually requires mouse interaction. You could probably cover accessibility
   with some use of <code>:focus</code> as well. CSS won't load the URL until
   the selector is hit and it will only load it once for a given page view.
   You'd want a separate URL per page, which is trivial enough, query params
   would likely be enough. I'm convinced that this would give me better data.
</p>
<p><em>I am</em> <strong>not</strong> <em>going to be implementing it.</em>
   I think it's mostly fine to use things like Plausible or Fathom that are
   privacy-oriented analytics platforms because it is a lot better than what
   was going on before and I think the exchange between visitor and site isn't
   entirely clearcut. The negotiation is implicit and the browsers set the
   outer boundary terms. But what we do within that space is what determines
   our character. And I think the best way to serve my audience is to be
   mindful of this stuff, not stare myself blind at the numbers and keep
   working to maintain the trust and attention I've built up.
</p>
<p>The ugly side is that there are things that you can do to indicate every
   single link your users hover, even if they are blocking all JS. You can
   absolutely use this to do things I consider somewhat of an overstep that
   are probably just table stakes in the analytics space. One could also
   measure how long people stay using invisible animations, doing the same
   thing I believe.
</p>
<p>But it is a neat hack and I hadn't really thought about the possibility
   before. It is already somewhat known. I haven't checked who does and doesn't
   do this. So I figured I'd share. Because I do like a neat hack.
</p>
<p>If you want to register your opinion in the aggregate you can just click one
   of the following links and you and all the bots will be recorded in the
   server logs:</p>
<ul>
<li><a href="/it-is-evil.html" target="_blank">It is evil</a></li>
<li><a href="/it-is-not-evil.html" target="_blank">It is not evil</a></li>
</ul>
<p>Where do you draw your lines on privacy? Are there things sites are fine to
   do to understand their traffic and visitors or should even the server log
   be abolished?</p>
<p>I'm genuinely curious how you feel. Feel free to get in touch via <a href="mailto:lars@underjord.io">lars@underjord.io</a> or just find me on Twitter as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Vlog - 2020-09-07</title>
      <link>https://underjord.io/vlog-2020-07-07.html</link>
      <pubDate>Tue, 08 Sep 2020 08:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/vlog-2020-07-07.html</guid>
      <description> A brief weekly run-down of what I&#39;ve been doing and what I&#39;m planning on doing.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 Sorry, your browser doesn&#39;t support embedded videos.   </description>
      <content:encoded><![CDATA[ 
<p>A brief weekly run-down of what I've been doing and what I'm planning on doing.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-07-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-07-1080.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-07-720.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/thumbnail.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-07-4k.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Vlog - 2020-09-07</title>
      <link>https://underjord.io/vlog-2020-09-07.html</link>
      <pubDate>Tue, 08 Sep 2020 08:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/vlog-2020-09-07.html</guid>
      <description> A brief weekly run-down of what I&#39;ve been doing and what I&#39;m planning on doing.
 function switch_video(element) { var src = element.getAttribute(&#34;href&#34;); var video = element.parentElement.querySelector(&#34;video&#34;); var sources = video.querySelector(&#34;source&#34;); video.pause(); sources.setAttribute(&#34;src&#34;, src); video.load(); video.play(); return false; }  4K HD 720 Sorry, your browser doesn&#39;t support embedded videos.  </description>
      <content:encoded><![CDATA[ 
<!-- corrected URL copy of the vlog that had a bad slug -->
<p>A brief weekly run-down of what I've been doing and what I'm planning on doing.</p>
<div class="fancy-video-container">
<script>
        function switch_video(element) {
            var src = element.getAttribute("href");
            var video = element.parentElement.querySelector("video");
            var sources = video.querySelector("source");
            video.pause();
            sources.setAttribute("src", src);
            video.load();
            video.play();
            return false;
        }
    </script>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-07-4k.mp4" onclick="return switch_video(this);" target="_blank">4K</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-07-1080.mp4" onclick="return switch_video(this);" target="_blank">HD</a>
<a href="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-07-720.mp4" onclick="return switch_video(this);" target="_blank">720</a>
<video class="fancy" controls="" poster="https://underjord-video.eu-central-1.linodeobjects.com/thumbnail.jpg">
<source src="https://underjord-video.eu-central-1.linodeobjects.com/vlog-2020-09-07-4k.mp4" type="video/mp4"/>
        Sorry, your browser doesn't support embedded videos.
    </video>
</div> ]]></content:encoded>
    </item>
    
    <item>
      <title>Nerves-keyboard - Running a mechanical keyboard with Elixir</title>
      <link>https://underjord.io/nerves-keyboard-running-a-mechanical-keyboard-with-elixir.html</link>
      <pubDate>Tue, 01 Sep 2020 09:15:00 +0000</pubDate>
      
      <guid>https://underjord.io/nerves-keyboard-running-a-mechanical-keyboard-with-elixir.html</guid>
      <description>Chris Dosé was interviewed by us on Elixir Mix. When he spoke about his work on a Nerves-powered keyboard I knew this was a project I wanted to try out. So I dropped into their dev channel, acquired the hardware (thanks for the help) and have done some playing around with it.  So what is the draw of a Nerves keyboard? Well, you can program it in Elixir. That&#39;s a big one for me personally.</description>
      <content:encoded><![CDATA[ 
<p><a href="https://topenddevs.com/podcasts/elixir-mix/episodes/emx-102-nerves-powered-mechanical-keyboards-with-chris-dose">
        Chris Dosé was interviewed</a> by us on Elixir Mix. When he spoke about his work on a Nerves-powered keyboard I knew this was a
    project I wanted to try out. So I dropped into their dev channel, acquired the hardware (thanks for the help) and have done some playing
    around with it.
</p>
<p>
    So what is the draw of a Nerves keyboard? Well, you can program it in Elixir. That's a big one for me personally.
    It also means that compared to a lot of high-level languages driving hardware you have pretty strong performance
    characteristics, resilience and specifically with Nerves a lot of best practices for running embedded hardware.
    It makes managing firmware updates easier, it mitigates corruption on SD cards and lots more. As an Elixir
    developer the pure convenience of customizing the keyboard in my favored language is of course delightful.
</p>
<p>
    This post should help you try it out if you have/get the required hardware. The project is currently using the Keybow
    keypad as a prototyping platform to figure out how things should work while they work on the details of a full
    keyboard and it is a neat little gadget, whatever you want to do with it.</p>
<h2>Getting started</h2>
<p>Requirements to try this:</p>
<ul>
<li>A Keybow kit, with Pi Zero (<a href="https://www.adafruit.com/product/4144">Adafruit</a>)</li>
<li>Micro USB Cable</li>
<li>Micro SD Card</li>
</ul>
<p>To get up and running you should:</p>
<ul>
<li><a href="https://hexdocs.pm/nerves/installation.html">Set up Nerves according to these instructions</a></li>
<li>Follow the <a href="https://github.com/ElixirSeattle/xebow">instructions at the xebow repo</a></li>
</ul>
<p>
    The installation guide might not tell you to go into <code>assets</code> and run <code>npm install</code> but I think
    you have to. I'm not going to tell you how to install Node and NPM because it varies. But you can probably find it <a href="https://nodejs.org/en/download/">around the Node site</a>.
</p>
<p>
    If you reach the point of having burned the firmware to your SD Card, congratulations. We are ready to play.
</p>
<h2>First firmware on the device</h2>
<p>So plug the SD card with your pristine Xebow firmware into the Keybow contraption and plug the Keybow into your
    computer
    with the Micro USB cable. Startup can take a little bit but it should settle into cycling through rainbow colors
    after a
    bit. It should also produce numbers when you poke it (the default keymap is a numpad plus some extras).</p>
<h2>Making changes</h2>
<p>I don't need a single numpad and now I have two of them. Let's make this more useful. Open
    <code>lib/xebow/keyboard.ex</code>
    in your preferred editor.</p>
<p>Replace the default keymap with:</p>

  <div class="code  elixir "  data-file="lib/xebow/keyboard.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/xebow/keyboard.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">    <span style="color:#a6e22e">@keymap</span> [
    <span style="color:#75715e"># THE NAVIGATOR!</span>
    %{
      <span style="color:#e6db74">k001</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:home</span>),
      <span style="color:#e6db74">k002</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:h</span>),
      <span style="color:#e6db74">k003</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:left</span>),
      <span style="color:#e6db74">k004</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:page_down</span>),
      <span style="color:#e6db74">k005</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:j</span>),
      <span style="color:#e6db74">k006</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:down</span>),
      <span style="color:#e6db74">k007</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:page_up</span>),
      <span style="color:#e6db74">k008</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:k</span>),
      <span style="color:#e6db74">k009</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:up</span>),
      <span style="color:#e6db74">k010</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:end</span>),
      <span style="color:#e6db74">k011</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:l</span>),
      <span style="color:#e6db74">k012</span>: <span style="color:#a6e22e">AFK.Keycode.Key</span><span style="color:#f92672">.</span>new(<span style="color:#e6db74">:right</span>)
    }
  ]</code></pre></div>
  </div>

<p>And then:</p>

  <div class="code  shell "  data-file="/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">export MIX_TARGET<span style="color:#f92672">=</span>keybow
mix firmware
mix firmware.gen.script
./upload.sh xebow.local</code></pre></div>
  </div>

<p>Sometimes the upload process can be finicky. You can always re-burn the firmware to the SD card instead using
    <code>mix firmware.burn</code> instead of the <code>gen.script</code>.</p>
<h2>The navigator</h2>
<p>So with the Keybow on its side, four wide. You'll have this fantastic navigation machine at your disposal.
    Top row are arrows, middle row is VIM navigation or GMail or wherever else the don't-move-your-fingers approach is
    used.
    Bottom row are the extremes of the first row.</p>
<table>
<tr>
<td>Left</td><td>Down</td><td>Up</td><td>Right</td>
</tr>
<tr>
<td>H</td><td>J</td><td>K</td><td>L</td>
</tr>
<tr>
<td>Home</td><td>PgDn</td><td>PgUp</td><td>End</td>
</tr>
</table>
<h2>Layers?</h2>
<p>So my plan for the keyboard just filled the entire keypad. But the xebow application supports layers, so you can do
    some
    interesting stuff there. I considered doing a chorded keyboard but I'm terrible at them and decided on this as
    something
    I might actually use. There are plenty of cool things you can do with Layers if you want.</p>
<h2>The Future</h2>
<p>The folks in #nerves-keyboard (on the Elixir Slack) are hard at work figuring out a real mechanical keyboard
    schematic
    that should be a very cool thing to own and play with. Most of the conversation in there right now is about PCB's, CAD and hardware layout. They are a welcoming bunch. So enjoy :)</p>
<p>If this post was good, or has problems that need fixing, let me know at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> on Twitter. If you want more of my writing I have
a non-tracking newsletter which tends to cover quality and sustainability in software.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Simple Solutions: UI choices without JS</title>
      <link>https://underjord.io/simple-solutions-ui-choices-without-js.html</link>
      <pubDate>Wed, 26 Aug 2020 15:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/simple-solutions-ui-choices-without-js.html</guid>
      <description>I&#39;ve been looking at creating some progressively enhanced UI which shouldn&#39;t require JS for any basic operations. The idea being that I can accelerate and simplify any operation with interactivity provided by Javascript but I won&#39;t implement things in a way that requires JS.
The demonstration below is one such example which can be used to give a very common type of interactivity without using Javascript. You can select an option or even expand a selection (similar method, but with a checkbox) without a line of Javascript.</description>
      <content:encoded><![CDATA[ 
<p>I've been looking at creating some progressively enhanced UI which shouldn't
   require JS for any basic operations. The idea being that I can accelerate
   and simplify any operation with interactivity provided by Javascript but I
   won't implement things in a way that requires JS.</p>
<p>The demonstration below is one such example which can be used to give a very
   common type of interactivity without using Javascript. You can select an
   option or even expand a selection (similar method, but with a checkbox)
   without a line of Javascript. It uses fairly simple methods but can be a bit
   confusing if you haven't seen this kind of thing before. I recall the first
   big <code>:hover</code>-based menus we could do in Firefox. Which wasn't 
   supported in IE6 and oh the frustrations.
</p>
<p>So we have the <code>:checked</code> pseudo-class which allows us to style
   an element because it has been checked. And then we have <code>~</code>,
   the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors#Combinators">General sibling combinator</a>.
   These two combine to allow us to target a sibling element of our checked
   element. This means that our radio button and our target that we want to
   show or hide have to be inside the same element. With <code>+</code> they
   would need to be exactly next to each other which makes label placement
   more annoying. Tilde is a bit more relaxed and we can just put the label
   in-between.
</p>
<p>There are a number of interesting pseudo-classes that we can do fun stuff
   with. Typically <code>:hover</code>, <code>:active</code> and
   <code>:focus</code>. They are all to some extent interactive. Additionally
   I would experiment with <code>:lang</code>, <code>:valid</code> and 
   <code>:invalid</code>. I think they could be useful. You might think that
   <code>:visited</code> could be one of these. But it won't work as it has
   been set to lie for sibling selection. Why? Privacy reasons. It would be
   trivial to track information about your visited sites if they didn't
   restrict the kinds of styling you could apply. Why? Because CSS can send
   calls to servers. Anyway, lets look at this UI stuff.
</p>
<h2>Demonstration</h2>
<style>
    .sample-editor {
        /* just some prettifying measures */
        border-radius: 4px;
        background-color: #555555;
        color: #eaeaea;
        padding: 8px;
        margin-top: 8px;
        margin-bottom: 8px;
    }
    .sample-editor textarea {
        /* just some prettifying measures */
        min-height: 128px;
        background: none;
        border: none;
        color: #eaeaea;
    }

    div.sample-editor {
        /* hide unselected editors */
        display: none;
    }

    .sample-editor-select:checked ~ div.sample-editor {
        /* show selected editor */
        display: block;
    }
</style>
<div>
<input checked="checked" class="sample-editor-select" id="sample-markdown" name="sample-editor-select" type="radio" value="markdown"/>
<label for="sample-markdown">Markdown</label>
<div class="sample-editor">
<textarea>My text!</textarea>
</div>
</div>
<div>
<input class="sample-editor-select" id="sample-richtext" name="sample-editor-select" type="radio" value="richtext"/>
<label for="sample-richtext">Rich text</label>
<div class="sample-editor">
<div class="richtext" contenteditable="true">
            My text!
        </div>
</div>
</div>
<h2>Code</h2>


  <div class="code  css "  data-file="style.css" >
    <div class="meta">
      <span class="language">css</span>
      <span class="filename">style.css</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-css" data-lang="css">    .<span style="color:#a6e22e">sample-editor</span> {
        <span style="color:#75715e">/* just some prettifying measures */</span>
        <span style="color:#66d9ef">border-radius</span>: <span style="color:#ae81ff">4</span><span style="color:#66d9ef">px</span>;
        <span style="color:#66d9ef">background-color</span>: <span style="color:#ae81ff">#555555</span>;
        <span style="color:#66d9ef">color</span>: <span style="color:#ae81ff">#eaeaea</span>;
        <span style="color:#66d9ef">padding</span>: <span style="color:#ae81ff">8</span><span style="color:#66d9ef">px</span>;
        <span style="color:#66d9ef">margin-top</span>: <span style="color:#ae81ff">8</span><span style="color:#66d9ef">px</span>;
        <span style="color:#66d9ef">margin-bottom</span>: <span style="color:#ae81ff">8</span><span style="color:#66d9ef">px</span>;
    }
    .<span style="color:#a6e22e">sample-editor</span> <span style="color:#f92672">textarea</span> {
        <span style="color:#75715e">/* just some prettifying measures */</span>
        <span style="color:#66d9ef">min-height</span>: <span style="color:#ae81ff">128</span><span style="color:#66d9ef">px</span>;
        <span style="color:#66d9ef">background</span>: <span style="color:#66d9ef">none</span>;
        <span style="color:#66d9ef">border</span>: <span style="color:#66d9ef">none</span>;
        <span style="color:#66d9ef">color</span>: <span style="color:#ae81ff">#eaeaea</span>;
    }

    <span style="color:#f92672">div</span>.<span style="color:#a6e22e">sample-editor</span> {
        <span style="color:#75715e">/* hide unselected editors */</span>
        <span style="color:#66d9ef">display</span>: <span style="color:#66d9ef">none</span>;
    }

    .<span style="color:#a6e22e">sample-editor-select</span>:<span style="color:#a6e22e">checked</span> <span style="color:#f92672">~</span> <span style="color:#f92672">div</span>.<span style="color:#a6e22e">sample-editor</span> {
        <span style="color:#75715e">/* show selected editor */</span>
        <span style="color:#66d9ef">display</span>: <span style="color:#66d9ef">block</span>;
    }</code></pre></div>
  </div>



  <div class="code  html "  data-file="simple.html" >
    <div class="meta">
      <span class="language">html</span>
      <span class="filename">simple.html</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html">&lt;<span style="color:#f92672">div</span>&gt;
    
    &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-markdown&#34;</span>
           <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-editor-select&#34;</span>
           <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;radio&#34;</span>
           <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-editor-select&#34;</span>
           <span style="color:#a6e22e">checked</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;checked&#34;</span>
           <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;markdown&#34;</span> /&gt;
    &lt;<span style="color:#f92672">label</span> <span style="color:#a6e22e">for</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-markdown&#34;</span>&gt;Markdown&lt;/<span style="color:#f92672">label</span>&gt;
    &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-editor&#34;</span>&gt;
        &lt;<span style="color:#f92672">textarea</span>&gt;My text!&lt;/<span style="color:#f92672">textarea</span>&gt;
    &lt;/<span style="color:#f92672">div</span>&gt;
&lt;/<span style="color:#f92672">div</span>&gt;

&lt;<span style="color:#f92672">div</span>&gt;
    
    &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-richtext&#34;</span>
           <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-editor-select&#34;</span>
           <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;radio&#34;</span>
           <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-editor-select&#34;</span>
           <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;richtext&#34;</span> /&gt;
    &lt;<span style="color:#f92672">label</span> <span style="color:#a6e22e">for</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-richtext&#34;</span>&gt;Rich text&lt;/<span style="color:#f92672">label</span>&gt;
    &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sample-editor&#34;</span>&gt;
        &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;richtext&#34;</span> <span style="color:#a6e22e">contenteditable</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;true&#34;</span>&gt;
            My text!
        &lt;/<span style="color:#f92672">div</span>&gt;
    &lt;/<span style="color:#f92672">div</span>&gt;
&lt;/<span style="color:#f92672">div</span>&gt;</code></pre></div>
  </div>


<p><em>Note:</em> The contenteditable in the example is a bit of a red herring because
    you can't submit those as form data and to the best of my knowledge using
    it for editing in a meaningful way will require JS in the end. In my project
    the editor would actually just show you raw HTML in a textarea if you need
    to edit without JS.</p>
<p>I think this is pretty neat and I don't think everyone knows about it.
    If you have questions or thoughts about this, feel free to get in touch via <a href="mailto:lars@underjord.io">lars@underjord.io</a> or just find me on Twitter as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Beam Bloggers Webring</title>
      <link>https://underjord.io/beambloggers-webring.html</link>
      <pubDate>Wed, 19 Aug 2020 08:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/beambloggers-webring.html</guid>
      <description>This is a brief announcement. If you are interested in following bloggers in the BEAM, Elixir, Erlang, etc. ecosystem or you blog yourself. Please check out beambloggers.com to get in on the old-school webring action. Bloggers can join it. It might lead to some traffic. Mostly it is a small nod to a stranger old time on the web when sites organized in rings for discoverability. I think the need for discoverability remains and some webrings have been showing up again.</description>
      <content:encoded><![CDATA[ 
<p>This is a brief announcement. If you are interested in following bloggers in the BEAM, Elixir, Erlang, etc. ecosystem or you blog yourself. Please check out <a href="https://beambloggers.com">beambloggers.com</a> to get in on the old-school webring action. Bloggers can join it. It might lead to some traffic. Mostly it is a small nod to a stranger old time on the web when sites organized in rings for discoverability. I think the need for discoverability remains and some webrings have been showing up again. You can find the shuffler for the webring at the bottom of my site as well and just let it carry you to a new site.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>The Strong Technologies</title>
      <link>https://underjord.io/the-strong-technologies.html</link>
      <pubDate>Fri, 14 Aug 2020 09:30:00 +0000</pubDate>
      
      <guid>https://underjord.io/the-strong-technologies.html</guid>
      <description>I feel like a curmudgeonly sort recently. I&#39;m honestly a pretty optimistic and positive person. But I&#39;m becoming increasingly technically curmudgeonly. I don&#39;t think it is age turning me conservative. But I feel like I&#39;m moving back to what I find tried and true in many ways. I feel resentful in a lot of cases where I can&#39;t really reasonably go back. So what am I talking about here?</description>
      <content:encoded><![CDATA[ 
<p>I feel like a curmudgeonly sort recently. I'm honestly a pretty optimistic and positive person. But I'm becoming increasingly technically curmudgeonly. I don't think it is age turning me conservative. But I feel like I'm moving back to what I find tried and true in many ways. I feel resentful in a lot of cases where I can't really reasonably go back. So what am I talking about here?</p>
<p>Well I don't like Spotify and their attitude towards music, artists or their attempts to turn podcasts proprietary. But I do have a lot of music library built up there, from when they were an up-and-coming challenger with a very snappy client. So leaving has an enormous cost of effort or lost music. My feelings on Netflix are similar. There are shows I want to watch. But I also just want to table-flip the whole idea of video streaming services because they are proliferating and the good shows are getting shifted around until there is not a single good offering anymore. Their offer is deteriorating and the whole value proposition to me was that they had almost everything I wanted. Facebook is terrible and mostly in my rearview mirror. Twitter is useful to me but I keep revising and limiting my use of it. Tweetdeck is helpful there. All the even more modern apps, your Instagrams and TikToks, are generally very silo:ed app experiences which rubs me the wrong way. I put my gardening on Instagram. That's about it.</p>
<p>So what did it used to be? Spotify was an MP3 collection. That's not exactly legal but everyone was doing that particular open secret. Film and TV was roughly the same unless you were getting Netflix DVDs in the mail or just buying them from bargain bins. Social media mostly wasn't but the ones that existed were usually forums or communities with specific topics or goals. The same kind of scoping so many want to go back to now before all the contexts we lived in collapsed in on one another. The reason why most of the good stuff these days happens in smaller groups in different chats.</p>
<p>Anyhow. I distrust the proprietary but am generally willing to accept some proprietary measures in the name of a decent business model for some kind of entertainment. But with everyone building their own sandcastle it doesn't feel like any kind of progress. There isn't a sweet new standard for buying video that means you can use any compatible player to stream your stuff across the platforms. No, Netflix has an app on almost anything and a web thing. But there is no cooperation towards a common standard for moving that particular industry forward and making things less painful. Proprietary products on proprietary platforms with no interoperability is uninteresting and weak to me. Even if they invent a really cool codec or some amazing UX you won't see the benefit of that elsewhere. You aren't moving the state of the art for the human collective much. You aren't contributing. That's fine according to capitalism (a different discussion, let's not right now) but proprietary progress without fitting it into the surrounding world is deeply uninteresting to me. Apple does this a lot. Sadly so does all the Android flavors. And this is why wearables still suck so much.</p>
<p>So to take the positive and optimistic view. What is good?</p>
<p>I find that there are a set of <em>technologies that have proven very reliable</em> and that people who care about this stuff seem to <strong>gravitate back to</strong>. I currently see it with content creators that want a better sense of control over their destiny and ownership of their platform and business. Typically you'll see these:</p>
<ul>
<li>E-mail &amp; Newsletters</li>
<li>Websites &amp; your own domain</li>
<li>RSS feeds</li>
<li>Podcasts</li>
</ul>
<p>So let's dig in a bit. Why are these good?</p>
<h2>E-mail &amp; Newsletters</h2>
<p>Most people don't think much of e-mail. It is the social plumbing of the web and wider Internet. When all else fails or you just want people to get a hold of you in a reliable way, e-mail is pretty good. For content creators it is magnificent. If you get yourself a newsletter mailing list, people that have chosen to be contacted by you, that want to read what you have to say, then you have a broadcast medium to reach out through. You can't rely on people coming back to your website unless you publish new things quite often and keep top of mind. The Questionable Content webcomic comes to mind. It publishes every weekday, I didn't have it in RSS or anything for the longest time, I just opened it habitually. But not every piece of content is the same, not every creator can set that kind of cadence. And e-mail allows you to get in touch weekly, or monthly or intermittently with whatever your readership, viewership, listenership is. It won't be everyone, but it can be the critical core that makes sure people find out when you publish things.</p>
<p>Another thing is paid newsletters where you basically subscribe to a newsletter. Why email here? Well I guess the delivery mechanism is convenient. Everyone can handle their email however they want, filtering, automation, etc. And getting email you actually want is usually not particularly upsetting even if you dislike your email overall. But what about protecting the content? Usually not a concern. Most indepdent creators, especially writers don't really suffer heavily from someone forwarding a paid email. When you support an independent writer you are usually more invested than to just turn around and broadcast it. And I'm not sure there is any market for torrents of paid newsletter archives. I haven't checked. Paid e-mail newsletters seem to work. E-mail is very reliable, fairly standardized and works. No one entity owns it and if some corporation tried to completely screw it up the economy would collapse. So incentives are fairly good to keep e-mail working. And so it works for the indie creator as well.</p>
<p>YouTube is especially interesting in this regard because it has become notoriously unreliable in letting video creators actually reach their Subscribers and even the people that "hit that bell". A lot of people have trouble with their work not surfacing to the people that actually follow them. Being able to reach out to your core following through e-mail to go "I made another thing!" is immensely powerful and much more reliable.</p>
<p>I really dislike popovers and nagging about signing up for someone's newsletter, there is a lot of growth-hack, dark pattern bullshit around this. Because it is marketing and business and there's always plenty of people who chase the numbers rather than consider what their efforts project and what behavior they are excibiting. So I absolutely get being skeptical of e-mail newsletters. I follow a bundle of them that I found useful. Some are just straight news, tracking the Elixir community stuff but some are writers sharing more considered thought or insight behind the scenes of projects. I find that quite engaging. I run my own newsletter. It won't pop up here. You'll find the sign-up at the bottom of the page if you are curious.</p>
<h2>Websites &amp; your own domain</h2>
<p>So even if you stitch a business together with your Substacks, your Calendlys, your Memberfuls and your Patreons, having a domain to refer people to is incredibly powerful. So when Patreon collapses or Substack gets bought by something or whatever might happen, the core of your online presence is under your control (asterisk: through domain registration, weird system, separate conversation). And you can direct people elsewhere when the time comes to move on to some other service or to bring it all back home under your loving care.</p>
<p>So a website might not be incredibly fashionable but having one remains beneficial and it is a good place to direct people as the central core in whatever sprawling system of services and platforms you exist on.</p>
<p>It is also nice if your email doesn't come from a free Gmail. I think that's a bit more classy.</p>
<h2>RSS feeds</h2>
<p>Not the most used technology but compared to YouTube subscribing it is very reliable. It didn't die with Google Reader. I use Feedly, there are many options. And having an RSS feed allows some of the more invested readers to actually read your updates at their leisure. I also <a href="https://underjord.io/feed.xml">provide and RSS feed</a>. As Promoe once wrote "We preach what we practice, cause it's easier that way than backwards".</p>
<p>It is also the open technology behind our next point which has a bit more sizzle to it. It even makes noise.</p>
<h2>Podcasts</h2>
<p>There is a suprising strength to hearing a voice on a regular basis. It builds a strong connection, arguably faster than you connect to a writer's voice. At the very least the nuances hit you faster. I am absolutely thrilled that there is an open practice for the spoken word that has solid traction and public interest. It is heavily tied into a strangely closed registry in the form of Apple's podcast directory but that part isn't actually required for podcasting to exist. It just simplifies matters significantly and allows podcast clients to bootstrap their directories.</p>
<p>Podcast builds right on top of RSS and I expect podcast clients (podcatchers?) outnumber the more general RSS "news reader" clients significantly in install base.</p>
<p>The usage pattern of podcasts feels different to me than most RSS reading. For the podcasts you follow closely every new episode is an exciting, comfortable or interesting listen. It is a very strong way of connecting to people. And it is open. Anyone can do it.</p>
<h2>In closing</h2>
<p>These technologies and these approaches are reliable, open and powerful. They can be polished or rough, they can be free or paywalled to some extent. They are flexible and they integrate and combine with one another allowing people to build content businesses online. Note that there isn't a really compelling solution for video that I know of. Not one with any traction next to YouTube at least. Let me know if you know some good options for that.</p>
<p>I think we are in the middle of a pendulum swing where some creators are moving away from proprietary platforms and taking more control of their relationship with their audiences. And I think a lot of audience is trying to remove themselves from engagement-driven timesink platforms that don't respect their users. The majority will still be on YouTube and all the socials. But the movement seems significant. And I'm all about it.</p>
<p>If I'm all wrong, all right or anywhere in between and you want to tell me all about it feel free to reach out via good old email <a href="mailto:lars@underjord.io">lars@underjord.io</a> or <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> on Twitter. And maybe next time we will go through the top 3 Gopher clients that your doctor hates, just to keep it old school.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>The best parts of Visual Studio Code are proprietary</title>
      <link>https://underjord.io/the-best-parts-of-visual-studio-code-are-proprietary.html</link>
      <pubDate>Mon, 03 Aug 2020 07:25:00 +0000</pubDate>
      
      <guid>https://underjord.io/the-best-parts-of-visual-studio-code-are-proprietary.html</guid>
      <description>I&#39;ve been very surprised and delighted over a number of years now by Microsoft&#39;s strong efforts in open source. I understand the skeptics, I was on Slashdot when they tried to sue Linux out of existence and I think only time will tell. I figure MS contributing is better than them hunting Linux distributions for sport. So I was mostly onboard for Microsofts efforts and I&#39;ve especially found Visual Studio Code useful.</description>
      <content:encoded><![CDATA[ 
<p>I've been very surprised and delighted over a number of years now by Microsoft's strong efforts in open
    source. I understand the skeptics, I was on Slashdot when they tried to sue Linux out of existence and I think only
    time will tell. I figure MS contributing is better than them hunting Linux distributions for sport. So I was mostly
    onboard for Microsofts efforts and I've especially found Visual Studio Code useful.</p>
<p>To settle a few things. When I tweeted on this subject I only got the response that I should use vim. Thanks. Great.
    I can and do on use vim. That misses a number of points. Visual Studio Code is an immensely popular editor and
    likely the most common recommendations to new developers. The primary reason I've used Visual Studio Code is
    that it has an incredibly compelling solution for remote pairing in the form of LiveShare. I've used that for a
    while with great success mentoring, coaching and generally working with other developers of varying experience and
    editor preferences. Most programmers can handle a "normal" editor like VS Code while something like Emacs
    or Vim depends a lot more on what they've learned.</p>
<p>I also ended up enjoying the Remote series of extensions for developing effectively inside remote servers or local
    containers.</p>
<h2>These things are proprietary</h2>
<p>At some point I read a piece of license that said that LiveShare could only be used with the Visual Studio family of
    products. "Huh, that sounds weird, VS Code is open source right?"</p>
<p>Sure enough. VS Code is fully MIT. The binary distribution has a separate license to allow telemetry and protect
    Microsoft trademarks and stuff. Nothing particularly weird, I can't really get worked up about telemetry, I know
    some can. But the extensions.. These extensions are in my book core differentiators that makes VS Code compelling.
    For me it is definitely part of what pushes it beyond the much leaner Sublime (paid, closed source) I was using
    before.</p>
<p>These extensions have a license limiting them and their online service parts to only be used with the Visual Studio
    family of products. This is the <a href="https://microsoftdocs.github.io/live-share/license/eula.html" title="License for LiveShare">license for LiveShare</a> and this is the <a href="https://code.visualstudio.com/preview-license" title="License for Remote">license for Remote</a>.</p>
<p>For me LiveShare is the most important thing. Google Docs style collaborative code editing, terminal sharing, port
    sharing and a bunch more features. I know Atom had an extension like this, I haven't checked the licensing there
    or tried it recently.</p>
<p>Remote is a very strong extension as well for anyone working on a server over SSH or in a container. It helps by
    installing extensions on the destination to allow language servers and such. I've seen it do terrible things to
    servers sometimes but it is very useful and generally works well.</p>
<p>It makes me uneasy to accept VS Code as an "open" project in any wider meaning of the word when compelling
    features are legally locked to only work inside the family of Visual Studio products. It makes me less certain that
    this isn't the Extend in Embrace, Extend, Extinguish. It also frustrates me that this prevents someone from
    building a compatible plugin for VIM or any other editor. This would be much more powerful if it could be in all the
    IntelliJs as well.</p>
<p>You'll find a repo for <a href="https://github.com/MicrosoftDocs/live-share" title="LiveShare on GitHub">LiveShare on GitHub</a> but it is only for documentation and issue tracking. There
    is no code. Same for <a href="https://github.com/microsoft/vscode-remote-release" title="Remote on GitHub">Remote</a>.</p>
<h2>The entire marketplace is proprietary</h2>
<p>Some additional salt in this particular wound is that the use of the Marketplace of VS Code extensions is also
    proprietarily licensed. So all these open source developers are shoving their extensions into a competitive
    advantage for one of the world's largest tech firms. And they disallow other uses of the marketplace. Even if
    the letter of open source is followed there is none of the openness, collaborative or community essence that I think
    exemplifies open source and free software projects.</p>
<p>What does this mean in practice? I guess it protects from the competition. Such as the <a href="https://github.com/VSCodium/vscodium" title="VS Codium">VS Codium</a> project which provides VS Code
    binaries without the proprietary parts. But also, as a consequence of this, without the Marketplace of extensions.
    There is an open source alternative called <a href="https://open-vsx.org" title="Open VSX">Open VSX</a>, but since
    it isn't the canonical one it is missing a bunch of extensions and the big Liveshare and Remote ones are still
    not allowed.</p>
<p>This also blocks the <a href="https://github.com/cdr/code-server" title="code-server">code-server editor</a> that
    allows running VS Code in the browser from using it which otherwise would have been perfect for me to do development
    on an iPad Pro. I can still use that but a lot of packages are not in Open VSX.</p>
<h2>What about lock-in?</h2>
<p>Visual Studio Code is marketed with LiveShare and Remote as powerful extensions. VS Code is also marketed as open
    source. It is easy to use the editor, install the extensions and be under the impression that you are using an open
    source software suite where Microsoft simply hosts the peering service for identifying and connecting you and your
    collaborator.</p>
<p>But the peering service is not the only closed part. The extensions are not open source projects as far as I can find
    and they are licensed during distribution in a way that disallows using them with anything but Visual Studio
    products.</p>
<p>This leaves me with a sour taste in my mouth. I wasn't sold on having an Electron-based editor to begin with but
    VS Code was substantially leaner than Atom so I've been mostly accepting it.</p>
<p>If you have good suggestions for strong collaborative development tools that are open source, please let me know at
    <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on Twitter <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. If you want to follow my writing the RSS feed is
    right below. If you want more of my writing I have a tracking-free newsletter that I'd love for you to sign up for,
    also below.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Five Buck Fatigue</title>
      <link>https://underjord.io/five-buck-fatigue.html</link>
      <pubDate>Thu, 23 Jul 2020 23:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/five-buck-fatigue.html</guid>
      <description>This is about a reaction I noticed with myself in response to membership programs, patreons and individual sponsorship as I&#39;ve run into significantly more of these following Corona. The advertising-supported model has confirmed many of the concerns about its sustainability and creators are looking for more reliable options. I think this feeling I have might be something other people experience and something for creators to be prepared for.
Creating anything in a sustainable fashion is hard work.</description>
      <content:encoded><![CDATA[ 
<p>This is about a reaction I noticed with myself in response to membership programs, patreons and individual sponsorship as I've run into significantly more of these following Corona. The advertising-supported model has confirmed many of the concerns about its sustainability and creators are looking for more reliable options. I think this feeling I have might be something other people experience and something for creators to be prepared for.</p>
<p>Creating anything in a sustainable fashion is hard work. You are fighting uphill against a cultural expectation for things to be free on the web. Often a simple solution is a good solution. The simplest way is asking ones extremely enthusiastic fans to chip in, often with some incentive, such as bonus content. It is a solution with strongly aligned incentives, those who pay get more of the thing they like and they are already invested in sustaining the creation. The creator gets paid without needing to tailor their format or subject matter to the whims of sponsors or advertisers and can focus on making what the fans already want. Well-aligned incentives.</p>
<p>I think this model is great when it works, I think CGP Grey <a href="https://www.youtube.com/watch?v=aN44ETT6mUY">made his case very well</a>. He generally makes tightly produced videos with long times between releases. YouTube advertising monetization is not optimized for his content though I'm sure it does pretty well. Beyond that the pandemic means companies cut down on advertising and this pushes more creators to try this model as rates go down and opportunities dwindle.</p>
<p>I've noticed something about my reaction to an increasing number of memberships. They frustrate me. Especially when they come in groups or too closely. This is a feeling, my robotic logic, I'm mostly human. I feel a bit of an "ugh" even before I consider whether the offer makes sense for me or not.</p>
<p>Part of it can be FOMO (Fear Of Missing Out). I absolutely dislike not having access to all of the content or behind-the-scenes for something I follow enthusiastically. Part of it can be the normal reaction to a kind of paywall. But generally the podcasts, newsletters and stuff that I follow aren't actually removing anything. They are just adding some premium stuff. I still feel that twitch, that small frustration.</p>
<p>So what do I think it is? A kind of fatigue, as the title indicates. I want to support, I want to engage. But I also want to be mindful in how I spend my money. And $5/month adds up pretty quickly. There is a lot of consideration in committing that recurring payment for .. indefinite duration. And I think I'm getting sick of deciding. I always feel an element of shame if I don't support a creator I like. I think a key term here is support. Positive thing right? You are supposed to support good things. So why aren't you being supportive? This is of course a bit irrational and you shouldn't feel bad. As often is the case with feelings, we don't get to decide them.</p>
<p>Let's call this the <strong>Five Dollar Fatigue</strong>, sounds catchy. If the trend continues I can be a thought-leader and I'm sure that's good for something. So yeah, the feeling of growing frustration connected to things you like or love continuously asking you for money.</p>
<p>I'm not sure how to lower the internal conflict when this kind of arrangement is brought up.</p>
<p>For me, personally. I think I might need to figure out what my actual budget is for supporting independent creators on a monthly basis. Then I don't have to re-evaluate it each time something I like wants my financial support.</p>
<p>For creators. I think most are thoughtful and graceful when they ask for the money and I think it would be worse if they were as heavy-handed as Wikipedia for example (lots of pressure and guilt in all communication, I've complained to them repeatedly). I think creators just need to be ready for the possibility that some fans will still react poorly, or more grudgingly accepting than enthusiastic not because of that particular arrangement but because of all the ones considered before it.</p>
<p>If you have thoughts on this piece of writing do let me know at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> on Twitter. If you want more of my writing I have
    a non-tracking newsletter which usually comes with
    podcast recommendations and more in-depth thinking. Often about sustainability in software.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>&amp;quot;More than one thing at a time&amp;quot;</title>
      <link>https://underjord.io/more-than-one-thing-at-a-time.html</link>
      <pubDate>Wed, 17 Jun 2020 17:45:00 +0000</pubDate>
      
      <guid>https://underjord.io/more-than-one-thing-at-a-time.html</guid>
      <description>On a recent Elixir Outlaws episode Chris Keathley told us all a nice story of the advantages of Elixir as opposed to Ruby. His frustration with Ruby and appreciation of how Elixir works resonate at the frequency of my own frustrations and joys. I believe my titular quote is accurate, that&#39;s one of the primary things he noted. How nice it is to use a runtime that can do more than one thing at a time.</description>
      <content:encoded><![CDATA[ 
<p>On <a href="https://elixiroutlaws.com/74">a recent Elixir Outlaws episode</a> <a href="https://twitter.com/ChrisKeathley">Chris Keathley</a> told us all a nice story of the advantages of Elixir
    as opposed to Ruby. His frustration with Ruby and appreciation of how Elixir works resonate at the frequency of my
    own frustrations and joys. I believe my titular quote is accurate, that's one of the primary things he noted.
    How nice it is to use a runtime that can do more than one thing at a time.</p>
<p>And I've never really used Ruby. What I have used is a lot of Python. And some Node.js. And I find the way they
    execute, async or not, to be frustrating and firmly in the camp of mostly sufficient. Not aspiring to greatness.</p>
<p>I really like Python. I enjoy using it. I'm skilled enough with it. I can see why people like Node.js to some
    extent. I understand that Node.js allows you to stick to a single language and provides a bunch of other interesting
    possibilities. Some of them are unique to JS. But I find both lacking when I compare their technical underpinnings
    with the BEAM which Erlang and Elixir runs on.</p>
<h2>Efficienct resource usage is an effective cost reduction</h2>
<p>Chris mentioned how many instances running the ruby service they could drop and replace with much fewer Elixir
    instances. This is bound to end up saving Bleacher Report a bunch of money over time. That wasn't the point of
    it all but I think people should keep in mind that performance directly relates to cost. Especially in the cloud.
</p>
<p>With auto-scaling cloud infrastructure the effects of strong utilization of resources can be very noticeable on the
    bill. Being competently concurrent and parallell means better utilization of the CPU's you are paying for.
    Providing better latency guarantees by means of the BEAM scheduler's pre-emptive scheduling as compared to
    single-threaded cooperative multi-tasking means that you reduce the risk of a single dodgy code-path or an odd piece
    of data processing blocking the event loop for a significant time.</p>
<h2>The capabilities of your runtime sets limits on your potential</h2>
<p>You can build great things in basically any language on any platform. And a lot of languages have distinct strengths
    that are not strictly technical but would take enormous effort to dislodge from the ecosystem where they were born.
    Such as Python and machine learning which is a matter of library support rather than technical fitness and it dives
    deep into C/C++ to extend Python in a performant way. Node.js has huge developer mindshare, a vast library
    ecosystem, near-native cross-platform app development that cannot be replicated without Javascript because of app
    ecosystem restrictions and it enjoys exclusive ties to frontend development.</p>
<p>Python won't unlock a stronger concurrency and parallellism story unless it manages to get rid of the GIL. So
    building highly performant web services in Python will always include paying attention to these limitations and
    working around them. That doesn't make it impossible or even particularly hard but I've worked with systems
    where the GIL was definitely a factor. Much as the single-threaded nature of Node would have been.</p>
<p>The BEAM VM, Erlang and OTP were built for creating reliable distributed systems. It does that well and has a strong
    core feature-set for that. The distributed computing story incidentally transfers well to online services such as
    web applications. The approach it uses with inexpensive processes and message passing also translates very well to
    multi-core workloads.</p>
<p>This is an incredible foundation to stand on. Elixir gave it a more approachable and dare I say modern language that
    people had an easier time getting going with. Erlang was already a great success for some companies but somewhat
    under the radar.</p>
<p>I firmly believe that this gives any appropriate application or system built on this foundation a much higher ceiling
    before the runtime and core building blocks actually start limiting progress. </p>
<p>When I see ideas about porting something born in the Elixir ecosystem, such as Phoenix Presence or LiveView to
    Node.js I usually scratch my head. I get why they'd want it but I don't believe in the idea. Both of those
    are examples of strengths of the BEAM, reliable stateful services in distributed applications. Data is maintained
    across the cluster, there is a strong approach for limiting damage (supervision trees, etc) if a process holding
    data breaks due to a bug, an outage or similar. You can clone the surface of LiveView or Presence and go "BAM!
    Node has it too". But I bet it doesn't.</p>
<h2>Pay attention to the trade-offs (if you want to make something great)</h2>
<p>This doesn't just hold true for Elixir. There are also plenty of places where Elixir and the BEAM would be the
    wrong choice. Consider React Native (or NativeScript, Capacitor, Cordova, Ionic, any cross-platform tool for making
    apps) for example. Most people are aware that they are making a trade-off when selecting React Native. And most
    people are probably right about picking it anyway. Because making native apps is expensive and time-consuming and
    you just want iOS+Android and they should be basically identical anyway.</p>
<p>By choosing the cross-platform tool you are probably binding yourself to a decent app. But you are limiting your
    potential to make it great. Technically you could drop into native implementations per platform with React Native.
    Just like a Python dev can always "just write some C". That's not normally why you'd choose the
    tool. You might even recruit based on the tool and not actually have anyone that know C, Swift or Kotlin that could
    dive deeper.</p>
<p>The best mobile apps are typically done natively in the intended tools. They fit in, they can integrate with all the
    relevant APIs of the operating system at the full level of detail without abstracting away the differences. They can
    take advantage of any optimizations the platform owner makes and they do not get screwed over by incompatibilities
    quite as often.</p>
<p>We see the same thing with Electron apps (and equivalents) on the desktop. There the problem is more directly felt
    because the overhead of running them is ridiculous compared to what the apps actually do. They all run a full
    browser. The amount of resources used by Slack, Discord and Spotify is maddening. I try not to think about how fast
    they could be, how lean. In Spotify's case, how fast, lean and useful it actually once was. And how much more
    efficiently they could do things.</p>
<p>So for mobile apps and desktop applications the potential for greatness is higher when working with the languages and
    tools that are well suite to the task. The same is true for web development. Stateful and distributed or even
    stateless thanks to the soft real-time latencies, I think Elixir and the BEAM have an incredible high ceiling for
    technical achievements compared to most comfortable high-level languages that it compares to. So consider what your
    language and ecosystem provides you and if it is in line with what you value. If that checks out, you are doing
    fine. If it doesn't maybe something to think about.
</p>
<p>I grant that I'm biased towards Elixir, BEAM and OTP. But I do really like Python. I just have frustrations with
    it. I don't hate Node either, I'm just not sure I like it. If you want to follow my writing this blog has a
    completely normal <a href="https://underjord.io/feed.xml">RSS feed</a> and you can sign up for my newsletter further
    down. If I'm very wrong or you have thoughts,
    questions or concerns feel free to get in touch via <a href="mailto:lars@underjord.io">lars@underjord.io</a> or just find me on Twitter as <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. It
    is not my intent to pee on your ecosystem, I just have feelings and opinions. I'm sure it's a fine
    ecosystem.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>WordPress &amp;amp; the gross inefficiencies</title>
      <link>https://underjord.io/wordpress-and-the-gross-inefficiences.html</link>
      <pubDate>Fri, 22 May 2020 12:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/wordpress-and-the-gross-inefficiences.html</guid>
      <description>My recent work with WordPress revived a frustration I built up while working with software from that era of the 2000&#39;s. Drupal, WordPress, Joomla and friends. Whenever you visit a page, the system will generate it from scratch.
Note: WordPress gets to take the brunt of the focus here. But it just happens to be what I recently used that re-ignited these frustrations. I believe this approach remains the standard for Drupal, Joomla, Mediawiki and many, many others.</description>
      <content:encoded><![CDATA[ 
<p>My recent work with WordPress revived a frustration I built up while working with software from that era of the
    2000's. Drupal, WordPress, Joomla and friends. Whenever you visit a page, the system will generate it from
    scratch.</p>
<p><em>Note: WordPress gets to take the brunt of the focus here. But it just happens to be what I recently used that
        re-ignited these frustrations. I believe this approach remains the standard for Drupal, Joomla, Mediawiki and
        many, many others. It is bad for all of them but the popularity and traditional use-cases around WordPress
        make it particularly relevant in my opinion.</em></p>
<p>This gives a lot of flexibility by sacrificing performance, something I definitely touched on in <a href="http://underjord.io/artisanal-software-beyond-pragmatism.html" title="Artisanal software - Beyond pragmatism">a previous blogpost</a>. It gives a lot of power, simplicity and
    developer
    convenience as part of that flexibility. But the end result is that for an extraordinary number of use-cases this is
    <strong>incredibly inefficient</strong>.</p>
<p>Generating a page at the time of request is only really meaningful if the content is either very time-sensitive (as
    in seconds, rather than minutes) or if the content is markedly different for every request (often the case for
    logged in pages or pages generated by the user's actions, mostly the realm of web applications, rather than web
    sites). In other cases you can generate the content before-hand and just serve that repeatedly with great
    efficiency.
</p>
<p>As a blog-engine you can argue for time-sensitivity, but usually to-the-second updates are not very relevant on a
    blog. And the stream of comments is often not that dense. Log-in and accounts is the second bit which some use, some
    don't.</p>
<p>An incredibly number of WordPress sites are <em>mostly static content</em>. The conventional wisdom is to put these
    behind
    Varnish or any of the many WordPress caching plugins. This mitigates a lot of the load for an active site and
    reduces load times for popular pages. Or one step further, use a WordPress plugin that generates a
    static version of your site to serve. These static generation plugins are among the best approaches for WordPress in
    my eyes. But they are usually also somewhat kludgy, a hack around the dynamic nature of the CMS. And you lose one of
    the great strengths of WordPress, its expansive ecosystem of plugins to do anything your heart desires.</p>
<p>Is this trade-off insurmountable? I don't think so. I think you can make most of a site static and still make
    parts dynamic. This has always been possible with the web platform overall. Just serve files if you have them and
    generates responses dynamically if you don't. But beyond this we now have the idea of the JAMstack (Javascript,
    APIs, Markup) that tries to provide mostly static pages with the power of dynamic parts. LiveView can potentially
    fill a similar role in the Elixir and Phoenix world. I hope to experiment with this sometime soon.</p>
<p>So it can be done. I think it made sense for WordPress to do things the way they did when they did them. It was
    basically the blessing of PHP to just be able to generate everything easily on-demand. It requires less knowledge
    from extension developers, less consideration. You can mix static content, dynamic blocks, everything is up to date
    with whatever is in the database. I wrote recently about some of my troubles with WordPress' <a href="https://underjord.io/the-wordpress-merging-problem.html">over-reliance on the database</a>.</p>
<p>But this is resource-heavy. A static site that generates in 100-300 ms on WordPress can easily be fetched in 30ms
    using
    some CDN. Requiring small orgs that want an information website that non-techies can edit to figure out good caching
    (one
    of the hard things in computer science) or to understand the trade-offs in making your WordPress static is
    not a fair shake. That stuff takes a bunch of experience in web dev, some people just want a website. A lot of
    people fitting this profile choose WordPress and similar sites, not knowing that their
    site could be blazingly fast, basically free to host and significantly more stable, secure and efficient if it was
    built differently.</p>
<p>This frustrates me if you couldn't tell. It wastes resources in the form of CPU cycles, RAM and power. It lowers
    the general quality of the web as an experience for users, platform for development and place of doing business.
    People pay for server performance they really shouldn't need, their sites use CPU and IO where the entire
    website would probably fit on a very small RAM disk and could be served straight out of memory. Or from a CDN where
    it can be provided as close to the end user as practically possible and be heavily optimized for fast delivery.</p>
<p>The trust and status of WordPress as a platform teaches developer after developer that this is fine, reasonable and
    even good. "I mean, if its good enough for the biggest and most beloved CMS on the planet, who am I to
    argue." That's what I picked up. I put trust in these tools and figured the problems were something unavoidable
    or my fault.</p>
<p>But honestly, I think <em>it kinda sucks</em>. I <strong>applaud the WordPress project</strong> for their <em>hard
        work
        on accessibility</em>,
    <strong>maintaining a complex legacy</strong> and successfully providing a strong competitor to proprietary
    solutions. But I
    don't think its technically healthy, I don't find it a sustainable way of working. And, aargh, the
    inefficiencies really get to me!</p>
<p>Do I have a solution to propose? Yeah. Sort of. Maybe. This is too long to get into that but the general ideas are in
    my writing here. I'm mostly presenting the case against the status quo here. I have my thoughts about what the
    we could be doing instead. But that will have to wait. Also, this blog is not exclusively dedicated to WordPress and
    frustrations. I just had some things to say on the topic.</p>
<p>Thanks for reading. If you have anything to share or want to argue about this with me, feel free to get in touch at
    <a href="mailto:lars@underjord.io">lars@underjord.io</a> or poke me on Twitter where I'm <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>. Have a lovely day.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>The WordPress merging problem</title>
      <link>https://underjord.io/the-wordpress-merging-problem.html</link>
      <pubDate>Mon, 18 May 2020 14:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/the-wordpress-merging-problem.html</guid>
      <description>WordPress is approximately the most popular CMS out there. I&#39;ve worked with it plenty over the years, off and on, as clients, employers and others have needed websites.
After a while every WordPress site has ended up feeling slightly sticky, icky and annoying to deal with in the longer run. So I don&#39;t really recommend it these days. It remains a popular choice. Because people don&#39;t ask me first which is probably reasonable.</description>
      <content:encoded><![CDATA[ 
<p>WordPress is approximately the most popular CMS out there. I've worked with it plenty over the years, off and on,
    as clients, employers and others have needed websites.</p>
<p>After a while every WordPress site has ended up feeling slightly sticky, icky and annoying to deal with in the longer
    run. So I don't really recommend it these days. It remains a popular choice. Because people don't ask me
    first which is probably reasonable.</p>
<p><strong>Note: This website does not run on WordPress. But it is only edited by me so none of this post is relevant
        there.</strong></p>
<p>I honestly don't have a good option for you right now if you want a good CMS. If you have an option for me, let
    me know at <a href="mailto:lars@underjord.io">lars@underjord.io</a>. Be warned that I'd rather not
    use something based on Node.js or PHP as a matter of preference. And the use-case I want a CMS for is to have
    something I can hand to non-technical people that need to manage a website and for some reason or other will not
    just pay for something like Squarespace (not sponsored, they're fine in my experience). From talking to people
    about things like Netlify CMS they don't quite scratch that non-technical user itch. Would love to hear more about
    that though.</p>
<p>I wrote more extensively about this in my newsletter and its not what I want to cover here. To the point! I recently
    encountered what seems to be a mostly intractable problem for anyone attempting to run WordPress as a software
    development project and a serious piece of infrastructure rather than a quick-and-dirty, deploy-and-close-your-eyes
    CMS install.</p>
<p>So you want your developers to be working on the pre-release behind-the-scenes themeing of a new campaign page. There
    is the page itself, maybe some custom blocks, maybe som asset files that should be uploaded, some theme changes.
    This all happens on your development environment. Because you don't want to experiment with your theme in
    production. Fair enough. You might even track code changes with git.</p>
<p>Meanwhile, in production, your webmasters or other editor types are busy authoring, editing, fixing and managing the
    site. They move a block. They touch up some page text. They post new content. All of this work is critical. It is
    the actual purpose of the site's very existence.</p>
<p>Your web developers or designers are done. They call you and say "hey! server-guy, we're ready, deploy our
    stuff please
    :)" and you might think to yourself "I would have scripted this to be a single-step or even
    automatic". That was my thinking. I tried to script it.</p>
<p>WordPress stores pretty much everything in the database. It is common practice when moving a WP site from development
    to production to run a global find and replace on the database to correct all references to specific domain names.
    Because the database is full of denormalized data that might reference the domain, URLs and much more.</p>
<p>So now you have two databases that are out of step and there are changes you want to use from both. Git does not
    solve this, even if you dump it to a file.</p>
<p>For one thing all "content types" have their content items stored in a table called wp_posts where they all
    get a numeric auto-incremented ID. This ID sequence will be out of whack between your environments. Supposedly the
    import/export content feature should solve this but it skips some data and I could not make it work overall.</p>
<p>The least risky and most reliable way of "deploying" changes I've found is to copy the code over to
    production and then repeat all database-related changes manually. Which means, you better keep a list. If you've
    worked with automation, testing, devops, any of it. This is madness.</p>
<p>I put some research into this because I couldn't believe this issue wasn't somehow resolved. There must be a
    good practice. There are huge sites using this CMS. How do they do it? No clue. Maybe they restrict devs to specific
    tables, they probably run a static site generator plugin, they probably do all sorts of odd things to make it work
    reliably. Or they are holding on for dear life and hoping for the best.</p>
<p>If you can divide the domains so that developers do no database changes and editors cannot touch anything
    code-related you can make it work. But this means that any change to block structures and that sort of configured
    layout and any custom blocks needed must be repeated in production. You rely on human memory and diligence which
    often works but is very inconvenient. The cognitive burden of figuring out what steps must be taken on which
    environment is dangerous. Arbitrary pieces of UI are now no-go zones for different workers but the CMS
    won't tell you which is which and some plugins definitely straddle that boundary. Mistakes can
    cause fairly serious problems. I find this untenable.</p>
<p>The most up-to-date overview of managing this issue that I found was <a href="https://deliciousbrains.com/syncing-wordpress-database-changes-merging/" title="Syncing WordPress database changes">this blog post</a> which basically concludes that there is no silver
    bullet, or in my eyes, no proper solution.</p>
<p>Looking for WordPress devops I found a few breathless posts about "oh yeah, do this with Git and then maybe some
    Docker and you can use this CI and this CD and this service is fabulous for jiggling your kerfuffles and in part 2 I
    will handle merging multiple environments". I found multiple posts that intended to write a part 2 and never
    finished it. I can see why. This is <a href="https://carlalexander.ca/introduction-automated-wordpress-deployments/" title="Introduction to Automated WordPress Deployments">one of them</a> that seemed promising until I found the
    comments about part 2. Another one (link rotted, was "billiemead.com/introduction-to-wordpress-and-devops") mentions the issues and doesn't really mention
    solving them but the part 2 link is dead, so I don't know. Turns out proper automation of WordPress might be
    hard.</p>
<p>If you are a developer and are the single arbiter of everything about a site this is not hard. But you might as well
    use something else in that case, an SSG will often serve you better. If you can stop your client from updating a
    site while you change things this is also manageable but then you might not have a very interesting site on your
    hands.</p>
<p>I don't find this an acceptable level for software development on one of the most well-used and most useful
    pieces of open source web application software out there. WordPress has a great reputation among non-technical users
    and a fairly strong one among techies. You need some painful experience with it and some experience on the
    alternative ways of running software application deployments to reliably see the madness seeping through.</p>
<p>I realize that when WordPress was released in 2003 the state of the art looked different and I applaud that they
    haven't broken much compatibility moving forward. WordPress is a big achievement. I would just never choose it
    today and I cannot recommend it today. And that concerns me. Especially as I don't see any strong options that
    can replace it. How would you run a website for non-technical editors today where you didn't have to sweat every
    deploy?</p>
<p>I have more to say on this website topic. Expect more posts on other slices of this particular pie.</p>
<p>If you do know of an option, or if I have missed the solution to this problem, please let me know at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a> on Twitter. If you want more of my writing I have
    a non-tracking newsletter which usually comes with
    podcast recommendations and more in-depth thinking. Often about sustainability in software.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>A wall too tall - Nerves &amp; k3s</title>
      <link>https://underjord.io/a-wall-too-tall-nerves-k3s.html</link>
      <pubDate>Wed, 06 May 2020 23:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/a-wall-too-tall-nerves-k3s.html</guid>
      <description>Short update on the general state of things. Pandemic quarantine in full swing. Me and mine are doing fine. Thankfully. We are staying at home awaiting a baby. I&#39;m likely to be fairly sporadic for a few months. But I do intend to keep writing. Most of my blog posts are written to be useful in the longer term. If you want more in-the-moment writing, the newsletter is more temporally anchored publication (signup further down, no pop-up).</description>
      <content:encoded><![CDATA[ 
<p>Short update on the general state of things. Pandemic quarantine in full swing. Me and mine are doing fine. Thankfully. We are staying at home awaiting a baby. I'm likely to be fairly sporadic for a few months. But I do intend to keep writing. Most of my blog posts are written to be useful in the longer term. If you want more in-the-moment writing, the newsletter is more temporally anchored publication (signup further down, no pop-up).</p>
<p>With all this and finishing up client work for April I really haven't had so much time to dive into the good Elixir stuff I want to provide here. But I stopped my client work from 1st of May to have some time off before parental leave. So obviously I started experimenting. This is <strong>a story about giving up on something</strong> for the foreseeable future after an intense investigation.</p>
<h2>The idea - A cheap but resilient distributed server infrastructure</h2>
<p>From a discussion with some activist people on Twitter the question was raised why there aren't activist data centers for the established networks of grassroots political activism. This centered on Swedish political movements, I intend to center this on the technical topic, if you have a genuine interest in my politics, get in touch, I'm not secretive. I started thinking about how to keep investment cost low since <em>I don't think most indie groups have the budget for an actual datacenter</em>.</p>
<p>As you might know I really like the <a href="https://www.nerves-project.org/">Nerves project</a> (new website, all grown up). I find their way of doing IoT or Connected Devices a healthy change of pace. Add NervesHub for safe and secure OTA firmware updates. Nerves is a central pillar in what I wanted to achieve with this idea. Putting <strong>device reliability</strong> and contributor simplicity front and center.</p>
<p>So my thoughts went to the Raspberry Pi 4 which is the most recent iteration and a quite performant little machine. There are almost certainly better options for optimizing compute per dollar but <em>the Pi are ubiquitous</em> and not particularly intimidating. And I'm pretty well familiar with both Nerves and these devices.</p>
<p>So I figured, to get the device ready, power supply, SD card, NervesKey chip with a private key burned into hardware to allow an operations group to update firmware in the field through NervesHub. Build your system on this and <strong>you can ship out the system to whoever</strong> wants to contribute bandwidth and power. As it starts up it can call back to NervesHub once installed at an appropriate location. From that authentication we can use that secure channel to provide additional information to bootstrap whatever software process and configuration we want. But this is outside the scope of what Nerves provides out of the box. So what to do there?</p>
<p>I have a buddy in the consulting space, Robin Morero of Spoonish (now <a href="https://fabled.se/">Fabled</a>) fame, who gets excited by devops, containers, deployment, automation, the finer points of scaling and high availability, all that stuff. He follows this world like I follow the Elixir ecosystem. So he offered me a few recommendations that I found sound. <a href="https://k3s.io/">k3s</a> as a lightweight replacement for Kubernetes (also known as k8s). It can be delivered as a single Go binary, around 40 Mb in total. <a href="https://rio.io/">Rio</a>, another Go binary, this time to provide a bunch of opinionated niceties on top of the Kubernetes concepts. We also discussed NAT, networking and some potential centralization issues there and he brought up <a href="https://github.com/mehrdadrad/radvpn">RadVPN</a> which seems super interesting.</p>
<p>Armed with this and having called him up and asked roughly a thousand questiosn about the details and what to expect I moved forward. I can dev the occasional ops but that's not what I know best. And this is precisely what he enjoys and excels at.</p>
<p>What would this give us if we can set up a Raspberry Pi with Nerves for underpinnings that basically goes directly into joining a lightweight Kubernetes cluster? It would allow us to <em>push updates via Nerves</em>, down to the OS-as-firmware level and <em>remote management even if the device can't join the cluster</em>. The hardware encryption chip should give a fair bit of safety and security in preventing random people running these devices from maliciously joining your cluster. It would at the very least make it non-trivial. Once joined to the cluster we can distribute application containers or whatever workloads we find necessary across these off-the-shelf hardware units. If an SD-card breaks, flash the firmware to a new one, <strong>nothing sensitive in the firmware</strong>. The thing starts back up and <em>bootstraps via the hardware key</em>.</p>
<p>This would make the buy-in to contribute to the server farm the cost of a Pi, case, power supply and the dirt cheap NervesKey chip. The trusted authorities that hold the crypto keys would have to bless the specific device before sending it out of course.</p>
<p>Note, I'm not trying to achieve decentralized, zero-trust infrastructure here. Because I want <strong>something that works</strong> (heh) in organizational structures that are already bootstrapped off of human relationships and a measure of trust. I'm trying to make something very <em>flexible and resilient with commodity hardware</em> which requires minimal knowledge to host on the contributor end. I wanted to try to verify if the idea works. So lets see where my explorations took me.</p>
<p>At this point I almost murdered my laptop to get Erlang + Homebrew to agree that OpenSSL exists on MacOS Catalina. Fair enough, I start my Nerves project. I throw some ARM binaries into the project via the rootfs overlay folder as <code>/cluster/k3s</code>. Then I spend some time fiddling with SD-cards, network cables and getting the Pi hooked up. Nerves did fine, my home network has too much NAT. I got the project up and running.</p>
<p>I run the binary from the iex prompt with a simple <code>/cluster/k3s server</code>  It logs a bunch of errors about being on a read-only filesystem. Yeah. Makes sense. Nerves sets you up with a writable <code>/root</code> for your application and some ephemeral <code>tmpfs</code> storage for <code>/tmp</code> but the system partition and all of that stuff is read-only to avoid SD card corruption bricking your device. Cool, cool.</p>
<p>My first approach was to see if I can restrict where k3s check for files, writes files, etc. The answer is, partly. There is a <code>--data-dir</code> option which as far as I went worked fine for the data directory. But it still expects to create configuration in <code>/etc/rancher/k3s</code>, do some other stuff in <code>/var/lib/rancher/k3s</code>.</p>
<p>Not entirely unreasonable. So I went for setting up separate partitions for just these folders. This meant making a custom version of the <code>nerves_system_pi4</code> repo and adding a bunch of partition-making in <code>fwup.conf</code> and some more stuff in the erlinit config file. After figuring out some compilation errors for the system I could settle in for the long compile time and I got my "system" built so it can be used from my project. This would have worked but hit the limit in fwup that says I can't use more than 4 partitions with an MBR approach. Not a shocker, just didn't think about it because I didn't do it in fdisk.</p>
<p>I had some other stuff to deal with from the logs as well, so to improve my odds of providing the environment that this would need I ripped most of the changes from Justin Schnecks <a href="https://github.com/mobileoverlord/nerves_system_docker_rpi4">Pi4 Docker system</a>.</p>
<p>New approach, I made sure the directories it required were in place. And then I mounted tmpfs mounts at those locations to allow k3s to write what it wanted. This seemed to work fine. I had to bump the size-allocation a bit.</p>
<p>Next problem. Cgroups. k3s seems to mostly expect systemd or System V. Nerves is neither afaik, it is a fairly minimal Buildroot setup. With Justin's Docker changes I did however seem to have most of the system details in place for cgroups and I figured out how to enable cpuacct specifically. I couldn't quite get k3s to absorb this information though and I think it expects some things that my system doesn't actually provide.</p>
<p>That was pretty much where I stopped my dive, after spending some evening time with it for three days. Buildroot and Linux internals are at the edge of what I know. Customizing these cluster-y devops setups is also at the point where I need to do constant reading and research to even know what I'm doing. I think this is feasible. But it was fairly clear to me that this uphill struggle was starting to look like a wall. I'm sure I could work through it. But I can't justify sinking more time into it right now. There are several lower-hanging fruit which I might actually be able to do without developing new skillsets from scratch.</p>
<p>This was incredibly educational. Nerves was well-behaved. The issues I'm having are inherent in trying to run k3s where it hasn't been intended to run versus the tightness of the Nerves system to stay resilient while avoiding bloat and complexity. k3s seemed like a nice project. I only got to barely scratch Rio as part of testing on Raspbian and I didn't get into RadVPN at all. The Nerves channel was helpful as always. Thanks to Justin and Frank specifically, but I know a few others chimed in as well. <a href="https://spoonish.tech/">Robin</a> was very helpful and if you want to sanity-check the way you run your infrastructure, he's definitely one to talk to.</p>
<p><strong>I don't think this idea is dead.</strong> But I need to set it aside currently, focus on more fruitful things. The <a href="https://github.com/lawik/nerves_system_rpi4_k3s">system repo is here</a> though you can basically start from a fresh pi4 fork. If you have questions about my thinking or want to pick it up and run with it. Feel free :)</p>
<p>For me, it was nice to revisit Nerves as I haven't had an opportunity in quite a bit. It was good to improve my understanding of Kubernetes, that ecosystem and also to try some alternative approaches. Overall I wanted to proof-of-concept this thing and it turned out to be a lot of finicky details to figure out to even try it well. I guess the proof-of-concept might be better done from Raspbian just to verify. But I already know the Pi4 can cluster from <a href="https://blog.alexellis.io/test-drive-k3s-on-raspberry-pi/">Will it cluster? k3s on your Raspberry Pi</a>. So that part wasn't interesting.</p>
<p>So the lesson today is, sometimes things are not worth doing or stop being fun when they are supposed to. Its OK to quit things when they suck or to cut your losses and go do something that feels more worthwhile. Especially when the only reason you are doing the thing is for your own sake. I feel very adult about all of this ;)</p>
<p>Stay safe out there.</p>
<p>If you need to tell me what I should have done instead, or have actual questions, I'm available at <a href="mailto:lars@underjord.io">lars@underjord.io</a> and occasionally on
    Twitter where I'm <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Self-evaluation improvements</title>
      <link>https://underjord.io/self-evaluation-improvements.html</link>
      <pubDate>Tue, 21 Apr 2020 06:15:00 +0000</pubDate>
      
      <guid>https://underjord.io/self-evaluation-improvements.html</guid>
      <description>A little while back I released a tool for self-evaluating as a web developer. I have just now updated it to include some explanation and guidance for things topics where the learner indicates a need for it.
You can try it for yourself at Web Development - Self-evaluation. It is centered on inexperienced developers. It might be of some benefit to old dogs that need to remind themselves about tricks forgotten as well.</description>
      <content:encoded><![CDATA[ 
<p>A little while back I released a tool for self-evaluating as a web developer. I have just now updated it to include
    some explanation and guidance for things topics where the learner indicates a need for it.</p>
<p>You can try it for yourself at <a href="https://evaluate.underjord.io/web">Web Development - Self-evaluation</a>. It
    is centered on inexperienced developers. It might be of some benefit to old dogs that need to remind themselves
    about tricks forgotten as well. Fill it out, the data is sent nowhere, it isn't tracked, you get a persistent link.
    You evaluate yourself, you change your answers as you learn.</p>
<p>It is technically quite simple. I wrote it in Elixir and Phoenix, because I enjoy those. The devil is in the writing,
    it turned out to be quite a lot of text to provide some context on 80+ topics of web development. I do think it can
    be quite useful and I hope you all give it a try.</p>
<p>If you just want to read it all, which I don't recommend, you can <a href="https://evaluate.underjord.io/web/result/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%3D">do
        so here</a>.</p>
<p>Very curious about your thoughts. I'm available at <a href="mailto:lars@underjord.io">lars@underjord.io</a>
    and occasionally on
    Twitter where I'm <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p>
<p>Take care of yourselves out there.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Check yourself - Web developer self-evaluation</title>
      <link>https://underjord.io/check-yourself.html</link>
      <pubDate>Wed, 08 Apr 2020 06:30:00 +0000</pubDate>
      
      <guid>https://underjord.io/check-yourself.html</guid>
      <description>I&#39;ve been thinking a lot about inexperienced (junior, if you must) web developers and just how much there is to learn about programming in general but the web in particular. You often hear people say that you don&#39;t need to know everything but you should have a solid foundation. Well, how do you establish a solid foundation and how do you know if you have one? How do you get introduced to all the relevant terminology and how do you find out what you haven&#39;t learned yet?</description>
      <content:encoded><![CDATA[ 
<p>I've been thinking a lot about inexperienced (junior, if you must) web developers and just how much there is to learn about programming in general but the web in particular. You often hear people say that you don't need to know everything but you should have a solid foundation. Well, how do you establish a solid foundation and how do you know if you have one? How do you get introduced to all the relevant terminology and how do you find out what you haven't learned yet?</p>
<p>I've created a tool that can help with this: <a href="https://evaluate.underjord.io/web">Web Development - Self-evaluation</a>. Fill it out, the data is sent nowhere aside from your client. You evaluate yourself, you change your answers as you learn. It could be pretty neat for learners and mentors/coaches/teachers alike.</p>
<p>It is still early days, right now it can give you an overview of areas you might want to get familiar with. I intend to add some actual information and instruction to it shortly but I wanted to get it out there since I think just the checklist is useful. It is not incredibly mobile-friendly at this point.</p>
<p>This doesn't really replace any other learning process. I just believe a tool like this can be useful to inform a learner or a teacher about what the learner actually needs to know better and where they feel more confident.</p>
<p>Very curious about your thoughts I'm available at <a href="mailto:lars@underjord.io">lars@underjord.io</a> and occasionally on
    Twitter where I'm <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>I always want to do it all</title>
      <link>https://underjord.io/i-always-want-to-do-it-all.html</link>
      <pubDate>Mon, 09 Mar 2020 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/i-always-want-to-do-it-all.html</guid>
      <description>My brain has very little chill on a day-to-day basis. There are moments where I can find a very peaceful state of mind. Doing something menial in the garden for an extended time, cooling off outside after a while in a sauna, winding down after heavy exercise. At most other times my mind is usually working on something or I&#39;m itching with the need to do things.
I&#39;ve had to do a lot of pacing myself recently.</description>
      <content:encoded><![CDATA[ 
<p>My brain has very little chill on a day-to-day basis. There are moments where I can find a very peaceful state of mind. Doing something menial in the garden for an extended time, cooling off outside after a while in a sauna, winding down after heavy exercise. At most other times my mind is usually working on something or I'm itching with the need to do things.</p>
<p>I've had to do a lot of pacing myself recently. The big thing is, we've bought a house, fixed a bunch of minor renovations and then moved in. This has been a fairly large chunk of work and continues to be a constant stream of unpacking boxes, getting things in order, reordering things that didn't work out. It isn't quite endless, but its definitely a long-distance run rather than something I can sprint. It takes continuous effort.</p>
<p>This eats into my happy creative lab time. I have so many things I want to do and very little time to do them. My writing has slowed down so I can keep up with client work in the midst of all this house-stuff. But my time to experiment with programming outside of client work has pretty much completely stopped.</p>
<p>So I wanted to share some things I want to sink my teeth into at some point. I don't imagine I'll do half of them, so don't consider this a todo-list, it's more of a want-to-do list and it is bound to change. Currently on the list are:</p>
<ul>
<li>Investigate if I can fix Pi4 support for Scenic and Nerves (Elixir, UI and hardware frameworks respectively), I've already dumped what info I could find in an issue on the Scenic Nerves Pi Driver</li>
<li>Build a general pointer device input driver for Scenic, mouse support and assorted touchpads/touchscreens, current one is only for the official 7" touch screen</li>
<li>Build a simple rendering driver for Scenic that draws using groldan's Chisel library rather than OpenGL</li>
<li>Get a simple screen specification up and running to share more library support between different simple Nerves display driver libraries</li>
<li>Experiment with building a web UI framework on the tools provided by Lumen</li>
<li>Finish up creation of some home sensor network stuff and actually deploy these sensors</li>
</ul>
<p>This is just a rough list off of the top of my head. But I felt like writing them out and I might as well share them once I do that. If any of these speak to you, feel free to run with them. They're just ideas, execution is the real thing that matters and I'm not precious about any of it.</p>
<p>Another exciting thing that's coming up is that we are making a room in the garage into a separate office, so my work-from-home setup will get significantly more serious as I'll have a proper office. This means a big opportunity to do some interior design which is fun and challenging.</p>
<p>I've also been thinking about streaming some things. I've gone from a bad ADSL to a very decent fiber setup so it feels doable now. Is there anything in particular that I've covered on this blog that you think would be beneficial to see me work on over Twitch or something? Let me know. I'm all ears.</p>
<p>So that's a quick update. Hope to find more writing time in the days to come. Take care now.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Lumen - Elixir &amp;amp; Erlang in the browser</title>
      <link>https://underjord.io/lumen-elixir-in-the-browser.html</link>
      <pubDate>Thu, 23 Jan 2020 23:59:00 +0000</pubDate>
      
      <guid>https://underjord.io/lumen-elixir-in-the-browser.html</guid>
      <description>The Lumen Project is an alternative implementation of the Erlang VM, more known as the BEAM. It is designed to work in WebAssembly with the specific goal of bringing Elixir and Erlang to the browser.
Note: This guide is outdated. These instructions will not work. Lumen is at the time of this update (2020-12-10) a moving target with an early release out. I plan on making an updated post at some point but will leave this post as it is.</description>
      <content:encoded><![CDATA[ 
<p>The Lumen Project is an alternative implementation of the Erlang VM, more known as the BEAM. It is designed to work
    in WebAssembly with the specific goal of bringing Elixir and Erlang to the browser.</p>

<p><strong>Note: This guide is outdated. These instructions will not work. Lumen is at the time of this update (2020-12-10) a moving target with an early release out. I plan on making an updated post at some point but will leave this post as it is. These instructions will not work.</strong></p>

<ul>
<li><a href="#future-illuminated">The future, illuminated?</a></li>
<li><a href="#try-it-yourself">Try it yourself</a></li>
</ul>
<p><em><strong>Note:</strong> Lumen is <em>still a pre-release</em> set of tools.</em> Don't expect anything to work.
    Don't expect any special features. It is moving steadily beyond proofs of concept and prototypes. But it is not a
    complete
    solution or full-fledged experience yet. If you want to know more about the details I really recommend the
    <a href="https://www.youtube.com/watch?v=uMgTIlgYB-U">announcement keynote from ElixirConf 2019</a>, YouTube at the
    link, embed below.</p>
<div class="iframe-embed"><iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="480" src="https://www.youtube.com/embed/uMgTIlgYB-U" width="625"></iframe>
</div>
<p><strong>This is not prime-time yet.</strong> With that disclaimer out of the way. The crew that is doing this work
    have been put together and funded by DockYard. That company is apparently hell-bent on making sure Elixir has enough
    cool projects to keep this blog running indefinitely. Much appreciated.</p>
<p>The Lumen team is working with the BEAM as
    their reference implementation, so while this is an alternate implementation that will differ in some details I
    don't expect this to be a split of the community efforts in any noticeable or detrimental way. As for my part in
    this, I'm just enthusiastic about it and I'm at most a community contributor. I'm not involved or paid beyond my own
    interest. If someone wants to pay me to work on Lumen in any capacity, I probably would. But that's not the case so
    the unbridled enthusiasm is my own.</p>
<h2 id="future-illuminated">The future, illuminated?</h2>
<p>So what do I think it means? Well. Phoenix LiveView provides one way of minimizing the amount of JS we as developers
    have to write and allow us to potentially focus on the application and let the harsh separation of frontend and
    backend fade into the background. Ideally. But if you have an application where the UI is highly interactive
    LiveView might not be the ideal solution. Server roundtrips can make it untenable, you might have specific needs
    that concern some web APIs. This is one case where I can see Lumen step in.</p>
<p>Brian Cardarella (of DockYard) has touched on Javascript development being a fairly significant timesink in client
    projects compared to what it has been in the past. I don't think of this as a big push for isometric applications in
    Elixir. But it would allow some of that. I think of it as being able to build the web-equivalent of
    the Scenic framework for web UI. Native web UI elements with the resilience of a supervision tree. Functional
    principles, not just tacked on in a prototype OOP whatever-JS-is-these-days. It also means
    that we can have a single project where some things are compiled to the frontend with Lumen and the backend runs on
    the battle-tested BEAM. I think we can discover ways in which OTP principles allow us to create new and useful web
    frameworks. And for those of us who enjoy Elixir or Erlang, we can work in our language with paradigms we enjoy.</p>
<p>An aside. I'm trying not to bash on Javascript because the community gets enough flack and that's not what
    this is about for me. JS and its ecosystem is not my preference and I have a hard time getting excited about JS as
    the future. I write things in JS and I don't overly complain, I'm a professional, not a baby. I also don't hold it
    against anyone if JS is their jam. That's fine. Back to Lumen.</p>
<p>What are the trade-offs of Lumen? Well, WebAssembly is still fairly fresh. The whole bindings thing between
    WebAssembly and
    Javascript is still early last I heard. So battle-tested it ain't. I think the Lumen team is taking a thoughtful and
    healthy approach to avoiding unnecessary bloat on the client. However, I think it will still be a bit on the heavier
    side compared to straight JS or something that is only Rust -> WebAssembly. Time will tell.</p>
<p>Another trade-off is that we are leaving behind the years of proven reliability of the BEAM. Lumen should provide the
    same potential to build resilient systems. But it won't have the raw history of time in production of the mainline
    BEAM. So that's a thing.</p>
<p>So what are they doing to make this feasible for web frontend dev? Well, as it stands Lumen provides modules that
    allow interaction from BEAM languages to the DOM, they are basic right now but the principles seems sound. Size is
    kept
    down by instead of providing a (big honking) VM that runs BEAM bytecode they are compiling whatever parts of the
    runtime are actually needed and only those. Essentially it compiles everything ahead of time and strips out anything
    you don't need that is in the Erlang or Elixir standard libraries. This kills hot code pluggability but seems
    entirely worthwhile. So it all compiles through LLVM with a combination of BEAM languages and Rust into a
    WebAssembly result that you can run in the browser. This is based off of what I've read, heard and seen but I
    reserve the right to be wrong about the details. I haven't gone deep enough to know first-hand.</p>
<p>The browser is a powerful runtime and that's how a lot of web apps use it. I think the Rust WebAssembly efforts do a
    lot to empower us developers to start considering non-JS options. I'll happily use JS in the future for things big
    and small. I just don't love the language and its not my preference. And in the long term, having things like Lumen
    will make the web a more powerful platform. There are also a lot of new tech that is targeting WebAssembly as a new
    common language, I think Lumen may end up having surprising potential in environments where the BEAM is a bit big or
    full ahead-of-time compilation makes more sense.</p>
<h2 id="try-it-yourself">Try it yourself</h2>
<p><em><strong>Note:</strong> Lumen is <em>still a pre-release</em> set of tools.</em></p>
<p>So can you try this bleeding-edge
    technology? Yes. I did. And then I hassled #lumen on Slack until I was content that
    there was something I could show that would be the correct level of interesting and exciting. So now I can share a
    fairly straight-forward approach to getting started with Lumen. It ends up running the interpreter in the browser
    rather than the process outlined above which would be compiling everything down to WebAssembly.</p>
<p>There is a Pull Request for the Lumen project as I just updated one of their examples to try making it more
    complete. You can find my branch and the relevant example <a href="https://github.com/lawik/lumen/tree/interpreter-demo-updates/examples/interpreter-in-browser">here</a> and
    try it yourself.</p>
<p>So lets walk through it. Lumen is built in Rust and if you want to help them with development you can follow the
    README at the root of <a href="https://github.com/lumen/lumen">their repo</a> to get a proper project setup. But to
    simplify, here come the compressed install instructions for MacOS. Start out with cloning the project git repo <a href="https://github.com/lawik/lumen">from my GitHub</a>, checkout the `interpreter-demo-updates` branch. We are
    using my branch because the PR is still in flight. I'll try to update this when it has been merged. <strong>Update:
        The PR for the example has been merged, you should just go ahead and clone from the main branch of
        lumen.</strong></p>


  <div class="code  shell "  data-file="/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">git clone https://github.com/lawik/lumen
cd lumen
git checkout interpreter-demo-updates
cd examples/interpreter-in-browser</code></pre></div>
  </div>


<p>Set up the basics you need for running the Rust stuff:</p>


  <div class="code  shell "  data-file="/lumen/examples/interpreter-in-browser" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/lumen/examples/interpreter-in-browser</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#75715e"># If you have rust installed through homebrew,</span>
<span style="color:#75715e"># you may need to uninstall it</span>
brew uninstall rust
<span style="color:#75715e"># Install rustup</span>
curl --proto <span style="color:#e6db74">&#39;=https&#39;</span> --tlsv1.2 -sSf https://sh.rustup.rs | sh
<span style="color:#75715e"># Activate the environment to not have to restart the terminal</span>
source ~/.cargo/env
rustup default nightly
rustup target add wasm32-unknown-unknown --toolchain nightly
cargo +nightly install wasm-bindgen-cli
<span style="color:#75715e"># Install wasm-pack</span>
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

<span style="color:#75715e"># Build the example</span>
wasm-pack build

<span style="color:#75715e"># Link the packages</span>
pushd www
npm install
popd

pushd pkg
npm link
popd

pushd www
npm link interpreter-in-browser
popd</code></pre></div>
  </div>


<p>Run the thing:</p>


  <div class="code  shell "  data-file="/lumen/examples/interpreter-in-browser" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/lumen/examples/interpreter-in-browser</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">cd www
npm run start
open http://localhost:8080 
<span style="color:#75715e"># Or just open the browser to that address ;)</span></code></pre></div>
  </div>


<p>To try your hand at sending new code to the interpreter, just edit the Erlang code in <code>www/ex_sample.erl</code>
    and it should reload (or you refresh by hand).
</p>
<p>I wrote some instructions for compiling Elixir code to Erlang code and using it with the interpreter, you can <a href="https://github.com/lawik/lumen/tree/interpreter-demo-updates/examples/interpreter-in-browser#compiling-from-elixir-to-erlang">follow
        them here</a>.</p>
<p>If you are interested in contributing to Lumen the team has been helpful in the #lumen Slack channel on the normal
    Elixir Slack and they've asked for help getting all the BIFs implemented previously. So I'm sure they'll help an
    enthusiastic contributor find their way.</p>
<p>I think the future looks bright. I'm very hopeful and optimistic about what Lumen might achieve.</p>
<p>If I screwed up some piece of the instructions, have an entirely inaccurate take on something or even better you have
    something positive to add I'm available at <a href="mailto:lars@underjord.io">lars@underjord.io</a> or on
    Twitter where I'm <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Why am I still excited about Elixir?</title>
      <link>https://underjord.io/why-am-i-still-excited-about-elixir.html</link>
      <pubDate>Mon, 20 Jan 2020 21:30:00 +0000</pubDate>
      
      <guid>https://underjord.io/why-am-i-still-excited-about-elixir.html</guid>
      <description>A good ol&#39; while back I wrote about why I&#39;m interested in Elixir. I think that deserves some follow-up.
So what&#39;s different? Well, I&#39;ve worked a lot more with Elixir in a professional capacity since then. I&#39;ve built more things in it both for work and play. I&#39;ve also gone digging and written about it in weird and extensive ways that I hadn&#39;t expected.
Professional work in Elixir I started working with a client that needed work done on an existing Elixir backend.</description>
      <content:encoded><![CDATA[ 
<p>A good ol' while back I wrote about <a href="why-am-i-interested-in-elixir.html">why I'm interested in
        Elixir</a>. I think that deserves some follow-up.</p>
<p>So what's different? Well, I've worked a lot more with Elixir in a professional capacity since then. I've
    built more things in it both for work and play. I've also gone digging and written about it in weird and
    extensive <a href="ecto-multi-tenancy-dynamic-repos-part-1-getting-started.html">ways that I hadn't
        expected</a>.</p>
<h2>Professional work in Elixir</h2>
<p>I started working with a client that needed work done on an existing Elixir backend. They needed to add an entirely
    new API to an existing solution. When I encountered the backend I was warned that it might be a bit messy. I agree
    that it had the signs of a system that had been through some startup-intensity development. I had concerns
    approaching it but its been fine.</p>
<p>What surprised me was how quickly I could get familiar with and understand the code. I'm pretty used to picking
    up a new system. But I think the fact that there aren't any complex class structures and that state is so
    isolated by of immutability reduced the cognitive effort of getting productive in the code-base. I could focus my
    brain on the business end of what I was doing.</p>
<h2>Product development in Phoenix</h2>
<p>I have a product that is still internal to my company that is being developed as a Phoenix backend which has so far
    been a fairly smooth ride. The biggest gotchas have been the aforementioned <a href="ecto-multi-tenancy-dynamic-repos-part-1-getting-started.html">deep dive into Ecto</a>
    and some things like <a href="elixir-signing-for-cloudfront-resources.html">CloudFront Signing</a>. When I encounter
    these edges that I end up spending
    time on I tend to write about them these days. I find it makes me push my understanding of what I'm doing
    further and I'm a lot more likely to be able to find the solution next time I go looking.</p>
<p>Lots of learning, lots of hands-on experience with what architecting in Phoenix will mean for you. It has been good
    overall. A lot of CRUD which is a bit dull but also some interesting cloud integration which mostly makes me dislike
    the AWS UI more and more.</p>
<p>I think my biggest take-away in both product work and client projects has been that I really quite like working with
    ExUnit. Testing Elixir-code feels straight-forward. It feels like the easiest way to verify whether I wrote the
    right code or not. I barely feel the need to run the code outside of tests.</p>
<h2>Automated complaints: Credo &amp; Dialyzer</h2>
<p>I've started working with both <a href="https://github.com/jeremyjh/dialyxir">Dialyzer</a> (mostly through the
    Elixir LS Fork + VS Code) and <a href="https://github.com/rrrene/credo">Credo</a> which both help
    keep me honest and catching mistakes, messy code, opportunities for refactor and a lot more.
</p>
<p>So Credo checks your code according to a bunch of best-practice principles. You can customize it, but I've been
    fine with the defaults.</p>
<p>Dialyzer performs analysis, mostly through type inference if I've understood it correctly to point out things
    like logically dead codepaths in your programs and such. Any time I've had Dialyzer complain, there has been an
    issue. Often not an issue that would cause an actual problem, but still incorrect code.</p>
<h2>Nerves &amp; Raspberry Pi 4</h2>
<p>The Pi 4 was released and Nerves supports it. It differs a lot from previous models. Its pretty cool hardware for the
    cost. I recommend trying it out. The most tried and true hobbyist hardware for Nerves is still the Pi 3, especially
    for Scenic. That's bound to shift over time. Anyway, the Pi 4 opens up more heavy-duty options within the same
    form-factor.</p>
<h2>The Lumen Project</h2>
<p>This one I have to dive deeper into soon. The grand promise of <a href="https://github.com/GetFirefly/firefly">this
        project</a> can be put quite succinctly.</p>
<p><strong>Elixir in the Browser.</strong></p>
<p>The details are more complex and a lot cooler. I know there have been projects for bringing Python to the browser
    which do so by providing a big compiled Python interpreter as JS download. I haven't looked into what ground has
    been won there with WebAssembly but I hope its significant. What Lumen is doing seems more ambitious to me.</p>
<p>Lumen and the related Eir project fills in some important gaps to get from an Elixir or Erlang application to
    compiling to WebAssembly through LLVM. Lumen is not a WebAssembly implementation of the Beam VM. It changes the
    approach to be potentially more lightweight by only compiling in the actually used parts of the runtime, compiling
    it all to a WebAssembly binary and integrating it with the browser.</p>
<p>The team has stated that they will follow the existing BEAM VM as their living standard for what Lumen should do and
    maintain as much compatibility as possible. They are dropping some features of the BEAM to win some web-friendly
    optimizations. Hot code swapping is removed to allow removing unused code for example.</p>
<p>There are examples (well there might be, link rot, see <a href="https://github.com/GetFirefly/firefly">Firefly</a>), you can run them
    now. You can run<em>*</em> Elixir<em>**</em> in your browser. Right now.</p>
<p>The ambition of Dockyard in doing this is exciting. They basically want to minimize
    the amount of JS they have to write and I think this approach might be feasible in the longer term. I really hope
    this can provide
    another option to the JS ecosystem. I find it workable but I don't particularly enjoy it.</p>
<p>Even if you don't believe in the idea I recommend watching <a href="https://www.youtube.com/watch?v=uMgTIlgYB-U">their presentation from ElixirConf</a> because I found it
    absolutely fascinating.</p>
<p><em>*</em> <i>There are still many limitations. You will likely only get running with the existing examples.</i></p>
<p><em>**</em> <i>Well, parts of the language. It is not entirely implemented, as not all of Erlang's BIFs are
        implemented yet.</i></p>
<h2>The Elixir Forum</h2>
<p><a href="https://elixirforum.com/">Visit the Elixir Forum</a></p>
<p>As I've been working rather than spending as much time chatting on the Elixir Slack I've found myself looking
    at more asynchronous communication channels and I've been visiting the Elixir Forum more and more. It is a
    well-run community that I've found rewarding to engage with. Very enjoyable. The Slack remains good as well.</p>
<h2>Any bad stuff?</h2>
<p>Not really, nothing in particular. I haven't seen any bad parts that feel particular to Elixir and friends. Maybe
    some vague Erlang errors messages. Oh, and I don't like reading the type spec stuff that comes out of dialyzer.
    Or writing them honestly. But it ain't terrible.</p>
<p>I haven't been able to pour the time I'd want into doing more Nerves stuff so I still find some of the driver
    and buildroot stuff impenetrable. I also haven't had much time for Scenic and I have some stuff I'd really
    want to build that relates to both of these projects. So yeah, bad stuff, I don't have time for all the things
    that I want to do. If another language can offer me this, let me know.</p>
<h2>In summary</h2>
<p>When I wrote about being interested in Elixir it was an ecosystem and community full of promise in my eyes. I had not
    spent much time doing serious work with it yet.</p>
<p>So far Elixir, Erlang/OTP and the BEAM have been delivering on what I hoped from them as tools. I'm still
    delighted to work with it on a day-to-day basis. I enjoy it more than I even expected to and there is so much more
    to get properly familiar with.</p>
<p>I find it easy to write, easy to read, easy to test and easy to reason about overall. It seems stupidly simple and
    provides a lot of elegance when you start getting fancy. I am every bit as excited about working with this tech
    going forward. I dare say </p>
<p>If you have thoughts, questions or want to get started with Elixir, let me know at <a href="mailto:lars@underjord.io">lars@underjord.io</a>. Consider signing up
    for my newsletter down below if you want more of my writing along with my favorite podcast recommendations.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Ecto &amp;amp; Multi-tenancy - Prefixes - Part 3</title>
      <link>https://underjord.io/ecto-multi-tenancy-prefixes-part-3.html</link>
      <pubDate>Fri, 10 Jan 2020 13:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/ecto-multi-tenancy-prefixes-part-3.html</guid>
      <description>This should be the final piece of this saga. Previous parts can be found here:
 Part 1 - Getting started Part 2  Now I&#39;ll cover the approach I finally ended up using. I had dynamic repos fully working but was somewhat haunted by the heavy-handed approach of running all those pools long before I knew anything about the loads I could expect for each tenant.
I think it would work fine.</description>
      <content:encoded><![CDATA[ 
<p>This should be the final piece of this saga. Previous parts can be found here:</p>
<ul>
<li><a href="ecto-multi-tenancy-dynamic-repos-part-1-getting-started.html">Part 1 - Getting started</a></li>
<li><a href="ecto-multi-tenancy-dynamic-repos-part-2.html">Part 2</a></li>
</ul>
<p>Now I'll cover the approach I finally ended up using. I had dynamic repos fully working but was somewhat haunted by
  the
  heavy-handed approach of running all those pools long before I knew anything about the loads I could expect for each
  tenant.</p>
<p>I think it would work fine. But it also felt like I had introduced a lot of complexity. More complexity than I wanted
  or should need. Elements of the solution felt
  somewhat fragile, such as the reliance on the Process dictionary and that repo selection would not survive through
  to a new process. And performance-wise I would need to tune both my pool sizes and my LRU approach to avoid
  potential trouble.</p>
<p>And that's when I noticed a guide in the Ecto docs which I must have overlooked. Like a mysterious shop in an
  80's mall. <a href="https://hexdocs.pm/ecto/multi-tenancy-with-query-prefixes.html#content" title="Multi-tenancy with query prefixes">Multi-tenancy with query prefixes</a> gave me another option
  entirely. I expected the prefix feature to be about the same as Wordpress, Drupal and such which generally just
  means namespacing all tables for the product with a name prefix to avoid collisions on cheap hosting where you only
  have one database. But no, it is better than that.</p>
<p>In Postgres it uses Schema which is a great terminology collision with Elixir in that both have a thing they call a
  Schema and they are completely different beasts. It basically means that by
  default your database tables live in the <code>public</code> Postgres schema and you can actually use the qualified
  name for them when querying as <code>select * from public.my_table ..</code>, but you don't have to, public is the
  default. This prefix functionality allows you to use Postgres Schemas (or schemata, but I'll go with schemas) to
  run separate instances of your Ecto Schema. So your end query might be
  <code>select * from customer_5.my_table ..</code> instead.</p>
<h2>Querying a prefix</h2>
<p>Since with prefixes all your data is in <strong>the same database</strong> you can use a single connection pool. But
  when you make a query you indicate clearly which prefix you want to work with by adding the
  <code>prefix: "customer_5"</code> keyword option.</p>
<h2>Setup and migrations</h2>
<p>I still needed my approach for runtime migrations. The migrations were unchanged I believe. I just needed to do some
  different setup when creating a new customer.</p>
<p>That ended up being a module like this:</p>


  <div class="code  elixir "  data-file="lib/my_app/customers/customer_selector.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/my_app/customers/customer_selector.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.Customers.CustomerSelector</span>
  <span style="color:#a6e22e">@moduledoc</span> <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">  Utilities that help with managing customer&#39;s prefixed databases.
</span><span style="color:#e6db74">  &#34;&#34;&#34;</span>

  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Ecto.Migration.SchemaMigration</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">MyApp.Repo.Migrations</span>

  <span style="color:#66d9ef">def</span> get_prefix_for_customer(customer) <span style="color:#66d9ef">do</span>
    <span style="color:#e6db74">&#34;customer_</span><span style="color:#e6db74">#{</span>customer<span style="color:#f92672">.</span>slug<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> create_customer_database_schema(customer) <span style="color:#66d9ef">do</span>
    prefix <span style="color:#f92672">=</span> get_prefix_for_customer(customer)

    config <span style="color:#f92672">=</span>
      <span style="color:#a6e22e">Application</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">:my_app</span>, <span style="color:#a6e22e">MyApp.Repo</span>)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>put(<span style="color:#e6db74">:name</span>, <span style="color:#66d9ef">nil</span>)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>put(<span style="color:#e6db74">:pool_size</span>, <span style="color:#ae81ff">2</span>)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>put(<span style="color:#e6db74">:migration_default_prefix</span>, prefix)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>put(<span style="color:#e6db74">:prefix</span>, prefix)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>delete(<span style="color:#e6db74">:pool</span>)

    {<span style="color:#e6db74">:ok</span>, pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>start_link(config)
    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>put_dynamic_repo(pid)

    query <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    CREATE SCHEMA &#34;</span><span style="color:#e6db74">#{</span>prefix<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>

    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>query(query)
    <span style="color:#a6e22e">SchemaMigration</span><span style="color:#f92672">.</span>ensure_schema_migrations_table!(<span style="color:#a6e22e">MyApp.Repo</span>, config)

    migrate_repo(
      <span style="color:#e6db74">prefix</span>: prefix,
      <span style="color:#e6db74">dynamic_repo</span>: pid
    )

    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>stop(<span style="color:#ae81ff">1000</span>)
    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>put_dynamic_repo(<span style="color:#a6e22e">MyApp.Repo</span>)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> migrate_repo(options \\ []) <span style="color:#66d9ef">do</span>
    options <span style="color:#f92672">=</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>put(options, <span style="color:#e6db74">:all</span>, <span style="color:#66d9ef">true</span>)

    <span style="color:#a6e22e">Ecto.Migrator</span><span style="color:#f92672">.</span>run(
      <span style="color:#a6e22e">MyApp.Repo</span>,
      <span style="color:#a6e22e">Migrations</span><span style="color:#f92672">.</span>get_migrations(),
      <span style="color:#e6db74">:up</span>,
      options
    )
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>Turns out all the work with dynamic repos left me well prepared to tackle the very same need here. We even use a
  dynamic repo to perform the migration. If there are better ways of making a schema through Ecto for Postgres I don't
  know about it. I couldn't find one. There may also be some belt-and-suspenders in the options here. But we basically
  just use a dynamic repo to create the customer's schema in Postgres, ensure the schema migrations metadata stuff is
  in place and then run the migrations.</p>
<h2>Drawbacks</h2>
<p>There is no one clever magic state we can fiddle with as for the process dictionary and put_dynamic_repo. Unless we
  actually use the process dictionary. But the lack of magic state is probably for the best. I'm currently just
  using
  the prefix explicitly. I'm considering whether it would make sense to make my repo module provide some
  additional helpers here. I looked into using the <code>prepare_query</code> hook but from what I could gather it
  couldn't affect the prefix. So explicitly I go. It's just this: <code>|> Repo.insert(prefix: prefix)</code></p>
<h2>Testing</h2>
<p>Prefixes definitely felt more like still being on the beaten/happy path, or very near to it, when it came to writing
  tests. I still have my own set of modifications. Mostly to set up migrations in the test helpers.</p>
<p>So my test helper looks like this:</p>


  <div class="code  elixir "  data-file="test/test_helper.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">test/test_helper.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#a6e22e">Ecto.Migrator</span><span style="color:#f92672">.</span>with_repo(<span style="color:#a6e22e">MyApp.Repo</span>, <span style="color:#66d9ef">fn</span> repo <span style="color:#f92672">-&gt;</span>
  <span style="color:#a6e22e">Ecto.Migrator</span><span style="color:#f92672">.</span>run(repo, <span style="color:#a6e22e">MyApp.Repo.Migrations</span><span style="color:#f92672">.</span>get_migrations(), <span style="color:#e6db74">:up</span>, <span style="color:#e6db74">all</span>: <span style="color:#66d9ef">true</span>)
<span style="color:#66d9ef">end</span>)

customer <span style="color:#f92672">=</span> %{
  <span style="color:#e6db74">name</span>: <span style="color:#e6db74">&#34;Test customer&#34;</span>,
  <span style="color:#e6db74">slug</span>: <span style="color:#e6db74">&#34;test_customer&#34;</span>
}

c <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyApp.Customers</span><span style="color:#f92672">.</span>get_customer_by_slug(customer<span style="color:#f92672">.</span>slug)

<span style="color:#75715e"># Clean up any existing instances of this customer from previous runs</span>
<span style="color:#66d9ef">if</span> c <span style="color:#66d9ef">do</span>
  <span style="color:#a6e22e">MyApp.Customers</span><span style="color:#f92672">.</span>delete_customer(c)
<span style="color:#66d9ef">end</span>

<span style="color:#75715e"># Create the customer</span>
{<span style="color:#e6db74">:ok</span>, customer} <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyApp.Customers</span><span style="color:#f92672">.</span>create_customer(customer)
<span style="color:#a6e22e">MyApp.Customers.CustomerSelector</span><span style="color:#f92672">.</span>create_customer_database_schema(customer)

<span style="color:#a6e22e">ExUnit</span><span style="color:#f92672">.</span>start(<span style="color:#e6db74">exclude</span>: [<span style="color:#e6db74">:skip</span>])

<span style="color:#a6e22e">Ecto.Adapters.SQL.Sandbox</span><span style="color:#f92672">.</span>mode(<span style="color:#a6e22e">MyApp.Repo</span>, <span style="color:#e6db74">:manual</span>)</code></pre></div>
  </div>


<p>And my ConnCase for example. DataCase is pretty similar:</p>


  <div class="code  elixir "  data-file="test/support/conn_case.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">test/support/conn_case.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyAppWeb.ConnCase</span> <span style="color:#66d9ef">do</span>
    <span style="color:#f92672">use</span> <span style="color:#a6e22e">ExUnit.CaseTemplate</span>
  
    <span style="color:#a6e22e">@customer_attrs</span> %{
      <span style="color:#e6db74">name</span>: <span style="color:#e6db74">&#34;Test customer&#34;</span>,
      <span style="color:#e6db74">slug</span>: <span style="color:#e6db74">&#34;test_customer&#34;</span>,
    }
  
    using <span style="color:#66d9ef">do</span>
      <span style="color:#66d9ef">quote</span> <span style="color:#66d9ef">do</span>
        <span style="color:#75715e"># Import conveniences for testing with connections</span>
        <span style="color:#f92672">use</span> <span style="color:#a6e22e">Phoenix.ConnTest</span>
        <span style="color:#f92672">alias</span> <span style="color:#a6e22e">MyAppWeb.Router.Helpers</span>, <span style="color:#e6db74">as</span>: <span style="color:#a6e22e">Routes</span>
  
        <span style="color:#75715e"># The default endpoint for testing</span>
        <span style="color:#a6e22e">@endpoint</span> <span style="color:#a6e22e">MyAppWeb.Endpoint</span>
      <span style="color:#66d9ef">end</span>
    <span style="color:#66d9ef">end</span>
  
    setup context <span style="color:#66d9ef">do</span>
      <span style="color:#e6db74">:ok</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Ecto.Adapters.SQL.Sandbox</span><span style="color:#f92672">.</span>checkout(<span style="color:#a6e22e">MyApp.Repo</span>)
  
      <span style="color:#66d9ef">unless</span> context[<span style="color:#e6db74">:async</span>] <span style="color:#66d9ef">do</span>
        <span style="color:#a6e22e">Ecto.Adapters.SQL.Sandbox</span><span style="color:#f92672">.</span>mode(<span style="color:#a6e22e">MyApp.Repo</span>, {<span style="color:#e6db74">:shared</span>, self()})
      <span style="color:#66d9ef">end</span>
  
      %{} <span style="color:#f92672">=</span> customer <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyApp.Customers</span><span style="color:#f92672">.</span>get_customer_by_slug(<span style="color:#a6e22e">@customer_attrs</span><span style="color:#f92672">.</span>slug)
  
      {<span style="color:#e6db74">:ok</span>, <span style="color:#e6db74">conn</span>: <span style="color:#a6e22e">Phoenix.ConnTest</span><span style="color:#f92672">.</span>build_conn(), <span style="color:#e6db74">customer</span>: customer}
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>And that's pretty much what I use.</p>
<p>Update: I think I should add a <code>prepare_query</code> similar to what Stephen Bussey suggest <a href="https://stephenbussey.com/2019/12/30/verifying-queries-with-ecto-s-prepare-query.html">in this blog post</a>
  but I'll check if the prefix is set for queries that should have it instead.</p>
<p>I really enjoyed diving deeper into Ecto. Parts were frustrating, parts were delightful. It was interesting to
  realize I was working outside the norm in my requirements and I'm glad to see the flexibility is indeed there.</p>
<p>I hope y'all enjoyed the adventures in Ecto Multi-tenancy. Or that it was at least useful to you. If you have
  corrections, questions, feedback or topics you want me to cover you can get in touch through <a href="mailto:lars@underjord.io">lars@underjord.io</a> or find me on Twitter where I'm <a href="https://hachyderm.io/@lawik">@lawik@hachyderm.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>A Slight Delight - Compile-time checking things</title>
      <link>https://underjord.io/a-slight-delight-compile-time-checking.html</link>
      <pubDate>Mon, 06 Jan 2020 11:30:00 +0000</pubDate>
      
      <guid>https://underjord.io/a-slight-delight-compile-time-checking.html</guid>
      <description>This was a short-but-sweet thing that struck me while working with a client code-base. It was trivial but both useful and delightful and it is a type of thing I haven&#39;t been able to do in Python, PHP and Javascript in quite the same way.
Initially, the code was something like this:
elixir  defmodule MyApp.Schemas do alias ExJson.Schema def my_schema do %{ &amp;#34;properties&amp;#34; =&amp;gt; %{ &amp;#34;email&amp;#34; =&amp;gt; %{ &amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;, }, &amp;#34;password&amp;#34; =&amp;gt; %{ &amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;, } } } |&amp;gt; Schema.</description>
      <content:encoded><![CDATA[ 
<p>This was a short-but-sweet thing that struck me while working with a client code-base. It was trivial but both useful
    and delightful and it is a type of thing I haven't been able to do in Python, PHP
    and Javascript in quite the same way.</p>
<p>Initially, the code was something like this:</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.Schemas</span> <span style="color:#66d9ef">do</span> 
    <span style="color:#f92672">alias</span> <span style="color:#a6e22e">ExJson.Schema</span>

    <span style="color:#66d9ef">def</span> my_schema <span style="color:#66d9ef">do</span> 
        %{
            <span style="color:#e6db74">&#34;properties&#34;</span> <span style="color:#f92672">=&gt;</span> %{
                <span style="color:#e6db74">&#34;email&#34;</span> <span style="color:#f92672">=&gt;</span> %{
                    <span style="color:#e6db74">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, 
                },
                <span style="color:#e6db74">&#34;password&#34;</span> <span style="color:#f92672">=&gt;</span> %{
                    <span style="color:#e6db74">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, 
                }
            }
        } <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Schema</span><span style="color:#f92672">.</span>resolve()
    <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>So for this JSON schema library the <code>Schema.resolve</code> function verifies that your JSON schema is defined in
    a
    valid manner and lives up to the JSON schema specification. So it doesn't do anything related to user-provided data
    or parsing JSON data. While familiarizing myself with the library I saw that you should probably avoid calling
    resolve repeatedly. Which makes sense, if the schema doesn't change, why call it again. Some previous developer
    probably missed that. It happens.</p>
<p>What I ended up doing was:</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">    <span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.Schemas</span> <span style="color:#66d9ef">do</span> 
        <span style="color:#f92672">alias</span> <span style="color:#a6e22e">ExJson.Schema</span>
    
        <span style="color:#a6e22e">@my_schema</span> <span style="color:#a6e22e">Schema</span><span style="color:#f92672">.</span>resolve(%{
            <span style="color:#e6db74">&#34;properties&#34;</span> <span style="color:#f92672">=&gt;</span> %{
                <span style="color:#e6db74">&#34;email&#34;</span> <span style="color:#f92672">=&gt;</span> %{
                    <span style="color:#e6db74">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, 
                },
                <span style="color:#e6db74">&#34;password&#34;</span> <span style="color:#f92672">=&gt;</span> %{
                    <span style="color:#e6db74">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, 
                }
            }
        })

        <span style="color:#66d9ef">def</span> my_schema <span style="color:#66d9ef">do</span> 
            <span style="color:#a6e22e">@my_schema</span>
        <span style="color:#66d9ef">end</span>
    <span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>So what does this give us? Well, module attributes are resolved at compile-time. This is why
    <code>@my_secret System.get_env("MY_SECRET")</code> often leads to unwanted behavior. But in this case, our schemas
    are part of our code and will not change at run-time or between environments. So we can shift the resolution to
    happen at compile-time and never have to do it at run-time.</p>
<ul>
<li>The cost of calling <code>Schema.resolve</code> is paid up front at compile-time.</li>
<li>Errors in defining JSON schemas show up at compile-time rather than run-time when you attempt to validate
        something.</li>
</ul>
<p>Having put much less time into compiled languages this was delightful. Being able to shift some checks back to
    compile-time is incredibly useful. I've also had a reason to write some macros which was fun.</p>
<p>If you have some suggestions, corrections or thoughts on this let me know at <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Elixir - Signing for Cloudfront resources</title>
      <link>https://underjord.io/elixir-signing-for-cloudfront-resources.html</link>
      <pubDate>Fri, 20 Dec 2019 10:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/elixir-signing-for-cloudfront-resources.html</guid>
      <description>This covers how to create Signed URL Custom Policies with Cloudfront in Elixir.
If you are working with Elixir and AWS you are probably familiar with ExAws. If you are unfamiliar, let&#39;s cover some quick ground:
 AWS - Amazon&#39;s cloud offering, has a whole bunch of oddly named services. S3 - File storage on AWS. Cloudfront - CDN for AWS, useful for better delivery performance for files for example.</description>
      <content:encoded><![CDATA[ 
<p>This covers how to create Signed URL Custom Policies with Cloudfront in Elixir.</p>
<p>If you are working with Elixir and AWS you are probably familiar with ExAws. If you are unfamiliar, let's cover
  some quick ground:</p>
<ul>
<li><strong>AWS</strong> - Amazon's cloud offering, has a whole bunch of oddly named services.</li>
<li><strong>S3</strong> - File storage on AWS.</li>
<li><strong>Cloudfront</strong> - CDN for AWS, useful for better delivery performance for files for example.</li>
<li><strong>ExAws</strong> - An incredibly useful Elixir library with a bunch of functionality for interacting with
    the AWS APIs to do most of what they offer.</li>
</ul>
<p>While exploring the ExAws library for a project I checked things like "can it presign URLs?", and sure
  enough ExAws.S3 has presign. "So I assume it can create a signed resource thing for Cloudfront, right?",
  well, not quite. So I was thinking that maybe no-one would have needed it yet. But that seemed a bit odd. And then I
  checked around a bit. One reason could be because Cloudfront presigning is not an API call. Its just crypto.</p>
<p>Lingo-wise, what we are doing should be a Signed URL using a Custom Policy. URL as opposed to Cookie (you can't
  have several of those, I need to be able to sign several) and Custom as opposed to Canned (I don't even remember
  the distinction anymore)</p>
<p>I didn't love the documentation that AWS provided but with some hacking I managed to create a signing script in
  bash which I could then attempt to port to Elixir. That lead to the module that follows. It uses the custom policy
  approach, you can read more about this stuff in the AWS docs. My use-case involves URL wildcards and signing for
  access to certain sections of an S3 bucket behind the CDN. I just use S3 presign for upload.</p>


  <div class="code  elixir "  data-file="my_app/infrastructure/cloudfront.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">my_app/infrastructure/cloudfront.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.Infrastructure.Cloudfront</span> <span style="color:#66d9ef">do</span>
  <span style="color:#a6e22e">@moduledoc</span> <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">  System tooling for signing download links and wildcard download policies.
</span><span style="color:#e6db74">  &#34;&#34;&#34;</span>
  <span style="color:#66d9ef">def</span> get_standard_expiration() <span style="color:#66d9ef">do</span>
    config <span style="color:#f92672">=</span> <span style="color:#a6e22e">Application</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">:my_app</span>, <span style="color:#a6e22e">MyApp.Infrastructure.Cloudfront</span>)
    expiration <span style="color:#f92672">=</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>get(config, <span style="color:#e6db74">:expiration</span>)
    timezone <span style="color:#f92672">=</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>get(config, <span style="color:#e6db74">:timezone</span>)

    {<span style="color:#e6db74">:ok</span>, dt} <span style="color:#f92672">=</span> <span style="color:#a6e22e">DateTime</span><span style="color:#f92672">.</span>now(timezone)
    dt <span style="color:#f92672">=</span> <span style="color:#a6e22e">DateTime</span><span style="color:#f92672">.</span>add(dt, expiration, <span style="color:#e6db74">:second</span>)
    {<span style="color:#e6db74">:ok</span>, dt} <span style="color:#f92672">=</span> <span style="color:#a6e22e">DateTime</span><span style="color:#f92672">.</span>shift_zone(dt, <span style="color:#e6db74">&#34;Etc/UTC&#34;</span>)
    dt
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> get_resource_url(resource_path) <span style="color:#66d9ef">do</span>
    config <span style="color:#f92672">=</span> <span style="color:#a6e22e">Application</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">:my_app</span>, <span style="color:#a6e22e">MyApp.Infrastructure.Cloudfront</span>)
    path <span style="color:#f92672">=</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>get(config, <span style="color:#e6db74">:path</span>)
    path <span style="color:#f92672">&lt;&gt;</span> resource_path
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> sign_for_resource(resource, dt_less_than \\ <span style="color:#66d9ef">nil</span>) <span style="color:#66d9ef">do</span>
    dt_less_than <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">case</span> dt_less_than <span style="color:#66d9ef">do</span>
        <span style="color:#66d9ef">nil</span> <span style="color:#f92672">-&gt;</span> get_standard_expiration()
        _ <span style="color:#f92672">-&gt;</span> dt_less_than
      <span style="color:#66d9ef">end</span>

    config <span style="color:#f92672">=</span> <span style="color:#a6e22e">Application</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">:my_app</span>, <span style="color:#a6e22e">MyApp.Infrastructure.Cloudfront</span>)
    access_key_id <span style="color:#f92672">=</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>get(config, <span style="color:#e6db74">:access_key_id</span>)
    private_key <span style="color:#f92672">=</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>get(config, <span style="color:#e6db74">:private_key</span>)

    sign_for_resource(resource, dt_less_than, access_key_id, private_key)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> sign_for_resource(resource, dt_less_than, key_id, private_key) <span style="color:#66d9ef">do</span>
    unixtime <span style="color:#f92672">=</span> <span style="color:#a6e22e">DateTime</span><span style="color:#f92672">.</span>to_unix(dt_less_than)

    payload <span style="color:#f92672">=</span>
      resource
      <span style="color:#f92672">|&gt;</span> create_custom_policy(unixtime)

    signature <span style="color:#f92672">=</span>
      payload
      <span style="color:#f92672">|&gt;</span> <span style="color:#e6db74">:public_key</span><span style="color:#f92672">.</span>sign(<span style="color:#e6db74">:sha</span>, private_key)
      <span style="color:#f92672">|&gt;</span> <span style="color:#e6db74">:base64</span><span style="color:#f92672">.</span>encode()

    encoded <span style="color:#f92672">=</span> <span style="color:#e6db74">:base64</span><span style="color:#f92672">.</span>encode(payload)

    [
      {<span style="color:#e6db74">&#34;Policy&#34;</span>, encoded},
      {<span style="color:#e6db74">&#34;Signature&#34;</span>, signature},
      {<span style="color:#e6db74">&#34;Key-Pair-Id&#34;</span>, key_id}
    ]
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> signature_to_query_string(signature) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>reduce(signature, <span style="color:#66d9ef">nil</span>, <span style="color:#66d9ef">fn</span> {key, value}, qs <span style="color:#f92672">-&gt;</span>
      <span style="color:#66d9ef">case</span> qs <span style="color:#66d9ef">do</span>
        <span style="color:#66d9ef">nil</span> <span style="color:#f92672">-&gt;</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">#{</span>key<span style="color:#e6db74">}</span><span style="color:#e6db74">=</span><span style="color:#e6db74">#{</span>value<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
        _ <span style="color:#f92672">-&gt;</span> qs <span style="color:#f92672">&lt;&gt;</span> <span style="color:#e6db74">&#34;&amp;amp;</span><span style="color:#e6db74">#{</span>key<span style="color:#e6db74">}</span><span style="color:#e6db74">=</span><span style="color:#e6db74">#{</span>value<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
      <span style="color:#66d9ef">end</span>
    <span style="color:#66d9ef">end</span>)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> load_key(private_key_filepath) <span style="color:#66d9ef">do</span>
    {<span style="color:#e6db74">:ok</span>, key_binary} <span style="color:#f92672">=</span> <span style="color:#e6db74">:file</span><span style="color:#f92672">.</span>read_file(private_key_filepath)
    [rsa_key_entry] <span style="color:#f92672">=</span> <span style="color:#e6db74">:public_key</span><span style="color:#f92672">.</span>pem_decode(key_binary)
    <span style="color:#e6db74">:public_key</span><span style="color:#f92672">.</span>pem_entry_decode(rsa_key_entry)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> create_custom_policy(resource, date_less_than) <span style="color:#66d9ef">do</span>
    policy <span style="color:#f92672">=</span> %{
      <span style="color:#e6db74">&#34;Statement&#34;</span> <span style="color:#f92672">=&gt;</span> [
        %{
          <span style="color:#e6db74">&#34;Resource&#34;</span> <span style="color:#f92672">=&gt;</span> resource,
          <span style="color:#e6db74">&#34;Condition&#34;</span> <span style="color:#f92672">=&gt;</span> %{
            <span style="color:#e6db74">&#34;DateLessThan&#34;</span> <span style="color:#f92672">=&gt;</span> %{
              <span style="color:#e6db74">&#34;AWS:EpochTime&#34;</span> <span style="color:#f92672">=&gt;</span> date_less_than
            }
          }
        }
      ]
    }

    <span style="color:#a6e22e">Jason</span><span style="color:#f92672">.</span>encode!(policy)
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>As Cloudfront signing requires a private key, of course you will need to be very careful about managing your secrets,
  especially that particular secret. If you have good ideas on how to approach this for development I'm all ears.
  Currently, sharing some credentials around a very small (two people) team is a minimal issue. But I'm not happy
  with it as a system.</p>
<p>This module only has one dependency and that's Jason. You can use any JSON library or actually have the JSON
  policy statement as a text template. I found this convenient as we already use Jason elsewhere. Other than that
  I've relied on the Erlang <code>:public_key</code> module for crypto.</p>
<p>It also demands that some values be set in your config, pretty standard fare.</p>
<p>I think the module is fairly straight-forward to follow. You give it a resource (an URL in my case) and an expiration
  time (dt_less_than), it creates a custom policy for you. If you have other needs than I did you may want to
  change things a bit but this should give you a good place to start. The generated policy can be added on to URLs to
  attempt to fetch the resource with the
  appropriately signed policy.</p>
<p>So if I want my authenticated users to be able to get anything under <code>fastfiles.underjord.io/user-files/</code>
  which I set up to be a Cloudfront distribution pointing to a non-public S3-bucket I could sign for them at login
  for <code>fastfiles.underjord.io/user-files/*</code> and they could use that to access
  <code>https://fastfiles.underjord.io/user-files/image.jpg</code> by appending the signature stuff as a query string.
  So you can give a time-based authorization to do something with your files to a user while still enjoying the
  performance of a CDN.</p>
<p>This is not something revolutionary. It's just an implementation. I simply didn't find any particularly good
  examples of doing this in code.
  Either Elixir or otherwise. AWS docs were rough but was what I had to go with. Punching through the SEO fog around
  anything Cloudfront + S3 for
  highly specific needs is increasingly difficult.</p>
<p>If you spot any problems, have any follow-up questions or anything like that, feel free to let me know at <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Happy little screens (with Elixir)</title>
      <link>https://underjord.io/happy-little-screens-with-elixir.html</link>
      <pubDate>Mon, 16 Dec 2019 16:23:00 +0000</pubDate>
      
      <guid>https://underjord.io/happy-little-screens-with-elixir.html</guid>
      <description>So me and Emilio Nyaray made Inky. We built on top of what was there from Nerves and Scenic and in the end we had the Inky series of eInk displays for Raspberry Pi devices working with Nerves through Elixir. Cool. That was a fun trip I&#39;ve covered previously:
 Inky library release Getting started, guide The story of how it came together Me &amp;amp; Emilio spoke about it on Elixir Mix (podcast)  Recently Frank Hunleth, Justin Schneck and Luis Gabriel Roldan (@groldan) who&#39;ve all been involved in some display-related shenanigans of different sorts move their repos into our organization that we&#39;ve used for Inky.</description>
      <content:encoded><![CDATA[ 
<p>So me and Emilio Nyaray made Inky. We built on top of what was there from Nerves and Scenic and in the end we had the
	Inky series of eInk displays for Raspberry Pi devices working with Nerves through Elixir. Cool. That was a fun trip
	I've covered previously:</p>
<ul>
<li><a href="/inky-library-release.html">Inky library release</a></li>
<li><a href="/an-eink-display-with-nerves-elixir.html">Getting started, guide</a></li>
<li><a href="/case-study-inky-an-elixir-library.html">The story of how it came together</a></li>
<li><a href="/i-was-on-a-podcast.html">Me &amp; Emilio spoke about it on Elixir Mix (podcast)</a></li>
</ul>
<p>Recently Frank Hunleth, Justin Schneck and Luis Gabriel Roldan (@groldan) who've all been involved in some
	display-related shenanigans of different sorts move their repos into our organization that we've used for Inky.
	We call it <a href="https://github.com/pappersverk/">pappersverk</a> because that sounds like a pretentious swedish
	arthouse of some sort. And
	also it means "paper mill" in swedish which made sense for our eInk stuff. Anyway, they got their related
	display stuff in there. So that's neat.</p>
<p>Groldan has made a driver for the SSD1306 (small black and white OLED display) which was what brought him into the
	conversation overall. But what he did next really got me. <a href="https://elixirforum.com/t/chisel-a-library-to-render-text-on-pixel-based-devices/27389">Here is the
		post</a> where he
	introduces <a href="https://github.com/luisgabrielroldan/chisel">Chisel</a> to the public. Chisel is a library for
	drawing bitmap fonts entirely in Elixir.</p>
<p>He let me try it out early. I did this:</p>
<p><img class="blog-image" src="assets/images/blog/chisel-inky-hello-world.jpg"/></p>
<p>So what's great about that? For one thing, no anti-aliasing. So text is rendered as intended. The way we've
	worked with Scenic has made text
	anti-aliased which works terribly with minimal resolution or few color displays. Also, it means we can skip Scenic
	as a dependency for many simple applications. That's a nice option.</p>
<p>But what this really makes me want to do is work out a proper standard for handling simple displays in Elixir so that
	all these different displays we may end up implementing support for will share a standard interface. This would
	allow us to implement a non-OpenGL Scenic Driver for example, it could draw text with Chisel, it could draw
	primitives with a similar approach and suddenly we could do simple UI for any number of display types where OpenGL
	and existing Scenic Drivers might not make the most sense.</p>
<p>It's not all about Scenic of course. This would also mean that development tooling for working with these
	displays, such as what we did with <code>inky_host_dev</code> might be done in more reusable manner. We could get
	away from writing everything for particular displays, even for the simpler displays.</p>
<p>We're not there now. We've only just started the discussion. But if you're interested, get involved, find
	us in the Elixir Slack under #nerves or #scenic. The work isn't particularly difficult but quite interesting and
	the people are friendly and enthusiastic.</p>
<p>Also, until then, try Chisel out. It will do a lot for small displays without much work. If you have the Inky,
	hooking that up is quite straight-forward.</p>
<p>If you have questions about this stuff, find me on the Elixir Lang Slack (in #nerves) or email me at <a href="mailto:lars@underjord.io">lars@underjord.io</a></p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Consider signing up for the Elixir Radar</title>
      <link>https://underjord.io/elixir-radar-referral.html</link>
      <pubDate>Fri, 08 Nov 2019 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/elixir-radar-referral.html</guid>
      <description>Old page, kept to avoid 404.</description>
      <content:encoded><![CDATA[ 
Old page, kept to avoid 404. ]]></content:encoded>
    </item>
    
    <item>
      <title>Ecto &amp;amp; Multi-tenancy - Dynamic Repos - Part 2</title>
      <link>https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-2.html</link>
      <pubDate>Fri, 01 Nov 2019 07:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-2.html</guid>
      <description>In the first part I covered the basics of getting started with Dynamic repositories with Ecto. Using that post we can create one or more repos at runtime, create the necessary database, run migrations to get it ready and then direct queries to it. That&#39;s a good start. Building blocks for something better. I&#39;ll try to get into the better bits here.
That said, I still ended up using prefixes because it made my code simpler.</description>
      <content:encoded><![CDATA[ 
<p>In <a href="/ecto-multi-tenancy-dynamic-repos-part-1-getting-started.html">the first part</a> I covered the basics of
  getting started with Dynamic repositories with Ecto. Using that post we can create one or more repos at runtime,
  create the necessary database, run migrations to get it ready and then direct queries to it. That's a good start.
  Building blocks for something better. I'll try to get into the better bits here.</p>
<p>That said, I still ended up using prefixes because it made my code simpler. It didn't end up as cool or as
  interesting. But it turned out a lot less suprising. I'm planning on covering prefixes in a separate post. You can
  read the <a href="https://hexdocs.pm/ecto/multi-tenancy-with-query-prefixes.html"></a>basic guide on prefixes</p> from
  the docs until then.
<p>A brief outline:</p>
<ul>
<li>Cleaning up runtime migrations</li>
<li>Managing multiple connection pools</li>
<li>Phoenix integration, plug and play</li>
<li>Testing</li>
</ul>
<h2>Cleaning up runtime migrations</h2>
<p>This is largely based on advice I received from <a href="https://github.com/elixir-ecto/ecto_sql/issues/152#issuecomment-536498513">José</a> during my dive into
  this stuff. I
  hadn't otherwise considered this solution.</p>
<p>By convention migrations live in <code>.exs</code> files, outside of your application. They live in the
  <code>priv</code> directory and are mostly a concern to whatever <code>mix ecto</code> commands you happen to be
  running. Since they
  aren't really a runtime concern normally this is reasonable. Keeps your application clean.</p>
<p>For us, they are a runtime concern. So what do we do? Well, <code>Ecto.Migrator.run</code> has support for getting a
  list of migration modules instead of a list of migration paths. Let's try that.</p>
<p>Given our trusted application <code>MyApp</code> and our repo module <code>MyApp.Repo</code> I'd simply add them
  to a migrations directory and make sure they live under <code>MyApp.Repo.Migrations.CreateMySpecificTable</code> and
  then create a <code>migrations.ex</code> :</p>


  <div class="code  elixir "  data-file="lib/repo/migrations.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/repo/migrations.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.Repo.Migrations</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">require</span> <span style="color:#a6e22e">Logger</span>

  <span style="color:#66d9ef">def</span> get_migrations() <span style="color:#66d9ef">do</span>
    migration_modules <span style="color:#f92672">=</span> [
      {<span style="color:#ae81ff">20190819_062207</span>, <span style="color:#a6e22e">MyApp.Repo.Migrations.CreateMySpecificTable</span>},
      {<span style="color:#ae81ff">20190819_063752</span>, <span style="color:#a6e22e">MyApp.Repo.Migrations.FixMySpecificTable</span>}
  ]

    <span style="color:#66d9ef">for</span> {_version, module} <span style="color:#f92672">&lt;-</span> migration_modules <span style="color:#66d9ef">do</span>
      <span style="color:#66d9ef">if</span> <span style="color:#f92672">!</span>migration?(module) <span style="color:#66d9ef">do</span>
        <span style="color:#a6e22e">Logger</span><span style="color:#f92672">.</span>error(
          <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">#{</span>inspect(module)<span style="color:#e6db74">}</span><span style="color:#e6db74"> does not seem to be a migration module. Please make sure that your migration files are .ex and not .exs for runtime migrations.&#34;</span>
        )
      <span style="color:#66d9ef">end</span>
    <span style="color:#66d9ef">end</span>

    migration_modules
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> migration?(mod) <span style="color:#66d9ef">do</span>
    function_exported?(mod, <span style="color:#e6db74">:__migration__</span>, <span style="color:#ae81ff">0</span>)
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>There is nothing special about this modules location.</p>
<p>This has more code than strictly necessary. But I shot myself in the foot last time I brought in some migrations
  because I didn't catch the <code>.ex</code> to <code>.exs</code> so I ripped out the private
  <code>migration?</code> check from the migrator to prevent myself from doing that again and I want to spare everyone
  else that nuisance.</p>
<p>So you can pass this <code>get_migrations()</code> result right into your <code>Ecto.Migrator.run</code>. So what
  have done here? We've made the migrations part of our application. Which makes sense since we want them to
  available at runtime.</p>
<h2>Managing multiple connection pools</h2>
<p>This was my next need, managing all the connection pools. So a consequence of this approach is that Ecto will start a
  connection pool for each tenant. I would have preferred being able to run a single pool for many databases but this is
  what we have for now.</p>
<p>So I set up a GenServer that you could ask for the repo PID of a customer. So it registers processes. But there are
  some extras that separate it from a straight up registry.</p>
<ul>
<li>When asking for a customer repo it will start one if there isn't one.</li>
<li>It maintains limits for how many repos we want to keep open at a time. Postgres has connection limits, we want to
    be able to stay within whatever limits we have on that end. On starting a new repo it would close old ones when
    close to the limit, LRU style.</li>
</ul>
<p>So the GenServer manages the registry state and some public functionality in the module would provide conveniences
  for creating a database, migrating a database and activating the one you need.</p>
<p>I ended up with code that looked like this:</p>


  <div class="code  elixir "  data-file="lib/customers/repomanager.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/customers/repomanager.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.Customers.RepoManager</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">GenServer</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">MyApp.Customers</span>

  <span style="color:#75715e"># Public API (client)</span>

  <span style="color:#66d9ef">def</span> start_link(settings) <span style="color:#f92672">when</span> is_list(settings) <span style="color:#66d9ef">do</span>
    settings <span style="color:#f92672">=</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>reduce(
      settings,
      %{},
      <span style="color:#66d9ef">fn</span> {key, value}, new <span style="color:#f92672">-&gt;</span>
        <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(new, key, value)
      <span style="color:#66d9ef">end</span>
    )
    start_link(settings)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> start_link(settings) <span style="color:#f92672">when</span> is_map(settings) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>start_link(__MODULE__, settings, <span style="color:#e6db74">name</span>: __MODULE__)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> get_database_name(customer) <span style="color:#66d9ef">do</span>
    <span style="color:#e6db74">&#34;database_</span><span style="color:#e6db74">#{</span>customer<span style="color:#f92672">.</span>slug<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> get_customer_options(customer) <span style="color:#66d9ef">do</span>
    customer_database <span style="color:#f92672">=</span> get_database_name(customer)
    config <span style="color:#f92672">=</span> <span style="color:#a6e22e">Application</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">:my_app</span>, <span style="color:#a6e22e">MyApp.Repo</span>)

    config
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>put(<span style="color:#e6db74">:name</span>, <span style="color:#66d9ef">nil</span>)
    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>put(<span style="color:#e6db74">:database</span>, customer_database)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> set_customer(customer, ensure_exists \\ <span style="color:#66d9ef">false</span>) <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># Yes, this is entirely side-effects</span>
    <span style="color:#66d9ef">if</span> ensure_exists <span style="color:#66d9ef">do</span>
      <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>cast(__MODULE__, {<span style="color:#e6db74">:ensure_repo_exists</span>, customer})
    <span style="color:#66d9ef">end</span>

    repo_pid <span style="color:#f92672">=</span> <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>call(__MODULE__, {<span style="color:#e6db74">:get_customer_pool</span>, customer})
    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>put_dynamic_repo(repo_pid)
    {<span style="color:#e6db74">:ok</span>, repo_pid}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> unset_customer(customer) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>call(__MODULE__, {<span style="color:#e6db74">:get_customer_pool</span>, customer})
    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>put_dynamic_repo(<span style="color:#a6e22e">MyApp.Repo</span>)
    <span style="color:#e6db74">:ok</span>
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> destroy_repo(customer, wait \\ <span style="color:#66d9ef">false</span>) <span style="color:#66d9ef">do</span>
    unset_customer(customer)
    options <span style="color:#f92672">=</span> get_customer_options(customer)

    <span style="color:#66d9ef">if</span> wait <span style="color:#66d9ef">do</span>
      <span style="color:#75715e"># Takes a bit</span>
      <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>__adapter__()<span style="color:#f92672">.</span>storage_down(options)
    <span style="color:#66d9ef">else</span>
      <span style="color:#a6e22e">Task</span><span style="color:#f92672">.</span>async(<span style="color:#66d9ef">fn</span> <span style="color:#f92672">-&gt;</span>
        <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>__adapter__()<span style="color:#f92672">.</span>storage_down(options)
      <span style="color:#66d9ef">end</span>)
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span>

  <span style="color:#75715e"># Callbacks (internal)</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> init(
        %{
          <span style="color:#e6db74">soft_limit</span>: soft_limit,
          <span style="color:#e6db74">hard_limit</span>: _hard_limit
        } <span style="color:#f92672">=</span> settings
      ) <span style="color:#66d9ef">do</span>
    <span style="color:#75715e"># Bonus point if we could sort this for most recently active</span>
    customers <span style="color:#f92672">=</span> <span style="color:#a6e22e">Customers</span><span style="color:#f92672">.</span>list_customers()

    <span style="color:#75715e"># Ensure all customers have databases</span>
    <span style="color:#66d9ef">for</span> customer <span style="color:#f92672">&lt;-</span> customers <span style="color:#66d9ef">do</span>
      ensure_repo_exists(customer)
    <span style="color:#66d9ef">end</span>

    state <span style="color:#f92672">=</span> %{
      <span style="color:#e6db74">pools</span>: %{},
      <span style="color:#e6db74">settings</span>: settings
    }

    state <span style="color:#f92672">=</span>
      customers
      <span style="color:#75715e"># Warm up by starting arbitrary customers up to the limit</span>
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>take(soft_limit)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>reduce(state, <span style="color:#66d9ef">fn</span> customer, state <span style="color:#f92672">-&gt;</span>
        start_connection_pool(customer, state)
      <span style="color:#66d9ef">end</span>)

    {<span style="color:#e6db74">:ok</span>, state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_call(
        {<span style="color:#e6db74">:get_customer_pool</span>, customer},
        _from,
        state
      ) <span style="color:#66d9ef">do</span>
    {repo_pid, state} <span style="color:#f92672">=</span> get_connection_pool(customer, state)
    {<span style="color:#e6db74">:reply</span>, repo_pid, state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_cast(
        {<span style="color:#e6db74">:ensure_repo_exists</span>, customer},
        state
      ) <span style="color:#66d9ef">do</span>
    ensure_repo_exists(customer)
    {<span style="color:#e6db74">:noreply</span>, state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
  <span style="color:#66d9ef">def</span> handle_cast(
        <span style="color:#e6db74">:clean_pool</span>,
        %{
          <span style="color:#e6db74">pools</span>: pools,
          <span style="color:#e6db74">settings</span>: %{
            <span style="color:#e6db74">soft_limit</span>: soft_limit
          }
        } <span style="color:#f92672">=</span> state
      ) <span style="color:#66d9ef">do</span>
    diff <span style="color:#f92672">=</span> map_size(pools) <span style="color:#f92672">-</span> soft_limit

    pools <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">if</span> diff <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">do</span>
        close_oldest(pools, diff)
      <span style="color:#66d9ef">else</span>
        pools
      <span style="color:#66d9ef">end</span>

    state <span style="color:#f92672">=</span> %{state <span style="color:#f92672">|</span> <span style="color:#e6db74">pools</span>: pools}

    {<span style="color:#e6db74">:noreply</span>, state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#75715e"># Internal functions</span>

  <span style="color:#66d9ef">defp</span> ensure_repo_exists(customer) <span style="color:#66d9ef">do</span>
    options <span style="color:#f92672">=</span> get_customer_options(customer)
    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>__adapter__()<span style="color:#f92672">.</span>storage_up(options)
    options <span style="color:#f92672">=</span> <span style="color:#a6e22e">Keyword</span><span style="color:#f92672">.</span>put(options, <span style="color:#e6db74">:pool_size</span>, <span style="color:#ae81ff">2</span>)
    {<span style="color:#e6db74">:ok</span>, repo_pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>start_link(options)
    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>put_dynamic_repo(repo_pid)

    <span style="color:#a6e22e">Ecto.Migrator</span><span style="color:#f92672">.</span>run(
      <span style="color:#a6e22e">MyApp.Repo</span>,
      <span style="color:#a6e22e">MyApp.Repo.Migrations</span><span style="color:#f92672">.</span>get_migrations(),
      <span style="color:#e6db74">:up</span>,
      <span style="color:#e6db74">all</span>: <span style="color:#66d9ef">true</span>,
      <span style="color:#e6db74">dynamic_repo</span>: repo_pid
    )

    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>stop(<span style="color:#ae81ff">1000</span>)
    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>put_dynamic_repo(<span style="color:#a6e22e">MyApp.Repo</span>)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> start_connection_pool(
         customer,
         %{<span style="color:#e6db74">pools</span>: pools, <span style="color:#e6db74">settings</span>: settings} <span style="color:#f92672">=</span> state
       ) <span style="color:#66d9ef">do</span>
    diff <span style="color:#f92672">=</span> map_size(pools) <span style="color:#f92672">-</span> settings<span style="color:#f92672">.</span>hard_limit

    pools <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">if</span> diff <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">do</span>
        close_oldest(pools, diff)
      <span style="color:#66d9ef">else</span>
        pools
      <span style="color:#66d9ef">end</span>

    options <span style="color:#f92672">=</span> get_customer_options(customer)
    {<span style="color:#e6db74">:ok</span>, repo_pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>start_link(options)
    last_used <span style="color:#f92672">=</span> timestamp()
    pools <span style="color:#f92672">=</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(pools, customer<span style="color:#f92672">.</span>id, {repo_pid, last_used})

    <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>cast(<span style="color:#e6db74">:clean_pool</span>, state)

    %{state <span style="color:#f92672">|</span> <span style="color:#e6db74">pools</span>: pools}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> get_connection_pool(customer, %{<span style="color:#e6db74">pools</span>: pools, <span style="color:#e6db74">settings</span>: _settings} <span style="color:#f92672">=</span> state) <span style="color:#66d9ef">do</span>
    pool <span style="color:#f92672">=</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>get(pools, customer<span style="color:#f92672">.</span>id, <span style="color:#66d9ef">nil</span>)

    state <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">if</span> pool <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> <span style="color:#66d9ef">do</span>
        start_connection_pool(customer, state)
      <span style="color:#66d9ef">else</span>
        state
      <span style="color:#66d9ef">end</span>

    %{<span style="color:#e6db74">pools</span>: pools} <span style="color:#f92672">=</span> state

    {repo_pid, _last_used} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>get(pools, customer<span style="color:#f92672">.</span>id, <span style="color:#66d9ef">nil</span>)

    <span style="color:#75715e"># Update usage timestamp</span>
    pools <span style="color:#f92672">=</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(pools, customer<span style="color:#f92672">.</span>id, {repo_pid, timestamp()})
    state <span style="color:#f92672">=</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(state, <span style="color:#e6db74">:pools</span>, pools)

    <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>cast(<span style="color:#e6db74">:clean_pool</span>, state)

    {repo_pid, state}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> close_oldest(pools, number) <span style="color:#66d9ef">do</span>
    {trim, keep} <span style="color:#f92672">=</span>
      pools
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>sort_by(<span style="color:#66d9ef">fn</span> {_customer_id, {_repo_pid, last_used}} <span style="color:#f92672">-&gt;</span>
        last_used
      <span style="color:#66d9ef">end</span>)
      <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>split(number)

    <span style="color:#66d9ef">for</span> {_customer_id, {repo_pid, _last_used}} <span style="color:#f92672">&lt;-</span> trim <span style="color:#66d9ef">do</span>
      close_pool(repo_pid)
    <span style="color:#66d9ef">end</span>

    <span style="color:#75715e"># Recreate map for the rest</span>
    <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>reduce(keep, %{}, <span style="color:#66d9ef">fn</span> {customer_id, pool}, pools <span style="color:#f92672">-&gt;</span>
      <span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(pools, customer_id, pool)
    <span style="color:#66d9ef">end</span>)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> close_pool(repo_pid) <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>put_dynamic_repo(repo_pid)
    <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>stop(<span style="color:#ae81ff">1000</span>)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> timestamp <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">DateTime</span><span style="color:#f92672">.</span>utc_now()
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>It runs a GenServer that keeps track of your connection pools and can give you access to them on-demand. It will also
  do some simplistic enforcing of limits on the number of pools. This allows some control of Postgres connection limits,
  memory usage and such.</p>
<p>This was an effort that never quite made it into use since I switched to prefixes. So consider your use case, check
  your assumptions and if anything, just use this code as an indicator of one way to go about it. It hasn't earned any
  battle-scars yet.</p>
<h2>Phoenix integration, plug and play</h2>
<p>To avoid having every developer that ever makes an Ecto query needing to remember to call
  <code>put_dynamic_repo</code>I figured it might be worthwhile to do that automatically. So I built a plug that did
  this. It assumes a few things about your API path structure. It uses the RepoManager we created above to set your
  dynamic repo based on information in the request.</p>


  <div class="code  elixir "  data-file="lib/customers/customer_plug.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/customers/customer_plug.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.Customers.CustomerPlug</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">import</span> <span style="color:#a6e22e">Plug.Conn</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">MyApp.Customers</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">MyApp.Customers.RepoManager</span>

  <span style="color:#66d9ef">def</span> init(options) <span style="color:#66d9ef">do</span>
    options
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">def</span> call(conn, _opts) <span style="color:#66d9ef">do</span>
    customer_id <span style="color:#f92672">=</span> extract_customer_id(conn)
    customer <span style="color:#f92672">=</span> <span style="color:#a6e22e">Customers</span><span style="color:#f92672">.</span>get_customer!(customer_id)
    {<span style="color:#e6db74">:ok</span>, repo_pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>set_customer(customer)

    conn
    <span style="color:#f92672">|&gt;</span> assign(<span style="color:#e6db74">:customer_id</span>, customer_id)
    <span style="color:#f92672">|&gt;</span> assign(<span style="color:#e6db74">:customer</span>, customer)
    <span style="color:#f92672">|&gt;</span> assign(<span style="color:#e6db74">:customer_repo</span>, repo_pid)
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> extract_customer_id(
         %<span style="color:#a6e22e">Plug.Conn</span>{
           <span style="color:#e6db74">path_info</span>: path_info
         } <span style="color:#f92672">=</span> conn
       ) <span style="color:#66d9ef">do</span>
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">List</span><span style="color:#f92672">.</span>starts_with?(path_info, [<span style="color:#e6db74">&#34;api&#34;</span>, <span style="color:#e6db74">&#34;customers&#34;</span>]) <span style="color:#66d9ef">do</span>
      <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>fetch(path_info, <span style="color:#ae81ff">2</span>) <span style="color:#66d9ef">do</span>
        {<span style="color:#e6db74">:ok</span>, customer_id_string} <span style="color:#f92672">-&gt;</span>
          <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Integer</span><span style="color:#f92672">.</span>parse(customer_id_string) <span style="color:#66d9ef">do</span>
            {integer, _} <span style="color:#f92672">-&gt;</span> integer
            <span style="color:#e6db74">:error</span> <span style="color:#f92672">-&gt;</span> auth_failed(conn)
          <span style="color:#66d9ef">end</span>

        <span style="color:#e6db74">:error</span> <span style="color:#f92672">-&gt;</span>
          auth_failed(conn)
      <span style="color:#66d9ef">end</span>
    <span style="color:#66d9ef">else</span>
      auth_failed(conn)
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> auth_failed(conn) <span style="color:#66d9ef">do</span>
    send_resp(conn, <span style="color:#ae81ff">400</span>, <span style="color:#e6db74">&#34;Invalid request, customer authentication failed.&#34;</span>)
    halt(conn)
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>This means that for the current process you will have the right dynamic repo set already, based on what the query is
  about. In my case I would likely add <strong>authorization</strong> to this plug as well to make sure only the
  appropriate users get access to a given customer.</p>
<p>Any developer can just keep using Ecto the way one usually would within the realm of a Phoenix application for
  example. But if you spawn a process you are bound to lose your carefully selected dynamic process, because that is set
  using the process dictionary for the current process. So in the end there may be patterns that are quite a bit safer.
  But as long as your default repo doesn't work at least you'll get an error trying to run queries to let you
  know that you failed to do the right thing.</p>
<h2>Testing</h2>
<p>I found testing to be a bit of a challenge. During testing we generally have the Ecto Sandbox to keep things nice,
  fast and contained. But if you are testing this sort of thing you might want to either break out of the sandbox or
  make darn sure you are using it in the right way.</p>
<p>And do you want all your tests to actually exercise the database approach you have or do you want to limit that to
  the most important. I spent a bunch of time fighting with the sandbox functionality, performance and trying to find a
  good balance.</p>
<p>So testing-wise it definitely feels like you'll be a bit off of the beaten path. I think you can successfully
  bring most of your tests back into the fold and have a fairly normal experience and fast tests. The tests that I
  started with created a dynamic repo for each test, setup and teardown for those took forever.</p>
<p>This was my setup most recently.</p>
<p>I do not start the dynamic repo in the my application. I start my customer repo there. And my test helper reflects
  that.</p>


  <div class="code  elixir "  data-file="test/test_helper.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">test/test_helper.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#a6e22e">ExUnit</span><span style="color:#f92672">.</span>start()

<span style="color:#a6e22e">Ecto.Adapters.SQL.Sandbox</span><span style="color:#f92672">.</span>mode(<span style="color:#a6e22e">MyApp.Customers.CentralRepo</span>, <span style="color:#e6db74">:manual</span>)</code></pre></div>
  </div>


<p>I couldn't find a good way to work with the sandbox and dynamic repos. So for the tests I used:</p>


  <div class="code  elixir "  data-file="test/test_helper.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">test/test_helper.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#f92672">..</span>
  
config <span style="color:#e6db74">:my_app</span>, <span style="color:#a6e22e">MyApp.Repo</span>,
  <span style="color:#e6db74">username</span>: <span style="color:#e6db74">&#34;postgres&#34;</span>,
  <span style="color:#e6db74">password</span>: <span style="color:#e6db74">&#34;postgres&#34;</span>,
  <span style="color:#e6db74">database</span>: <span style="color:#e6db74">&#34;my_app_test&#34;</span>,
  <span style="color:#e6db74">hostname</span>: <span style="color:#e6db74">&#34;localhost&#34;</span>,
  <span style="color:#e6db74">pool_size</span>: <span style="color:#ae81ff">2</span>

  <span style="color:#f92672">..</span></code></pre></div>
  </div>


<p>To test the repos and repo management I used this:</p>


  <div class="code  elixir "  data-file="test/my_app/repo_test.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">test/my_app/repo_test.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyApp.RepoTest</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">ExUnit.Case</span>

  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">MyApp.Quality</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">MyApp.Customers</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">MyApp.Customers.RepoManager</span>

  <span style="color:#66d9ef">def</span> new_customer_attrs() <span style="color:#66d9ef">do</span>
    %{
      <span style="color:#e6db74">name</span>: <span style="color:#e6db74">&#34;Test customer&#34;</span>,
      <span style="color:#e6db74">slug</span>: <span style="color:#a6e22e">Ecto.UUID</span><span style="color:#f92672">.</span>generate(),
    }
  <span style="color:#66d9ef">end</span>

  <span style="color:#a6e22e">@create_published_attrs</span> %{
    <span style="color:#e6db74">title</span>: <span style="color:#e6db74">&#34;some title&#34;</span>,
    <span style="color:#e6db74">text</span>: <span style="color:#e6db74">&#34;some text&#34;</span>
  }

  setup <span style="color:#66d9ef">do</span>
    <span style="color:#e6db74">:ok</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Ecto.Adapters.SQL.Sandbox</span><span style="color:#f92672">.</span>checkout(<span style="color:#a6e22e">MyApp.Customers.CentralRepo</span>)
    <span style="color:#a6e22e">Ecto.Adapters.SQL.Sandbox</span><span style="color:#f92672">.</span>mode(<span style="color:#a6e22e">MyApp.Customers.CentralRepo</span>, {<span style="color:#e6db74">:shared</span>, self()})
    <span style="color:#e6db74">:ok</span>
  <span style="color:#66d9ef">end</span>

  describe <span style="color:#e6db74">&#34;dynamic repos&#34;</span> <span style="color:#66d9ef">do</span>
    test <span style="color:#e6db74">&#34;create documentation with repo&#34;</span> <span style="color:#66d9ef">do</span>
      {<span style="color:#e6db74">:ok</span>, customer} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Customers</span><span style="color:#f92672">.</span>create_customer(new_customer_attrs())
      <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>set_customer(customer, <span style="color:#66d9ef">true</span>)
      {<span style="color:#e6db74">:ok</span>, documentation} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Quality</span><span style="color:#f92672">.</span>create_documentation(<span style="color:#a6e22e">@create_published_attrs</span>)

      assert documentation<span style="color:#f92672">.</span>id <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span>

      <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>destroy_repo(customer)
    <span style="color:#66d9ef">end</span>

    test <span style="color:#e6db74">&#34;create documentation with two different repos&#34;</span> <span style="color:#66d9ef">do</span>
      {<span style="color:#e6db74">:ok</span>, customer} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Customers</span><span style="color:#f92672">.</span>create_customer(new_customer_attrs())
      <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>set_customer(customer, <span style="color:#66d9ef">true</span>)

      {<span style="color:#e6db74">:ok</span>, documentation} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Quality</span><span style="color:#f92672">.</span>create_documentation(<span style="color:#a6e22e">@create_published_attrs</span>)
      assert documentation<span style="color:#f92672">.</span>id <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span>

      {<span style="color:#e6db74">:ok</span>, other_customer} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Customers</span><span style="color:#f92672">.</span>create_customer(new_customer_attrs())
      <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>set_customer(other_customer, <span style="color:#66d9ef">true</span>)

      {<span style="color:#e6db74">:ok</span>, documentation} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Quality</span><span style="color:#f92672">.</span>create_documentation(<span style="color:#a6e22e">@create_published_attrs</span>)
      assert documentation<span style="color:#f92672">.</span>id <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span>

      <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>destroy_repo(customer)
      <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>destroy_repo(other_customer)
    <span style="color:#66d9ef">end</span>
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>



<p>These tests create repos on-demand and verify that this functionality works as expected. Creating the same ID in
  parallell.</p>
<p>I wouldn't swear to this <code>conn_case.ex</code> below being perfect. I know I fought quite a bit getting to a
  place where the RepoManager and the sandbox wouldn't be a complete shitshow. This was part of both
  <code>ConnCase</code> and <code>DataCase</code> to make sure other tests did not have to be written with customer
  management in mind.</p>


  <div class="code  elixir "  data-file="test/support/conn_case.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">test/support/conn_case.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#f92672">..</span>
  setup_all tags <span style="color:#66d9ef">do</span>
    <span style="color:#e6db74">:ok</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Ecto.Adapters.SQL.Sandbox</span><span style="color:#f92672">.</span>checkout(<span style="color:#a6e22e">MyApp.Customers.CentralRepo</span>)

    <span style="color:#66d9ef">unless</span> tags[<span style="color:#e6db74">:async</span>] <span style="color:#66d9ef">do</span>
      <span style="color:#a6e22e">Ecto.Adapters.SQL.Sandbox</span><span style="color:#f92672">.</span>mode(
        <span style="color:#a6e22e">MyApp.Customers.CentralRepo</span>,
        {<span style="color:#e6db74">:shared</span>, self()}
      )
    <span style="color:#66d9ef">end</span>

    {<span style="color:#e6db74">:ok</span>, customer} <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyApp.Customers</span><span style="color:#f92672">.</span>create_customer(<span style="color:#a6e22e">@customer_attrs</span>)

    <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>set_customer(customer, <span style="color:#66d9ef">true</span>)

    <span style="color:#e6db74">:ok</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Ecto.Adapters.SQL.Sandbox</span><span style="color:#f92672">.</span>checkin(<span style="color:#a6e22e">MyApp.Customers.CentralRepo</span>)

    on_exit(<span style="color:#66d9ef">fn</span> <span style="color:#f92672">-&gt;</span>
      <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>set_customer(customer)
      <span style="color:#a6e22e">RepoManager</span><span style="color:#f92672">.</span>destroy_repo(customer)
    <span style="color:#66d9ef">end</span>)

    <span style="color:#e6db74">:ok</span>
  <span style="color:#66d9ef">end</span>
  <span style="color:#f92672">..</span></code></pre></div>
  </div>


<p>So we set up cases to use a specific dynamic repo and unless they screw around with processes they don't have to care
  about the current customer or current repo. I think that was more elegant than passing a customer everywhere and using
  some boilerplate to get every test in line. With prefixes I took another approach. Which I'll cover when I cover that.
  If there is interest.</p>
<p>Update: <a href="ecto-multi-tenancy-prefixes-part-3.html">Part 3 - Prefixes</a> is available.</p>
<p>Would you be interested in more Ecto-related posts? Or does your business need someone to dive deep into these odd
  corners of our beloved ecosystem and draw a map in this manner. I rather enjoy it and would be happy to figure
  something out. Let me know at <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>I was on a podcast</title>
      <link>https://underjord.io/i-was-on-a-podcast.html</link>
      <pubDate>Wed, 23 Oct 2019 06:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/i-was-on-a-podcast.html</guid>
      <description>You can listen to it here.
Mark Ericksen of Elixir Mix got in touch and asked if I&#39;d come on to talk about the things I&#39;ve done with Inky, Elixir and Nerves. So I did. And I brought my co-conspirator Emilio with me. I thought the episode turned out well. I certainly enjoyed it and I haven&#39;t been on a podcast before. So thanks to my readers, the community and the Elixir Mix gang for the opportunity.</description>
      <content:encoded><![CDATA[ 
<p>You can <a href="https://pca.st/u8q91lhy">listen to it here</a>.</p>
<p>Mark Ericksen of Elixir Mix got in touch and asked if I'd come on to talk about the things I've done with
    Inky, Elixir and Nerves. So I did. And I brought my co-conspirator Emilio with me. I thought the episode turned out
    well. I certainly enjoyed it and I haven't been on a podcast before. So thanks to my readers, the community and the
    Elixir Mix gang for the opportunity.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Ecto &amp;amp; Multi-tenancy - Dynamic Repos - Part 1 - Getting started</title>
      <link>https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-1-getting-started.html</link>
      <pubDate>Mon, 14 Oct 2019 21:37:00 +0000</pubDate>
      
      <guid>https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-1-getting-started.html</guid>
      <description>Ecto is the database library we know and love from the Elixir ecosystem. It is used by default in Phoenix, the high-profile web framework. Ecto has a bunch of cool features and ideas. But this post is about a corner full of nuts, bolts and very little of the shiny or hot stuff. It just covers some rather specific needs. Ecto docs for these features are this guide and this API.</description>
      <content:encoded><![CDATA[ 
<p>Ecto is the database library we know and love from the Elixir ecosystem. It is used by default in Phoenix, the
	high-profile
	web framework. Ecto has a bunch of cool features and ideas. But this post is about a corner full of nuts, bolts and
	very little of the shiny or hot stuff. It just covers some rather specific needs. Ecto docs for these features are
	<a href="https://hexdocs.pm/ecto/replicas-and-dynamic-repositories.html#content">this guide</a> and <a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:put_dynamic_repo/1">this API</a>. But that is usually not the
	whole picture. I'll try to cover some of the practicalities.</p>
<p><strong>Fair warning:</strong> You probably <em>do not need dynamic repos</em>. Investigate <a href="https://hexdocs.pm/ecto/multi-tenancy-with-query-prefixes.html#content">prefixes</a> first to try and
	keep things simple. Implementing prefixes is significantly simpler. This feature is high power but has the potential
	to bring a lot of complexity.</p>
<p>A brief outline:</p>
<ul>
<li>What are Dynamic Repos?</li>
<li>Starting a repo</li>
<li>Activating a repo for use</li>
<li>Creating a repo</li>
<li>Running migrations</li>
</ul>
<h2>What are Dynamic Repos?</h2>
<p>They provide one approach for multi-tenancy for Ecto. Multi-tenancy in the sense of being able to use multiple copies
	of a single database definition (a repo) in the same application.</p>
<p>Or more technically, they are independent instances of your repo-module that you can start with a varied set of
	runtime configurations.</p>
<h3>Why though?</h3>
<p>In my use-case, I'd like multiple customers to be able to use my app. I want to avoid storing all their data in
	the same database with just a customer_id to tell them apart. This has advantages such as when the customer invokes
	the GDPR and asks for all their data. I just dump one database, I dump one directory of object storage with their
	media and I export one line of customer records. Bam! GDPR export complete. </p>
<p>Some other benefits are simpler scoping for backups and restores. And a simpler design for access control. The main
	detriment is that it does add significant complexity. Carefully consider your use-case.</p>
<p>If you simply need multiple databases but you know which ones they will be at compile time you do not need this. If
	you need to connect to entirely separate databases identified or created at runtime, this could be your jam.</p>
<h2>Starting a repo</h2>
<p>There really isn't much to starting an Ecto repo for use as a dynamic repo. You can either name it something you
	like if you want explicit naming. Or for my needs just give the name <code>nil</code> and it will be anonymous, only
	identified by its pid.</p>
<p>The code to do this, assuming you have <code>MyApp.Repo</code> in your application is just:</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#75715e"># Get the normal config from your config files, but set a name key to nil</span>
our_repo_config <span style="color:#f92672">=</span> 
	<span style="color:#a6e22e">Application</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">:my_app</span>, <span style="color:#a6e22e">MyApp.Repo</span>)
	<span style="color:#a6e22e">Map</span><span style="color:#f92672">.</span>put(<span style="color:#e6db74">:name</span>, <span style="color:#66d9ef">nil</span>)

{<span style="color:#e6db74">:ok</span>, repo_pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>start_link(our_repo_config)</code></pre></div>
  </div>


<h2>Activating a repo for use</h2>
<p>So now you want to be able to actually use this repo for queries. Generally Ecto will expect a default repo to be
	started as <code>MyApp.Repo</code>, that is, the module name for the repo. So now we actually need to visit the
	specific API for dynamic repos which is <code>Ecto.Repo.get_dynamic_repo\0</code> and
	<code>Ecto.Repo.put_dynamic_repo\1</code>.</p>
<p>So we can do this:</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">{<span style="color:#e6db74">:ok</span>, repo_pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>start_link(our_repo_config)

<span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>put_dynamic_repo(repo_pid)</code></pre></div>
  </div>


<p>The docs state, regarding <code>Ecto.Repo.put_dynamic_repo\2</code> that "from this moment on, all future
	queries done by the current process will run on[your dynamic repo]". I haven't dug into the details of
	scope here and if sub-processes will absolutely lose track of their repo. It is stored in the process dictionary (<a href="https://brainlid.org/elixir/2017/09/24/elixir-processes-and-state-abuse.html">more about that here</a>).
	So I imagine subprocesses do not share that. Something to be careful with and aware of.</p>
<p>There is also the sibling of this function which of course gets the current dynamic repo set. Which defaults to the
	repo default, so <code>MyApp.Repo</code> in our case.</p>
<h2>Creating a repo</h2>
<p>We've covered all of the functions provided by the dynamic repo API already, it is small and sweet. But it
	doesn't solve my use-case on its own.</p>
<p>When starting my app I want to ensure that existing customers have databases in our data store (Postgres in my case).
	Starting these fancy repos with their connection pools won't help a bit if I don't have a database created
	on the database server. In fact, there will be errors.</p>
<p>So we need to be able to do this dynamically too, because we want to <strong> do it at runtime</strong>. Someone
	registers as a customer. What do we do? Do we bring down our app, write some configuration dynamically, run a quick
	compilation and start it back up? Seems ... <em>inconvenient</em>.</p>
<p>So why not just use Mix? Mix isn't necessarily available on your runtime environment. If you have a two-stage
	Docker build for example you should end up with a production release without mix, because you don't need it. And
	you don't need it for this either. I heard shelling out is selling out. Definitely <strong>too catchy to be good
		advice</strong> but in this case it seems apt. I don't want mix to be required for running the application
	and I don't want shell commands for something that should be Ecto's job.</p>
<p>Check <code>Ecto.Adapter.Storage</code> and specifically the callbacks. <code>storage_down\1</code> and
	<code>storage_up\1</code>. I thought this was using nasty internals, but I was reassured when I brought it up that
	this is the intended approach. Double underscore make me think "hidden" and "internal use only"
	from life in Python. It looks like this:</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#a6e22e">MyApp.Repo</span><span style="color:#f92672">.</span>__adapter__<span style="color:#f92672">.</span>storage_up(our_repo_config)</code></pre></div>
  </div>


<p>And if the config is legit this should create your database. Nothing more to it. You can use
	<code>storage_down</code> to clean up and remove your database, use it with great care because hell if that
	isn't a dangerous little function. It will drop your database. If you want to close the connection pool there
	are other options. <strong>This one will remove your database</strong>.</p>
<h2>Running migrations</h2>
<p>Creating a database is probably only about 10% of the story of managing your DB from code. We all have our
	migrations. They need to be run or this is entirely pointless. Thankfully, we can. And we won't be using mix
	here either.</p>
<p>The <code>Ecto.Migrator</code>module takes care of us. There are some nuances. I'm not 100% sure if I needed to
	run <code>put_dynamic_repo</code> before <code>Ecto.Migrator.run\3</code> but it didn't hurt. I had some
	challenges with that function missing the <code>dynamic_repo</code> option. Turns out it supports it fine, just a
	documentation issue. My PR for that has been merged, so it should be fixed in a future release.</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#a6e22e">Ecto.Migrator</span><span style="color:#f92672">.</span>run(<span style="color:#a6e22e">MyApp.Repo</span>, <span style="color:#e6db74">:up</span>, <span style="color:#e6db74">dynamic_repo</span>: repo_pid)</code></pre></div>
  </div>


<p>That should do it.</p>
<p><strong>Note:</strong> If you run this for multiple repos <em>you will experience warnings</em> because it keeps
	loading the migration modules over and over again. I investigated potential PRs to fix this. That rabbit hole ended
	at "no, we shouldn't patch the elixir code server to improve this corner case" which seems fair. Jose
	gave me a very reasonable option which is to just bring the migrations out of priv and into my application like the
	modules they are. More on that in a later write-up.</p>
<p>So to recap, we can:</p>
<ul>
<li>Create an anonymous instance of a repo with a different config (such as database name)</li>
<li>We can create the database at runtime</li>
<li>We can run migrations at runtime</li>
</ul>
<p>But can we test it? Sure we can. It was a bit of a pain and I'm sure there is a lot of space to optimize. But I
	got myself some green dots and now know that it largely works. I'll attempt to cover that in a future post.
	Because there are a lot of parts to all of this.</p>
<p>So I've a few things I want to cover still but I wanted to get this out there because I honestly had to do a lot
	of digging, trial &amp; error to get all the parts of this working.</p>
<p>And in the end it seems like I will be using <a href="https://hexdocs.pm/ecto/multi-tenancy-with-query-prefixes.html#content" title="Multi-tenancy with query prefixes">prefixes</a> because that puts me much closer to the
	batteries-included happy-path of Phoenix and Ecto and keeps me from learning too much about managing pools of
	connection pools.</p>
<p>More to come on this topic.</p>
<p>Update: <a href="ecto-multi-tenancy-dynamic-repos-part-2.html">Part 2</a> is available.</p>
<p>Update: <a href="ecto-multi-tenancy-prefixes-part-3.html">Part 3 - Prefixes</a> is available.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>What I&#39;m up to - Mostly Elixir things</title>
      <link>https://underjord.io/what-im-up-to.html</link>
      <pubDate>Thu, 03 Oct 2019 11:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/what-im-up-to.html</guid>
      <description>While I&#39;m writing something a bit more involved and substantial I figured I could give an update on what I&#39;ve been doing. Mostly around Elixir. But I&#39;ll cover a few different things.
Ecto - A deep dive Working on a project I&#39;ll cover more at a later date has led me to work more with Ecto. I rather like it. What I didn&#39;t like is that my use-case seems fairly unexplored and a little bit light on documentation.</description>
      <content:encoded><![CDATA[ 
<p>While I'm writing something a bit more involved and substantial I figured I could give an update on what I've been
    doing. Mostly around Elixir. But I'll cover a few different things.</p>
<h2>Ecto - A deep dive</h2>
<p>Working on a project I'll cover more at a later date has led me to work more with Ecto. I rather like it. What I
    didn't like is that my use-case seems fairly unexplored and a little bit light on documentation. The project
    requires multi-tenancy for the database, that is, running multiple instances of the same Ecto repo. The support for
    this exists and is actual very reasonable overall. But it wasn't exactly a packaged deal and the number of people
    who have written about it is low. Expect me to publish a write-up on this with a bunch of examples. I'm just dealing
    with blog post scope creep.</p>
<p>Fun parts of this journey involved taking a few looks into how Ecto actually does things. I cannot recommend this
    enough, <strong>read the code</strong>. Look how it works under the hood. I have never worked in an ecosystem where
    I felt the code was easier to reason about than Elixir. I do not have to ingest the entire class and object
    hierarchy out of someones mind through the straw of their library code. Most things are easy to reason about. There
    are strong conventions around most of the slightly complex things like GenServers but often you're just dealing with
    pure functions and some IO.</p>
<p>The dive resulted in two issues and a PR. If I'm happy with my post I might contribute a streamlined version into the
    Ecto docs to cover some of what I'm doing. Creating repos at runtime, ensuring the database has been created,
    running migrations at runtime and that sort of stuff. It's all there in Ecto, but there aren't so many guides around
    it.</p>
<h2>ExUnit - Testing and mocking</h2>
<p>I've done some work now which benefited greatly from mocking some of the calls. As they required user interaction on
    a mobile device to trigger the response (yeah, auth through a second factor, nothing fancy). Getting it exactly
    right was a bit of a hassle from what I recall. But it was a little while back. I might do a write-up on it if there
    is interest. But there's plenty of stuff about Mox and all that already. If I write something it'll probably be
    mostly about the gotchas or how I used it.</p>
<p>So what was this 2-factor-stuff? Well, read on if you have niche interests!</p>
<h2>BankID - Swedish national identity sign-on</h2>
<p>I made a library for using the swedish national identity sign-in thing for Elixir. I fully intend to release it but
    it needs some polish and I still need to decide if I like having my own personal information as test data ;)</p>
<p>It is not a complex API to implement so most people could do it on their own. But at least this gives you a GenServer
    that handles some of the keep-alive mechanisms and stuff for you and wraps it all up quite nicely. I even ended up
    getting familiar with Erlangs <code>:httpc</code> module to minimize dependencies.</p>
<p>If this would be useful to you in a project and I haven't released it yet. Get in touch. You can have it :)</p>
<h2>Nerves &amp; Scenic - More people</h2>
<p><a href="https://www.youtube.com/watch?v=fRP_dVton7o">Justin's talk at ElixirConf US 2019</a> seems to have sparked
    quite a bit of interest. I think the stream of people into #nerves and #scenic on the Slack has gone up. Still a
    very chill place and a lot of friendly people. It mostly caught my eye because there have been <strong>a
        lot</strong> of people getting started with Nerves and/or Scenic in there.</p>
<p>Which leads me to what I spent yesterday evening investigating and the morning swearing about.</p>
<h2>A Scenic diversion</h2>
<p>If you run Scenic on your development environment chances are you use your mouse to click things and your keyboard to
    type.</p>
<p>If you run the same project using Nerves and the Scenic Nerves display driver (the one meant for the official
    touchscreen) and then you connect a mouse, you will not have a good time. Or you won't click things, that's for
    sure. Because the Scenic driver doesn't know anything about mice. On your host it uses glfw which handles all this.
    On device it uses nanovg which doesn't. But you have a touch driver, which drives the touchscreen very well.</p>
<p>So I spent a bunch of time getting to know the Nerves library for InputEvent (very neat, had no idea, so much
    potential in there) and trying to hack the touch driver to be a mouse driver instead. I dare say I got fairly close.
    But then I had to actually work for a living. And that's where that foray ended. For now. IT got the ol' juices
    going though. If you've any interest in running with this, let me know and I'll point you in all the best
    directions. Probably ;)</p>
<h2>The Vue from here is pretty good</h2>
<p>Ye olde secret project demanded a frontend of significant complexity and high UX fidelity. So I figured I'd pick a
    framework. I've been wanting to do a Redux or Vuex thing for a bit. So now I have a Vue project on my hands. Because
    honestly React just seems like it offers less of a coherent whole. But I haven't gone deep into figuring out the
    differences. Generally I've had a better experience trying to get started with Vue &amp; friends than trying to get
    started with React &amp; friends.</p>
<p>It's been fine. I'm not a big fan of the dependencies, the infinite build tool configs or whatever. But the Vue
    project generator set most things up in a way I can largely follow. We had to fight some more when I wanted to add
    some specific testing things, maybe I elected to not include those. I don't recall at this point. What I will say is
    that I'm getting used to ES-whatever-the-version-is-now and a bunch of the modern notation is very useful, quite
    concise.</p>
<p>Feels good to be getting more practice in a current-generation JS code-base because I felt a bit old there for a bit.
    I don't hate it. I like Vue conceptually, I've enjoyed how it gets out of the way mostly when I want to figure out a
    component. I enjoy the single file component thing. I have my concerns about the way the JS ecosystem works. But it
    is currently on my good side and I will try to keep up to date enough that it stays there.</p>
<p>If you love JS and find it the best thing you've ever worked with. Please do take this for what it is, just another
    techie that prefers other things. Do your thing.</p>
<hr/>
<p>So that's a brief update on what I've been up to. The current secret project should lead to quite a few more posts
    over time. But I also have to build the damn thing. Stay strong out there and use the source.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Why a newsletter?</title>
      <link>https://underjord.io/why-a-newsletter.html</link>
      <pubDate>Thu, 12 Sep 2019 08:56:00 +0000</pubDate>
      
      <guid>https://underjord.io/why-a-newsletter.html</guid>
      <description>What is it good for? Doing it wrong Simple tech Readership contact Resilience and sustainability  So I&#39;m launching a newsletter. The sign-up is at the bottom of the page, it won&#39;t pop up here, so read on in peace.
What is it good for? Every tech blogger has a newsletter these days. How come? Because it works. Because it is bed-rock old-web stuff. It is relationship building, it is building a network, an audience and connections.</description>
      <content:encoded><![CDATA[ 
<ul>
<li>What is it good for?</li>
<li>Doing it wrong</li>
<li>Simple tech</li>
<li>Readership contact</li>
<li>Resilience and sustainability</li>
</ul>
<p>So I'm launching a newsletter. The sign-up is at the bottom of the page, it won't pop up here, so read on in
	peace.</p>
<h2>What is it good for?</h2>
<p>Every tech blogger has a newsletter these days. How come? Because it works. Because it is <strong>bed-rock
		old-web</strong> stuff. It
	is relationship building, it is building a network, an audience and connections. Its good business, potentially fun
	and can be used in many ways.</p>
<p>A lot of people have had good outcomes based on creating and running a newsletter. For some, that means turning a
	sliver of their readership into paid newsletter subscribers. Others have courses or other products to sell. Having
	the ability to reach out <em>directly to your readership</em> on a regular basis is very powerful. And rather than
	cold
	advertising on the internet it seems much more effective. Since it is heavily opt-in and easy to unsubscribe or
	filter out it keeps the author more honest. Your subscribers can just leave.</p>
<p>I read a few newsletters. <a href="https://changelog.com/weekly">Changelog Weekly</a>, <a href="http://plataformatec.com.br/elixir-radar/">Plataformatecs Elixir Radar</a>, <a href="https://elixirweekly.net/">Elixir Weekly</a> are some tech ones. Those
	are extremely pragmatic and generally very light on the personal touch. I also follow some random authors (<a href="http://orbitaloperations.com/">Warren
		Ellis - Orbital Operations</a> and <a href="https://www.wrightallison.com/">Allison Wright - I don't hate
		it</a>) and have
	some Patreon pledges that include a newsletter. Some of it helps me keep up to date and can be skimmed. Some of it
	is like a tasty morsel of something I already like that I reserve a moment to read without interruption. Newsletters
	come in many flavors.</p>
<h2>Doing it wrong</h2>
<p>I am so frustrated about all these newsletters, or rather their marketing approach. Impossibly
	heavy-handed marketing that gets all up in everyone's face with popovers, slide-unders, side-winders or whatever
	the pop-up of the day is called. "Oh, you've spent 10
	seconds on this article and don't know who I am, buy my course and sign up for my newsletter! "
	<strong>Damnit, what
		is the No button called this time.</strong> "Sorry, I don't like knowledge" perhaps? This throws
	a shadow over all newsletters, tainting the whole practice with the feel of aggressive marketing.</p>
<p>If you are new here, take this article for what it is, a brief primer on why people keep making newsletters as well
	as an announcement about me starting one. But if you don't particularly enjoy the read or know that my writing
	is relevant to you, don't sign up. I won't hassle you to. Also, there is an RSS feed for the blog if that's
	all you want. I'm trying to do things correctly here. Keeping things sane.</p>
<h2>Simple tech, no gods, no masters</h2>
<p>With newsletters we are still very much in the realm of what the Internet <em>has always done</em>. <strong>Things
		are simple.</strong> We need a web form for signup and email for delivery. Even if you use a platform for
	convenience it is still
	basically a list of email addresses that you send to. The sign-up form tends to use a bit of JS these days and it
	might not end up going to a cgi-bin. But the basic principles are the same. No one can disrupt email too much
	without pissing off entirely too many business people apparently. Even Google is sitting fairly still in the Gmail
	boat. The Newsletter platform providers all try to stay somewhat diligent to avoid getting spam-flagged.</p>
<p>There is no app silo, no large controlling company, the platforms (your Campaign Monitors, ConvertKits and Drips) are
	optional and largely equivalent. It is not a walled garden as long as I can take my list and go. In my case I had to
	beat the Campaign Monitor signup a bit to make the form explicitly opt whoever signs up out of being activity
	tracked. But it should be well-behaved enough now.</p>
<p>Newsletters of this kind are explicitly opt-in. "Yes, I will allow you to push information to me, here's my
	email address." And it has a strong common practice for opting out by unsubscribing. The email you deliver can
	be managed however the
	recipient feels like, maybe your newsletter is filed into a folder for later reading, maybe they are eagerly
	awaiting it. Reader's choice.</p>
<p>And if my writing doesn't meet your standard for what is relevant or worthwhile to you, you just leave. There
	are rules preventing me from following you and I don't want to.</p>
<h2>Readership contact</h2>
<p>Keeping in touch with readers on the web is suprisingly hard.</p>
<p>Comments are often a shitshow. I have chosen not to have them as I don't want to check my site constantly. But I
	do want a way to reach my viewership beyond my site. If I launch a project that someone is interested in following
	they might not have an RSS reader (of course I offer RSS, I'm not an animal) or they might want me to actively
	push them the update. Maybe providing some context.</p>
<p>And if I'm brave, which I try to be, I'll just leave my email there for you to respond to. Seems fair up to a
	certain scale.</p>
<p>Writing to an audience that know what you are about can also be quite different to writing for the entire internet.
	You can get into the more interesting weeds, you can solicit more specific types of feedback.</p>
<p>And if you want to do something new, start a YouTube channel, Twitch stream, Patreon or whatever. The newsletter is
	your best bet for getting in touch with people that might be interested. For many creators and business owners you
	can't just rely on people ending up on your site naturally to see the new thing you did. So if interested people can
	sign up to follow you, that can be a good thing.</p>
<h2>Resilience &amp; sustainability</h2>
<p>A newsletter, correctly managed, should provide resilience for a free-lancer that publishes themselves. YouTuber
	CGP Gray set up a newsletter/mailing list thing for his fans as a measure against the increasing risk of the YouTube
	platform. The platform is in no danger of going away. But there is a rising concern that the algorithm does not
	necessarily surface videos to a creator's actual subscribers. As one in the sea of mid-size YouTubers he has no
	voice and no say in the platform and he realizes this.</p>
<p>An email list allows him to go elsewhere if he wants or needs, he can kickstart things in a new environment using
	that list.
	And the people who signed up are of course people who do not want to lose track of him. He maintains a central core
	of fans for any new venture he might embark on.</p>
<p>So I'm launching a newsletter. And if I live up to my end of the bargain and deliver some interesting email every
	now and then you just might stay subscribed to it and be part of sustaining my life as a freelancer. I'd appreciate
	that.</p>
<p>My plan for the contents right now is to gather up what I've been reading recently (mostly online) along with a
	podcast recommendation and give my thoughts on it all. This means the newsletter has different contents than the
	site. Its all new to me, so I'm sure some polish will happen along the way.</p>
<p>The sign-up if you want it is down below.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Case Study: Inky - An elixir library</title>
      <link>https://underjord.io/case-study-inky-an-elixir-library.html</link>
      <pubDate>Fri, 09 Aug 2019 14:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/case-study-inky-an-elixir-library.html</guid>
      <description>This is a post covering the creation and refinement of an open source project within the Elixir ecosystem. More words than code. Be warned.  I bought* a display from Pimoroni called the Inky PHAT, the red version. It is an eInk display. That means it can show an image even without power being supplied to the screen. In this case it can show black, white and red. It refreshes very slowly (12 seconds or more) but is a cool and potentially useful piece of tech.</description>
      <content:encoded><![CDATA[ 
<p>
  This is a post covering the creation and refinement of an open source project
  within the Elixir ecosystem. More words than code. Be warned.
</p>
<p>
  I bought* a display from Pimoroni called the Inky PHAT, the red version. It is
  an eInk display. That means it can show an image even without power being
  supplied to the screen. In this case it can show black, white and red. It
  refreshes very slowly (12 seconds or more) but is a cool and potentially
  useful piece of tech.
</p>
<p>
<strong>*</strong>
<em>I got it as a replacement after buying another eInk device that was dead on
    arrival and when I got in touch it was no longer in stock. Fair enough.</em>
</p>
<p>
  I tried it on Raspbian to make sure it worked. But to be honest most of my
  interests for the Raspberry Pi tends towards using
  <a href="https://nerves-project.org" title="Nerves Project">Nerves</a> these
  days. So I wanted to get it working there. The Pimoroni libraries for
  <a href="https://github.com/pimoroni/inky" title="Inky libraries by Pimoroni">Inky</a>
  are in Python. Getting Python going on Nerves is definitely feasible but I
  felt like trying to port the library to Elixir instead to avoid pulling in all
  of Python. Inky isn't a huge code base. A good learning experience I
  figured.
</p>
<p>I'll try to cover the steps the library has gone through.</p>
<ul>
<li>Get it working - A straight port from Python</li>
<li>Cleaning it up - Initial refactor, a bit more Elixir</li>
<li>Make it pretty? - Refactoring towards testability</li>
<li>A use-case, the Scenic Driver</li>
<li>Make it beautiful - Isolating state in a GenServer</li>
<li>A curiosity - The host development library</li>
<li>Publishing</li>
<li>What's next?</li>
</ul>
<h2>Get it working - A straight port from Python</h2>
<p>
  GitHub reference:
  <a href="https://github.com/pappersverk/inky/tree/9cb92f11e40e14cde177267a58515e18d5146651/lib" title="Commit">Specific commit</a>
</p>
<p>
  Getting it actually working took a bit of work. I haven't had a chance to
  do much Elixir previously. I had no real specification for the hardware aside
  from the Python library. I had to understand what the Python library did in
  the finicky details to actually get pixels, correctly aligned, to show up on
  the screen.
</p>
<p>
  The ElixirALE, and later, Circuits libraries were quite nice to work with both
  for GPIO and SPI. So no problems there.
</p>
<p>
  The Python library is object-oriented in style and simply provides a class
  that you instantiate with some configuration for your specific device (also
  supports the WHAT form-factor and the yellow variants instead of red). So I
  started by mkaing a straight port working quite close to what the original
  class did. <code>Inky.setup</code> takes some configuration and generates a
  state struct. The state struct is then passed around for all further calls.
</p>
<p>
  The flow of the application entirely quite in line with the Pimoroni Inky
  version I worked from at the time. Same level of abstractions, same binary
  magic numbers/commands in the long Inky.update. I stripped out some things to
  try to isolate some problems I had (anything touching the what or yellow
  displays was removed).
</p>
<p>
  Testing was done by running on hardware and printing different colored stripes
  on the display and hoping that they would line up correctly. After a while
  they did. Finally.
</p>
<p>
  This is as far as you would need to go for just getting your thing working and
  it is perfectly valid to just do what you need and get your stuff going. If
  you do, give a shout and a GitHub link in the relevant channel on the
  elixir-lang slack and you've increased the chance of someone else getting
  value out of your work.
</p>
<h2>Cleaning it up - Initial refactor, a bit more Elixir</h2>
<p>
  GitHub reference:
  <a href="https://github.com/pappersverk/inky/tree/0.1/lib" title="Tag 0.1 reference">https://github.com/pappersverk/inky/tree/0.1/lib</a>
</p>
<p>
  A friend I made at ElixirConf EU (2019, Prague) offered to review the code
  once I had it working. And I got some good feedback there. The focus became
  readability and breaking out the incredibly specific sequences of magic
  numbers to discrete commands.
</p>
<p>The setup function went from this:</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#66d9ef">def</span> setup(state \\ <span style="color:#66d9ef">nil</span>, type, luts_color)
      <span style="color:#f92672">when</span> type <span style="color:#f92672">in</span> [<span style="color:#e6db74">:phat</span>, <span style="color:#e6db74">:what</span>] <span style="color:#f92672">and</span> luts_color <span style="color:#f92672">in</span> [<span style="color:#e6db74">:black</span>, <span style="color:#e6db74">:red</span>, <span style="color:#e6db74">:yellow</span>] <span style="color:#66d9ef">do</span>
    state <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">case</span> state <span style="color:#66d9ef">do</span>
        %<span style="color:#a6e22e">State</span>{} <span style="color:#f92672">-&gt;</span>
          state

        <span style="color:#66d9ef">nil</span> <span style="color:#f92672">-&gt;</span>
          {<span style="color:#e6db74">:ok</span>, dc_pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">GPIO</span><span style="color:#f92672">.</span>open(<span style="color:#a6e22e">@dc_pin</span>, <span style="color:#e6db74">:output</span>)
          {<span style="color:#e6db74">:ok</span>, reset_pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">GPIO</span><span style="color:#f92672">.</span>open(<span style="color:#a6e22e">@reset_pin</span>, <span style="color:#e6db74">:output</span>)
          {<span style="color:#e6db74">:ok</span>, busy_pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">GPIO</span><span style="color:#f92672">.</span>open(<span style="color:#a6e22e">@busy_pin</span>, <span style="color:#e6db74">:input</span>)
          <span style="color:#75715e"># GPIO.write(gpio_pid, 1)</span>
          {<span style="color:#e6db74">:ok</span>, spi_pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">SPI</span><span style="color:#f92672">.</span>open(<span style="color:#e6db74">&#34;spidev0.&#34;</span> <span style="color:#f92672">&lt;&gt;</span> to_string(<span style="color:#a6e22e">@cs0_pin</span>), <span style="color:#e6db74">speed_hz</span>: <span style="color:#ae81ff">488_000</span>)
          <span style="color:#75715e"># Use binary pattern matching to pull out the ADC counts (low 10 bits)</span>
          <span style="color:#75715e"># &lt;&lt;_::size(6), counts::size(10)&gt;&gt; = SPI.transfer(spi_pid, &lt;&lt;0x78, 0x00&gt;&gt;)</span>
          %<span style="color:#a6e22e">State</span>{
            <span style="color:#e6db74">dc_pid</span>: dc_pid,
            <span style="color:#e6db74">reset_pid</span>: reset_pid,
            <span style="color:#e6db74">busy_pid</span>: busy_pid,
            <span style="color:#e6db74">spi_pid</span>: spi_pid,
            <span style="color:#e6db74">color</span>: luts_color
          }
      <span style="color:#66d9ef">end</span>

    <span style="color:#a6e22e">GPIO</span><span style="color:#f92672">.</span>write(state<span style="color:#f92672">.</span>reset_pid, <span style="color:#ae81ff">0</span>)
    <span style="color:#e6db74">:timer</span><span style="color:#f92672">.</span>sleep(<span style="color:#ae81ff">100</span>)
    <span style="color:#a6e22e">GPIO</span><span style="color:#f92672">.</span>write(state<span style="color:#f92672">.</span>reset_pid, <span style="color:#ae81ff">1</span>)
    <span style="color:#e6db74">:timer</span><span style="color:#f92672">.</span>sleep(<span style="color:#ae81ff">100</span>)

    state <span style="color:#f92672">=</span>
      <span style="color:#66d9ef">case</span> type <span style="color:#66d9ef">do</span>
        <span style="color:#e6db74">:phat</span> <span style="color:#f92672">-&gt;</span> <span style="color:#a6e22e">InkyPhat</span><span style="color:#f92672">.</span>update_state(state)
        <span style="color:#e6db74">:what</span> <span style="color:#f92672">-&gt;</span> <span style="color:#a6e22e">InkyWhat</span><span style="color:#f92672">.</span>update_state(state)
      <span style="color:#66d9ef">end</span>

    soft_reset(state)
    busy_wait(state)
    state
  <span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>And was changed into this:</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#66d9ef">def</span> setup(state \\ <span style="color:#66d9ef">nil</span>, type, luts_color)
      <span style="color:#f92672">when</span> type <span style="color:#f92672">in</span> [<span style="color:#e6db74">:phat</span>, <span style="color:#e6db74">:what</span>] <span style="color:#f92672">and</span> luts_color <span style="color:#f92672">in</span> [<span style="color:#e6db74">:black</span>, <span style="color:#e6db74">:red</span>, <span style="color:#e6db74">:yellow</span>] <span style="color:#66d9ef">do</span>
    state
    <span style="color:#f92672">|&gt;</span> init_state(luts_color)
    <span style="color:#f92672">|&gt;</span> init_reset
    <span style="color:#f92672">|&gt;</span> init_type(type)
    <span style="color:#f92672">|&gt;</span> setup_derived_values
    <span style="color:#f92672">|&gt;</span> soft_reset
    <span style="color:#f92672">|&gt;</span> busy_wait
  <span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>Which felt good and was easier to follow.</p>
<p>Inky.update was a monster that looked like this:</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#66d9ef">defp</span> update(state, buffer_a, buffer_b) <span style="color:#66d9ef">do</span>
    setup(state, state<span style="color:#f92672">.</span>type, state<span style="color:#f92672">.</span>color)

    <span style="color:#75715e">## Straight ported from python library, I know very little what I&#39;m doing here</span>

    <span style="color:#75715e"># little endian, unsigned short</span>
    packed_height <span style="color:#f92672">=</span> [
      <span style="color:#e6db74">:binary</span><span style="color:#f92672">.</span>encode_unsigned(<span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>fetch!(state<span style="color:#f92672">.</span>resolution_data, <span style="color:#ae81ff">1</span>), <span style="color:#e6db74">:little</span>),
      &lt;&lt;<span style="color:#ae81ff">0x00</span>&gt;&gt;
    ]

    <span style="color:#75715e"># Skipped map ord thing for packed_height..</span>
    <span style="color:#75715e"># IO.puts(&#34;Starting to send shit..&#34;)</span>

    <span style="color:#75715e"># Set analog block control</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Set analog block control&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x74</span>, <span style="color:#ae81ff">0x54</span>)

    <span style="color:#75715e"># Set digital block control</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Set digital block control&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x7E</span>, <span style="color:#ae81ff">0x3B</span>)

    <span style="color:#75715e"># Gate setting</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Gate setting&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x01</span>, <span style="color:#e6db74">:binary</span><span style="color:#f92672">.</span>list_to_bin(packed_height <span style="color:#f92672">++</span> [<span style="color:#ae81ff">0x00</span>]))

    <span style="color:#75715e"># Gate driving voltage</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Gate driving voltage&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x03</span>, [<span style="color:#ae81ff">0b10000</span>, <span style="color:#ae81ff">0b0001</span>])

    <span style="color:#75715e"># Dummy line period</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Dummy line period&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x3A</span>, <span style="color:#ae81ff">0x07</span>)

    <span style="color:#75715e"># Gate line width</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Gate line width&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x3B</span>, <span style="color:#ae81ff">0x04</span>)

    <span style="color:#75715e"># Data entry mode setting 0x03 = X/Y increment</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Data entry mode setting 0x03 = X/Y increment&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x11</span>, <span style="color:#ae81ff">0x03</span>)

    <span style="color:#75715e"># Power on</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Power on&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x04</span>)

    <span style="color:#75715e"># VCOM Register, 0x3c = -1.5v?</span>
    <span style="color:#75715e"># IO.inspect(&#34;# VCOM Register, 0x3c = -1.5v?&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x2C</span>, <span style="color:#ae81ff">0x3C</span>)
    send_command(state, <span style="color:#ae81ff">0x3C</span>, <span style="color:#ae81ff">0x00</span>)

    <span style="color:#75715e"># Always black border</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Always black border&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x3C</span>, <span style="color:#ae81ff">0x00</span>)

    <span style="color:#75715e"># Set voltage of VSH and VSL on Yellow device</span>
    <span style="color:#66d9ef">if</span> state<span style="color:#f92672">.</span>color <span style="color:#f92672">==</span> <span style="color:#e6db74">:yellow</span> <span style="color:#66d9ef">do</span>
      send_command(state, <span style="color:#ae81ff">0x04</span>, <span style="color:#ae81ff">0x07</span>)
    <span style="color:#66d9ef">end</span>

    <span style="color:#75715e"># Set LUTs</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Set LUTs&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x32</span>, get_luts(state<span style="color:#f92672">.</span>color))

    <span style="color:#75715e"># Set RAM X Start/End</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Set RAM X Start/End&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x44</span>, <span style="color:#e6db74">:binary</span><span style="color:#f92672">.</span>list_to_bin([<span style="color:#ae81ff">0x00</span>, trunc(state<span style="color:#f92672">.</span>columns <span style="color:#f92672">/</span> <span style="color:#ae81ff">8</span>) <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>]))

    <span style="color:#75715e"># Set RAM Y Start/End</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Set RAM Y Start/End&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x45</span>, <span style="color:#e6db74">:binary</span><span style="color:#f92672">.</span>list_to_bin([<span style="color:#ae81ff">0x00</span>, <span style="color:#ae81ff">0x00</span>] <span style="color:#f92672">++</span> packed_height))

    <span style="color:#75715e"># 0x24 == RAM B/W, 0x26 == RAM Red/Yellow/etc</span>
    <span style="color:#66d9ef">for</span> data <span style="color:#f92672">&lt;-</span> [{<span style="color:#ae81ff">0x24</span>, buffer_a}, {<span style="color:#ae81ff">0x26</span>, buffer_b}] <span style="color:#66d9ef">do</span>
      {cmd, buffer} <span style="color:#f92672">=</span> data

      <span style="color:#75715e"># Set RAM X Pointer start</span>
      <span style="color:#75715e"># IO.inspect(&#34;# Set RAM X Pointer start&#34;)</span>
      send_command(state, <span style="color:#ae81ff">0x4E</span>, <span style="color:#ae81ff">0x00</span>)

      <span style="color:#75715e"># Set RAM Y Pointer start</span>
      <span style="color:#75715e"># IO.inspect(&#34;# Set RAM Y Pointer start&#34;)</span>
      send_command(state, <span style="color:#ae81ff">0x4F</span>, &lt;&lt;<span style="color:#ae81ff">0x00</span>, <span style="color:#ae81ff">0x00</span>&gt;&gt;)
      <span style="color:#75715e"># IO.inspect(&#34;# Buffer thing&#34;)</span>
      send_command(state, cmd, buffer)
    <span style="color:#66d9ef">end</span>

    <span style="color:#75715e"># Display Update Sequence</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Display Update Sequence&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x22</span>, <span style="color:#ae81ff">0xC7</span>)

    <span style="color:#75715e"># Trigger Display Update</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Trigger Display Update&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x20</span>)

    <span style="color:#75715e"># Wait Before Deep Sleep</span>
    <span style="color:#e6db74">:timer</span><span style="color:#f92672">.</span>sleep(<span style="color:#ae81ff">50</span>)
    busy_wait(state)

    <span style="color:#75715e"># Enter Deep Sleep</span>
    <span style="color:#75715e"># IO.inspect(&#34;# Enter deep sleep&#34;)</span>
    send_command(state, <span style="color:#ae81ff">0x10</span>, <span style="color:#ae81ff">0x01</span>)
  <span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>
  After the refactor, breaking out the different commands into separate
  functions we had:
</p>


  <div class="code  elixir " >
    <div class="meta">
      <span class="language">elixir</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">  <span style="color:#66d9ef">defp</span> update(state, buffer_a, buffer_b) <span style="color:#66d9ef">do</span>
    state
    <span style="color:#f92672">|&gt;</span> setup(state<span style="color:#f92672">.</span>type, state<span style="color:#f92672">.</span>color)
    <span style="color:#f92672">|&gt;</span> set_analog_block_control
    <span style="color:#f92672">|&gt;</span> set_digital_block_control
    <span style="color:#f92672">|&gt;</span> set_gate
    <span style="color:#f92672">|&gt;</span> set_gate_driving_voltage
    <span style="color:#f92672">|&gt;</span> dummy_line_period
    <span style="color:#f92672">|&gt;</span> set_gate_line_width
    <span style="color:#f92672">|&gt;</span> set_data_entry_mode
    <span style="color:#f92672">|&gt;</span> power_on
    <span style="color:#f92672">|&gt;</span> vcom_register
    <span style="color:#f92672">|&gt;</span> set_border_color
    <span style="color:#f92672">|&gt;</span> configure_if_yellow
    <span style="color:#f92672">|&gt;</span> set_luts
    <span style="color:#f92672">|&gt;</span> set_dimensions
    <span style="color:#f92672">|&gt;</span> push_pixel_data_to_device(buffer_a, buffer_b)
    <span style="color:#f92672">|&gt;</span> display_update_sequence
    <span style="color:#f92672">|&gt;</span> trigger_display_update
    <span style="color:#f92672">|&gt;</span> wait_before_sleep
    <span style="color:#f92672">|&gt;</span> deep_sleep
  <span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>
  I find it gave a significantly clearer overview of the sequence of events.
  There was also a large amount of pure spring-cleaning. Removing printing that
  was no longer relevant and comments that ended up redundant.
</p>
<p>
  The larger structure of the library remained roughly the same. But it became
  quite a bit more readable using the Elixir pipe syntax and by breaking the
  whole thing up into functions that clarified different purposes.
</p>
<h2>Make it pretty? - Refactoring towards testability</h2>
<p>
  GitHub reference:
  <a href="https://github.com/pappersverk/inky/tree/0.2/lib/inky" title="Tag 0.2 reference">https://github.com/pappersverk/inky/tree/0.2/lib/inky</a>
  (and onward through 0.3)
</p>
<p>
  In the tags 0.2 and 0.3 we can see that Nyaray joined the effort. We'd
  spoken in the #nerves channel on the elixir-lang Slack and also met up during
  Code Beam Sto (2019, Stockholm), after which he gave me some help getting
  things in order. And then increasingly he brought his FP-knowledge to bear on
  this thing.
</p>
<p>
  What we started to do was break apart the commands bit from the SPI/GPIO
  calls. The goal here was to be able to test things without the device and get
  some protection against regressions. The library was fully functional. But
  relatively small changes risked screwing things up. Verifying on hardware
  every time takes a lot of time.
</p>
<p>
  This also gave birth to the idea in my head that we might be able to give the
  developer a host development option. But how could we do that in the best way
  with minimal dependencies. I'd heard there was a UI lib in erlang. Was it
  wx? Lets put a pin in that.
</p>
<p>
  Along the way there was less passing around of the entire state and we would
  instead just pass the relevant data, making functions easier to reason about.
  We also spent some time stripping out the vestiges of a default Elixir
  application to make it into a proper library. Something you can include in
  other things. My little library grew up.
</p>
<p>
  Anyway, with time we (well, Nyaray) even got the tests in there. A pluggable
  IO-module is used for the tests instead of the actual hardware, allowing them
  to run fast and without the hardware. And then we added CircleCI to the
  repository. Glorious.
</p>
<h2>A use-case, the Scenic Driver</h2>
<p>
  We've been quite curious about the Scenic UI framework for a while. Both
  of us have done some hobby project work with Scenic and want to do more. Since
  a while back I'd considered using Scenic to render to Inky, mostly to
  avoid rendering text.
</p>
<p>
  For Scenic to render to a display it needs a driver. To implement a driver
  usually means rendering the resolved Scenic graph to some kind of OpenGL
  subsystem. But there is already a driver for Raspberry Pi. It is intended for
  the official Pi touchscreen display. But it generates a frame buffer at
  <code>/dev/fb0</code>. Those are useful.
</p>
<p>
  Sidebar: I attended the Nerves training session at ElixirConf EU where Justin
  Schneck (<a href="https://mobile.twitter.com/mobileoverlord" title="Twitter: @mobileoverlord">@mobileoverlord</a>) ran
  us through a rather in-depth game project using Scenic, the Adafruit
  OLED Bonnet, Nerves and NervesHub. They made a simple driver for the OLED
  Bonnet's black-and-white display, using Scenic, mostly to avoid having to
  render text on their own but also for doing UI in general.
</p>
<p>
  Taking a look at their driver it was fairly straight-forward to remove some
  input-related code and use our Inky library to send data to our device instead
  of the OLED screen.
</p>
<p>
  The most challenging thing was managing pixel color, which the OLED driver
  entirely by-passed. But I wanted the accent color to work as intended. Some
  pixels I expected to be white were off-white and not all blacks were equal,
  there was some blending and some anti-aliasing. But with some thresholds it
  worked. The display could show text. It can probably render images (badly) as
  well, we just haven't tried it. From some discussion with Frank Hunleth of
  Nerves fame I have the feeling that the odd colors are probably related to
  some kind of color-adjustments being done near or on the GPU before the
  framebuffer is provided to us.
</p>
<p>
  I've even added some configuration where you can let the driver
  automatically create shading by dithering some of the in-between colors.
  Anyway, the driver works. We can now render UI, text and such to the Inky
  display. With Scenic that means you can even calculate what text size you
  would need to be able to fit text on the display and such, using the font
  metrics the framework provides. The possibilities are pretty much endless.
</p>
<p>You can find the driver here:</p>
<p>
  GitHub project:
  <a href="https://github.com/pappersverk/scenic_driver_inky">https://github.com/pappersverk/scenic_driver_inky</a>
</p>
<p>
  GitHub sample application:
  <a href="https://github.com/lawik/sample_scenic_inky">https://github.com/lawik/sample_scenic_inky</a>
</p>
<h2>Make it beautiful - Isolating state in a GenServer</h2>
<p>
  Since way back we've been planning to put the entire thing in a GenServer.
  There is no reason why your application should need to know about the display
  state of your Inky or care about the internal operations on that state. In the
  latest set of changes Nyaray brought the library into a GenServer after some
  lively discussion to settle on an API. It now exposes: start_link, set_pixels,
  show and stop
</p>
<p>
  This has included more work on internal separation where the Commands module
  has been replaced by a pluggable HAL (Hardware Abstraction Layer) which allows
  another layer of tests along with some other niceties.
</p>
<p>
  This is likely to be the API for Inky for the foreseeable future. And it will
  allow us to keep the library with good test coverage, making maintenance
  easier.
</p>
<h2>A curiosity - The host development library</h2>
<p>
  GitHub:
  <a href="https://github.com/pappersverk/inky_host_dev">https://github.com/pappersverk/inky_host_dev</a>
</p>
<p>
  I mentioned wx, lets pull that pin back out and take a look. This is an erlang
  standard library thing where we have access to wxWidgets by default for native
  cross-platform GUI-creation.
</p>
<p>
  I decided to use it to make a simulation of the inky for development on your
  "host", that is, your developer machine. This means you get a
  good-enough facsimile to work against while trying to convince your Inky to do
  your bidding without having to push firmware to device constantly since it is
  rather time-consuming even with all the conveniences of Nerves. It is a
  separate dependency that you can add for development only. It was a fun thing
  to make and hopefully it ends up useful to someone. I was quite pleased to be
  able to do this using the standard library. I imagine I'll revisit wx at
  some point. I'm a sucker for pain.
</p>
<p>
  Note: if you are using Scenic anyway, you already have rendering on the host
  with the glfw driver. Though it uses a driver that is significantly more
  capable than Inky, you'd need to use that to get everything else that
  Scenic offers. The Inky Scenic Driver will not work on your host machine since
  it is specific to the Raspberry Pi environment.
</p>
<h2>Publishing</h2>
<p>
  We matched up our libraries, ran our tests, made sure everything was good and
  then we went through the mild process of publishing packages to Hex. It was
  very straight-forward. I had no real issues, I had a brief confusion trying to
  figure out if we needed an organization or not. We didn't. Other than
  that, smooth sailing.
</p>
<p>
  We've created an org called (<a href="https://github.com/pappersverk">pappersverk</a>) where we gather this stuff
  and possibly some other projects. Keep an eye
  out.
</p>
<h2>What's next?</h2>
<h3>Performance optimizations</h3>
<p>
  Nyaray has been going wild on benchmarking, measuring and optimizing on some
  of our slower code paths lately. So I imagine that'll be in a release
  soon. The hardware remains quite slow. The best thing you can do speed-wise is
  to run it as a black-and-white configuration to avoid the time-consuming
  accent color. I think it cuts 5-10 seconds from the visible refresh time. I
  haven't tried it recently.
</p>
<h3>Community contributions</h3>
<p>
  One community-member got in touch because his wHAT wasn't working. Since
  neither me or Nyaray have that hardware this was an incredible help in ironing
  out the issues. I had simply removed too much code when building the first
  version and it never made its way back in. It should now work with yellow
  devices and wHAT form factors. Last I saw these fixes were being merged so
  probably ready for the next release.
</p>
<h3>Version updates and regressions</h3>
<p>
  Another community member got in touch about some hard errors with the Scenic
  driver. Turns out things broke with Elixir 1.9 and Nerves 1.5. So we could
  track down an upstream issue with framebuffer capture module which lead to
  Frank Hunleth tracking down some missing functionality in the RPi firmware
  version that Nerves was shipping.
</p>
<h2>Final thoughts</h2>
<p>
  I must say it has been <strong>incredibly cool</strong> to hear about
  <strong>people using our library</strong>, someone is experimenting with using
  it for a veterinarian system where they need signs with the names of the
  animals and such (you can find the post on the Elixir Forums). And people
  getting in touch and letting us know about problems and helping us verify the
  issue and drive towards a resolution. That has been very satisfying.
</p>
<p>
  My post with the guide about this library also hit the Hacker News front page
  and went here and there on the Internet which lends some weight to my theory
  that <strong>a lot of people pay attention to Elixir</strong> right now. Or
  <em>people love eInk displays</em>. Or both?
</p>
<p>
  Thanks for reading about the process and let me know if you have any
  particular thoughts, corrections or feedback at
  <a href="mailto:lars@underjord.io">lars@underjord.io</a>. I'm also
  open to talking business if you need some help with some of the things I do.
</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Artisanal software - Beyond pragmatism</title>
      <link>https://underjord.io/artisanal-software-beyond-pragmatism.html</link>
      <pubDate>Mon, 15 Jul 2019 15:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/artisanal-software-beyond-pragmatism.html</guid>
      <description>Whenever we design and create software we need to pay attention to the trade-offs we are making.
When you make software for a business purpose within a company, the trade-offs are made in relation to business goals, available resources, the deadline and often office and team politics. Quite a few things come into play to shape what you are doing. And developers in these environments generally can&#39;t control all the trade-offs that are being made.</description>
      <content:encoded><![CDATA[ 
<p>Whenever we design and create software we need to pay attention to the trade-offs we are making.</p>
<p>When you make software for a business purpose within a company, the trade-offs are made in relation to business
    goals, available resources, the deadline and often office and team politics. Quite a few things come into play to
    shape what you are doing. And developers in these environments generally can't control all the trade-offs that
    are being made.</p>
<p>I find myself growing wary about what trade-offs we make and whether we are aware of them all.</p>
<p>Need to make a desktop application?<br/>Electron would be a popular choice.</p>
<p>Upsides? We build with the web stack we know. We have a lot of flexibility in the UI design. It runs anywhere that
    can run Chromium. It is modern and convenient to work with as a developer. Getting web frontend devs is generally
    also cheaper than developers comfortable with any other UI toolkit.</p>
<p>Trade-offs? Disk space and memory usage are the first to go. We simplify to a mostly single-threaded browser runtime
    which while fast for what it is shouldn't be anywhere near a native UI toolkit in performance. And the UI
    won't fit the OS in any real way.</p>
<p>Who is paying the cost for the benefits we gain? It doesn't seem to be the developer. <strong>Nope, it's the
        user.</strong>
</p>
<p>And it is a pragmatic choice. I don’t think developers should feel any particular shame in making choices they
    believe will serve them well. Especially not when working within tight constraints without full control. But I also
    don’t like where some of the current software trends are taking us. A pragmatic choice is usually not the <em>ideal
        choice</em>. It is a <em>situational choice</em> and if we can loosen the constraints a bit we might have the
    room to make a
    better choice.</p>
<p><strong>Electron is not the problem.</strong> It is simply the new cross-platform solution, much like a Java GUI was
    a few hundred
    years ago. It worked everywhere and barely felt at home anywhere. It too had performance trade-offs. Today
    they'd be blazingly fast and incredibly efficient in comparison I imagine.</p>
<p>This is one of many kinds of choices made frequently in software development where the downside is largely placed on
    the end-user’s shoulders. There are plenty of others. How many slightly-too-big-but-very-productive frameworks do we
    bring in? I don't feel the same about Bootstrap as I once did, for example. It is <em>useful</em>, don't get
    me wrong.
    But is it
    in line with what I envision my craft to be? Do I feel professional pride in a good choice well made or did I just
    default into it?</p>
<p>Maybe you write things single-threaded and have to think less as a developer. Yay, we've solved UI! But your
    application meanders along where it could be blazing forward on 8+ under-utilized cores. Ads show up in an operating
    system you paid actual money for, costing you performance, peace of mind and pissing you off all at the same time.
</p>
<p>Computers are fast now, right? It won’t even be noticeable. And <em>ads are fine, right?</em> Software has ads.
    <strong>Right?</strong></p>
<p>Many of us know the feeling of a new PC laptop full of pre-installed garbage where all you really want are the basic
    drivers. A lot of people work with Slack every day, I tend to. That is not a snappy client, I’m sorry to say. Is it
    any wonder that our computers don't seem to be getting much faster?</p>
<p>Spotify got rid of their old client which was a fast, competent, music player and now it is a sluggish web-based
    beast. They did not make that change for us customers. Or did they? I guess it could be argued that they can execute
    on their plans and development faster and build more visually compelling designs with less effort and complexity.
    But there was a significant cost in performance. It went from an app that I enjoyed using to one I'm
    <strong>constantly
        a little bit annoyed</strong> with. It might be a sensible choice for them and their business. Can you tell that
    I think they
    made a poor choice?</p>
<p>This is normal. It aligns with business goals I’m sure. But what I want to see more of is software that serves the
    customer or user directly and primarily. It does not have to mean minimalist. Maybe there is a point to Discord
    looking the exact same on every device. Maybe it would never have hit market if they couldn’t lean on Chromium’s
    WebRTC implementation for communication. But I’d love it if it was leaner.</p>
<p>On the Mac and iOS side there is a culture of power-users <em>paying for software</em>. Software that works for the
    user,
    where ads would detract from the quality and experience. Instead the developer gets paid for the software, often
    these days, with a subscription to make the business case more sustainable. They are often written with the native
    tooling, they fit into the system as a whole, they extend it where need be. There might a significant Windows
    subculture for power user software like that. I’m not familiar with it.</p>
<p>I’ve heard trying to sell software to Linux users can be a bit of an uphill battle. Understandably so. But the open
    source community also has a pretty good tradition of native clients, native software and so on. A bit more
    fragmented perhaps but for fair cultural reasons. There are solid options with GTK, Qt and probably plenty more.</p>
<p>So should everything be like Mac power-user software? Not necessarily. I think of this model as the artisanal coffee,
    craft beer, audiophile approach to software. I don't think we can get everyone to abandon the free-as-in-ads
    services, arguably the fast food of software. We don't have to bring everyone over. But if we can make sure
    people are aware that there are options. That they are either making trade-offs or are suffering the trade-offs of
    heavy-handed business choices. Much like a modern hipster-compliant café we would just need to seem nicer, cooler,
    the high-quality choice.</p>
<p>I think we could make software that is fast, useful to the actual user and costs money where it makes sense. I
    don't think you can get the same scale. But if you are a single developer, or a small company, <strong>you
        don't
        need web-scale</strong>, you don't need the growth hacking or VC money. Just get paid, normally. You just
    need to make a good living. And in spite
    of the current mainstream, I think you can make a living getting paid for making good software, even great software.
    Some people definitely do that.</p>
<p>This is something I want to aspire to in how I build software. I want to build fast and performant. I want to make
    software that fulfills its purpose well. Not apps that are trying to capture the user's engagement, min-max
    their emotional response or profile them for marketing. I want to provide systems and services that serve the user
    well rather than serving an opaque mess of business interests.</p>
<p>This grass-fed, hand-ground artisanal development generally won't be a good fit for client-work so I'm likely
    to primarily apply it for my own creations. I'm holding some hope that with the declining value of random ads as
    a means of funding apps, services and websites we might even see this kind of craftsmanship- &amp; quality-oriented
    software rise in popularity. As a selling point if nothing else. Much like the organic growth of business or
    Company-of-One attitude, I think this is a <em>long-term more sustainable</em> thing for our culture. But of course,
    I'm not
    holding my breath.</p>
<p>If you have some thoughts to share on this topic, feel free to let me know at <a href="mailto:lars@underjord.io">lars@underjord.io</a>. If you have an interest in my professional
    services in spite of this post, feel free to browse the rest of the site.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>An eInk display with Nerves &amp;amp; Elixir - Getting started with Inky</title>
      <link>https://underjord.io/an-eink-display-with-nerves-elixir.html</link>
      <pubDate>Sun, 07 Jul 2019 18:10:00 +0000</pubDate>
      
      <guid>https://underjord.io/an-eink-display-with-nerves-elixir.html</guid>
      <description>So I&#39;ve been curious about what kinds of displays you can connect to the Pi-series single board computers for a while. I happened to accidentally order a few. Among others an eInk display. I ordered the PaPiRus ePaper. It ended up being dead on arrival and then out of stock so I received an Inky to replace it. Fair enough.
eInk, as you know, is cool because it doesn&#39;t need power to keep displaying whatever you made it display last.</description>
      <content:encoded><![CDATA[ 
<p>So I've been curious about what kinds of displays you can connect to the Pi-series single board computers for a
    while. I happened to accidentally order a few. Among others an eInk display. I ordered the PaPiRus ePaper. It ended
    up being dead on arrival and then out of stock so I received an Inky to replace it. Fair enough.</p>
<p>eInk, as you know, is cool because it doesn't need power to keep displaying whatever you made it display last.
    Also, it is quite readable in sunlight and a few other niceties that light-emitting displays lack.</p>
<p>Drawbacks? Terrible refresh rate. The Inky refreshes in seconds, actually quite a bunch of them. And it does an
    annoying blink-cycle and stuff. But it supports an additional color. Generally red or yellow so far. That is pretty
    neat.</p>
<p>So lets get you started with running this from under Nerves and Elixir.</p>
<h2>What you need</h2>
<h3>Knowledge</h3>
<ul>
<li>A working understanding of programming, preferrably functional programming.</li>
<li>Some experience with the Elixir programming language probably helps. This is targeted to Nerves enthusiasts and
        people with an interest in Elixir.</li>
</ul>
<h3>Hardware</h3>
<ul>
<li>An Inky display from Pimoroni. We've tested the Red PHAT because we've had those to play with. But we
        should be compatible with the WHAT too.</li>
<li>A Raspberry Pi device. Our testing has been on Pi Zero W. It should work fine with whatever the Inky supports.
    </li>
<li>SD-card reader for whatever SD-card you use with your Pi. A card you don't mind overwriting.</li>
<li>Preferably a micro-USB-male to USB-male (type A) cable for gadget mode updates on a Pi 3A+ or Zero. Otherwise
        any network connection between your computer and Pi device should also be fine, wired or less so.</li>
</ul>
<h3>Software</h3>
<ul>
<li>Nerves with Elixir and OTP, follow <a href="https://hexdocs.pm/nerves/installation.html" title="Nerves installation guide">the Installation guide</a>.</li>
<li>A code editor (Visual Studio Code is a free one that's fine).</li>
<li>A working terminal where you can do your mix command stuff for Elixir.</li>
</ul>
<h2>A basic Nerves example</h2>
<p>We'll start by getting things running on Nerves and then work our way up.</p>
<h3>Generating the app</h3>
<p>To get a new project set up you can use this command. I think the --init-gadget flag might be the default now but I'm
    backwards compatible I guess. Say yes if it asks to download deps. Those are nice to have.</p>


  <div class="code  shell "  data-file="/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix nerves.new eink --init-gadget
cd eink
export MIX_TARGET<span style="color:#f92672">=</span>rpi0</code></pre></div>
  </div>


<p>You need to set your MIX_TARGET-variable. It should match the type of Pi hardware you have. And it needs to be done
    again if you use a fresh new terminal.</p>
<p>To get going with this we want to add our dependency for our display. We edit our mix.exs to include this in the
    deps, right under <code>{:toolshed, "~> 0.2"},</code> or so is fine:</p>


  <div class="code  elixir "  data-file="mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">{<span style="color:#e6db74">:inky</span>, <span style="color:#e6db74">&#34;~&gt; 1.0&#34;</span>}</code></pre></div>
  </div>


<p>Fetch dependencies to get inky into your project. This will also pull in circuits_gpio and circuits_spi since
    that's how we talk to the device.</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix deps.get</code></pre></div>
  </div>


<p>Next we add some code to actually get our code running and doings things. We will change the Eink.Application module
    to start with. Primarily we change the children functions.</p>


  <div class="code  elixir "  data-file="/eink/lib/eink/application.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/lib/eink/application.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Eink.Application</span> <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">@target</span> <span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span>target()

    <span style="color:#f92672">use</span> <span style="color:#a6e22e">Application</span>

    <span style="color:#66d9ef">def</span> start(_type, _args) <span style="color:#66d9ef">do</span>
        opts <span style="color:#f92672">=</span> [<span style="color:#e6db74">strategy</span>: <span style="color:#e6db74">:one_for_one</span>, <span style="color:#e6db74">name</span>: <span style="color:#a6e22e">Eink.Supervisor</span>]
        <span style="color:#a6e22e">Supervisor</span><span style="color:#f92672">.</span>start_link(children(<span style="color:#a6e22e">@target</span>), opts)
    <span style="color:#66d9ef">end</span>

    <span style="color:#66d9ef">def</span> children(_target) <span style="color:#66d9ef">do</span>
        [
        {<span style="color:#a6e22e">Eink.Display</span>, <span style="color:#66d9ef">nil</span>}
        ]
    <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>And we'll create a basic GenServer that will run our display for us:</p>


  <div class="code  elixir "  data-file="/eink/lib/eink.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/lib/eink.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Eink.Display</span> <span style="color:#66d9ef">do</span>
	<span style="color:#f92672">use</span> <span style="color:#a6e22e">GenServer</span>

	<span style="color:#66d9ef">def</span> start_link(opts) <span style="color:#66d9ef">do</span>
	  <span style="color:#a6e22e">GenServer</span><span style="color:#f92672">.</span>start_link(__MODULE__, <span style="color:#66d9ef">nil</span>, opts)
	<span style="color:#66d9ef">end</span>

	<span style="color:#a6e22e">@impl</span> <span style="color:#66d9ef">true</span>
	<span style="color:#66d9ef">def</span> init(_) <span style="color:#66d9ef">do</span>
	  {<span style="color:#e6db74">:ok</span>, pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Inky</span><span style="color:#f92672">.</span>start_link(<span style="color:#e6db74">:phat</span>, <span style="color:#e6db74">:red</span>)

	  <span style="color:#a6e22e">Inky</span><span style="color:#f92672">.</span>set_pixels(pid, <span style="color:#66d9ef">fn</span> x, y, _width, _height, _current <span style="color:#f92672">-&gt;</span>
		<span style="color:#75715e"># Delightful checkerboard</span>
		x_odd <span style="color:#f92672">=</span> rem(x, <span style="color:#ae81ff">2</span>) <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span>
		y_odd <span style="color:#f92672">=</span> rem(y, <span style="color:#ae81ff">2</span>) <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span>

		<span style="color:#66d9ef">case</span> x_odd <span style="color:#66d9ef">do</span>
		  <span style="color:#66d9ef">true</span> <span style="color:#f92672">-&gt;</span>
			<span style="color:#66d9ef">case</span> y_odd <span style="color:#66d9ef">do</span>
			  <span style="color:#66d9ef">true</span> <span style="color:#f92672">-&gt;</span> <span style="color:#e6db74">:black</span>
			  <span style="color:#66d9ef">false</span> <span style="color:#f92672">-&gt;</span> <span style="color:#e6db74">:accent</span>
			<span style="color:#66d9ef">end</span>

		  <span style="color:#66d9ef">false</span> <span style="color:#f92672">-&gt;</span>
			<span style="color:#66d9ef">case</span> y_odd <span style="color:#66d9ef">do</span>
			  <span style="color:#66d9ef">true</span> <span style="color:#f92672">-&gt;</span> <span style="color:#e6db74">:accent</span>
			  <span style="color:#66d9ef">false</span> <span style="color:#f92672">-&gt;</span> <span style="color:#e6db74">:white</span>
			<span style="color:#66d9ef">end</span>
		<span style="color:#66d9ef">end</span>
	  <span style="color:#66d9ef">end</span>)

	  {<span style="color:#e6db74">:ok</span>, <span style="color:#66d9ef">nil</span>}
	<span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>Perfect! Let's <strong></strong>ship it!</p>
<h2>Try it on the device</h2>
<h3>Pre-flight check for firmware</h3>
<h4>SSH-keys</h4>
<p>To connect to the device over SSH after it is up and running you will need a working cryptographic key. Nerves does a
    decent
    job of trying to pick up your public key so if you check in <code>config/config.exs</code> and look for the
    <code>keys</code> variable, you'll see that it will pick up <code>~/.ssh/id_rsa.pub</code> and a few others
    automatically. You can add yours if you don't use id_rsa.pub and you will be ready for SSH. Then you can skip down
    to <a href="#networking-usb">Networking/USB</a>.</p>
<p>If you don't know anything about SSH and keys you can might want to read up on it. But for now you can move on by
    doing this:</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">ssh-keygen -t rsa -C <span style="color:#e6db74">&#34;your_email@example.com&#34;</span> -f nerves</code></pre></div>
  </div>


<p>That will generate a the key (the sensitive key, treat it like a password in a file) with the filename
    <code>nerves</code> and the public key (harmless) <code>nerves.pub</code>.</p> To make nerves pick these up,
    change the keys section to this:


  <div class="code  elixir "  data-file="/eink/config/config.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/config/config.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">..</span>
keys <span style="color:#f92672">=</span>
  [
    <span style="color:#e6db74">&#34;./nerves.pub&#34;</span>
  ]
  <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>filter(<span style="color:#f92672">&amp;</span>amp;<span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>exists?<span style="color:#f92672">/</span><span style="color:#ae81ff">1</span>)
<span style="color:#f92672">..</span></code></pre></div>
  </div>


<h4 id="networking-usb">Networking/USB</h4>
<p>Using a Pi Zero or a Pi 3 A+ and a decent USB-cable you can use the default configuration for nerves_init_gadget
    which is to do networking through the usb0 interface over USB gadget mode. This means you plug your Pi Zero in with
    the inner micro-USB port and it gets power from your machine while it also establishes a network connection. This
    connection can be used to push updated firmware which is incredibly convenient. An mdns domain is used to allow you
    a convenient way to connect (note: this will not resolve on Android).</p>
<p>By default you get this:</p>

  <div class="code  elixir "  data-file="/eink/config/config.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/config/config.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">config <span style="color:#e6db74">:nerves_init_gadget</span>,
	<span style="color:#e6db74">ifname</span>: <span style="color:#e6db74">&#34;usb0&#34;</span>,
	<span style="color:#e6db74">address_method</span>: <span style="color:#e6db74">:dhcpd</span>,
	<span style="color:#e6db74">mdns_domain</span>: <span style="color:#e6db74">&#34;nerves.local&#34;</span>,
	<span style="color:#e6db74">node_name</span>: node_name,
	<span style="color:#e6db74">node_host</span>: <span style="color:#e6db74">:mdns_domain</span></code></pre></div>
  </div>


<p>If you do not have a cable or have something like Pi 3 of the bigger variety you don't have USB Gadget mode. It's
    fine. We got you. The <a href="https://github.com/nerves-project/nerves_init_gadget">Nerves Init Gadget README</a>
    covers the basic configurations variants. You can use Ethernet or WiFi without issue. A sample wifi config would
    look like this. Note, I include the SSID and PSK in the config, <strong>this is not a good idea for a real
        project</strong>. The proper way is covered in <a href="https://github.com/nerves-project/nerves_network#installation-and-setup">the
        nerves_network README</a>. I
    simplify to avoid a confusing and an ever-branching guide.</p>


  <div class="code  elixir "  data-file="/eink/config/config.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/config/config.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">config <span style="color:#e6db74">:nerves_init_gadget</span>,
	<span style="color:#e6db74">ifname</span>: <span style="color:#e6db74">&#34;wlan0&#34;</span>,
	<span style="color:#e6db74">address_method</span>: <span style="color:#e6db74">:dhcp</span>, <span style="color:#75715e"># Note: not dhcpd, that&#39;s for the USB thing</span>
	<span style="color:#e6db74">mdns_domain</span>: <span style="color:#e6db74">&#34;nerves.local&#34;</span>,
	<span style="color:#e6db74">node_name</span>: node_name,
	<span style="color:#e6db74">node_host</span>: <span style="color:#e6db74">:mdns_domain</span>

<span style="color:#75715e"># Additionally, configure the wifi connection in nerves_network</span>
config <span style="color:#e6db74">:nerves_network</span>, <span style="color:#e6db74">:default</span>,
	<span style="color:#e6db74">wlan0</span>: [
	<span style="color:#e6db74">networks</span>: [
		[
		<span style="color:#e6db74">ssid</span>: <span style="color:#e6db74">&#34;YourNetworkName&#34;</span>, <span style="color:#75715e"># Change this</span>
		<span style="color:#e6db74">psk</span>: <span style="color:#e6db74">&#34;YourNetworkPass&#34;</span>, <span style="color:#75715e"># Change this</span>
		<span style="color:#e6db74">key_mgmt</span>: <span style="color:#a6e22e">String</span><span style="color:#f92672">.</span>to_atom(<span style="color:#e6db74">&#34;WPA-PSK&#34;</span>),
		<span style="color:#e6db74">scan_ssid</span>: <span style="color:#ae81ff">1</span> <span style="color:#75715e">#if your WiFi setup as hidden</span>
		]
	]
	],
	<span style="color:#e6db74">eth0</span>: [
	<span style="color:#e6db74">ipv4_address_method</span>: <span style="color:#e6db74">:dhcp</span>
	]</code></pre></div>
  </div>


<p>Finally, if you need ethernet it is a minor edit to the usb0 default:</p>


  <div class="code  elixir "  data-file="/eink/config/config.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/config/config.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">config <span style="color:#e6db74">:nerves_init_gadget</span>,
	<span style="color:#e6db74">ifname</span>: <span style="color:#e6db74">&#34;eth0&#34;</span>,
	<span style="color:#e6db74">address_method</span>: <span style="color:#e6db74">:dhcp</span>, <span style="color:#75715e"># Note, not dhcpd</span>
	<span style="color:#e6db74">mdns_domain</span>: <span style="color:#e6db74">&#34;nerves.local&#34;</span>,
	<span style="color:#e6db74">node_name</span>: node_name,
	<span style="color:#e6db74">node_host</span>: <span style="color:#e6db74">:mdns_domain</span></code></pre></div>
  </div>


<p>With one of these approaches you should have a network once we get the firmware on the device. Let's do the
    ceremonial burning of an image to a hard medium and then we can just shuffle bits like the futuristic people we are.
</p>
<h3>Burning the initial firmware</h3>
<p>So connect your card-reader, stick the SD-card you want to sacrifice to the hardware gods in there and run the
    following to burn it.</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#75715e"># Compiles your firmware, we will do this a lot.</span>
mix firmware
<span style="color:#75715e"># Burns it to the card, with some dialog options. Ideally, we do this once.</span>
mix firmware.burn</code></pre></div>
  </div>


<p>This process is temporary, nerves_init_gadget should allow us to push firmware over the connection we configured.
    Let's try it. Put the SD card in your device, connect the device to power.</p>
<p>Wait for it.. Wait for it... It <em>should</em> start blinking... and <strong>bam!</strong> Hopefully you have very
    dense pattern of red, white and black on your screen. Otherwise you should still be able to connect to the device
    and get some logs about what is happening.</p>
<p>If using <code>usb0</code> for
    networking, use the inner port and connect it to your development computer through USB. The Pi 3 A+ probably needs
    separate power, I haven't tried that.</p>
<p>If using WiFi or Ethernet you should just need power and possibly an actual ethernet cable.</p>
<p>Run <code>ping nerves.local</code> and you should see the device resolve and respond to ping. That should mean you
    can connect. If you had your SSH in order you can just use <code>ssh nerves.local</code>. If you made your own
    <code>nerves.pub</code> key to put in the config you connect with <code>ssh -i nerves nerves.local</code> to specify
    the correct key. You should have an interactive elixir prompt (iex) in front of you.</p>
<p>To investigate any issue you can do what the prompt will inform you about an run: <code>RingLogger.next</code>
    inside the device's iex and you'll have the latest logs. You can also do it to just verify things are working.</p>
<p>To close the session and return to your terminal, type <code>exit</code> and hit enter. If that is not working, hit
    enter a few times and the type <code>~.</code> which is some kind of magic evocation for SSH to murder the
    connection.</p>
<h3>Pushing firmware</h3>
<p>Anytime you have changes you want to put on the device from now on you should just push them over the network. To do
    that nerves offers a script. To get the script in place you should run:</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix firmware.gen.script</code></pre></div>
  </div>


<p>This will create a script called <code>upload.sh</code>.</p>
<p>Next time you have changes to try on the device, just run this:</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#75715e"># Compile the firmware</span>
mix firmware
<span style="color:#75715e"># Upload the firmware and trigger a device reboot</span>
./upload.sh</code></pre></div>
  </div>


<p>I keep a terminal up with <code>ping nerves.local</code> to see when the device stops responding (goes down for
    reboot) and comes back up (can be connected to again). This is how I know when I can expect my changes to take
    effect.</p>
<h2>On the host</h2>
<p>Lets get back to the code-base, the project and our development machine for some more useful things.</p>
<h3>Briefly on mocking for hardware development</h3>
<p>Well. Nerves makes pushing firmware very simple and straight-forward. However, when working on visual stuff or
    iterating quickly on some detail of your application it is still pretty inconvenient to have to wait for pushing and
    rebooting over and over again. For this reason and for testability it is very common that you as a developer build
    out mocks or alternate implementations of different sorts. This can allow tests to run without peripheral hardware
    (this is used in the Inky library for example). It can also allow you to iterate faster by having stubs and
    simulations of the hardware you lack while developing on the host environment. We wanted that for you. And for Inky.
</p>
<h3>The host dev library</h3>
<p>In your mix.exs, add our development dependency:</p>


  <div class="code  elixir "  data-file="mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">..</span>
{<span style="color:#e6db74">:inky_host_dev</span>, <span style="color:#e6db74">&#34;~&gt; 1.0&#34;</span>, <span style="color:#e6db74">targets</span>: <span style="color:#e6db74">:host</span>, <span style="color:#e6db74">only</span>: <span style="color:#e6db74">:dev</span>},
<span style="color:#f92672">..</span></code></pre></div>
  </div>


<p>Now we need to do some plumbing just to get things in order for a separation of on-host development vs on-device
    development. By default the nerves init gadget is configured in config.exs. But it doesn't make sense for a host
    environment. So it isn't loaded there. So we shouldn't be configuring things that are not used. What we want is some
    common configuration (maybe). And separate configuration depending on the environment. So most of the configuration
    will move to a device-based configuration in <code>config/device.exs</code> and the host-based configuration will
    be in <code>config/host.exs</code>. So these three pieces will make up our configuration, create the files as
    needed:</p>


  <div class="code  elixir "  data-file="/eink/config/config.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/config/config.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">use</span> <span style="color:#a6e22e">Mix.Config</span>

targeting <span style="color:#f92672">=</span>
  <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span>target() <span style="color:#66d9ef">do</span>
    <span style="color:#e6db74">:host</span> <span style="color:#f92672">-&gt;</span> <span style="color:#e6db74">:host</span>
    _ <span style="color:#f92672">-&gt;</span> <span style="color:#e6db74">:device</span>
  <span style="color:#66d9ef">end</span>

import_config <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">#{</span>targeting<span style="color:#e6db74">}</span><span style="color:#e6db74">.exs&#34;</span></code></pre></div>
  </div>



  <div class="code  elixir "  data-file="/eink/config/host.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/config/host.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">use</span> <span style="color:#a6e22e">Mix.Config</span>

config <span style="color:#e6db74">:inky</span>,
	<span style="color:#e6db74">hal_module</span>: <span style="color:#a6e22e">InkyHostDev.HAL</span></code></pre></div>
  </div>



  <div class="code  elixir "  data-file="/eink/config/device.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/config/device.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">use</span> <span style="color:#a6e22e">Mix.Config</span>

config <span style="color:#e6db74">:nerves</span>, <span style="color:#e6db74">:firmware</span>, <span style="color:#e6db74">rootfs_overlay</span>: <span style="color:#e6db74">&#34;rootfs_overlay&#34;</span>

config <span style="color:#e6db74">:shoehorn</span>,
	<span style="color:#e6db74">init</span>: [<span style="color:#e6db74">:nerves_runtime</span>, <span style="color:#e6db74">:nerves_init_gadget</span>],
	<span style="color:#e6db74">app</span>: <span style="color:#a6e22e">Mix.Project</span><span style="color:#f92672">.</span>config()[<span style="color:#e6db74">:app</span>]

config <span style="color:#e6db74">:logger</span>, <span style="color:#e6db74">backends</span>: [<span style="color:#a6e22e">RingLogger</span>]

<span style="color:#75715e"># Note, make this match your working key-setup</span>
keys <span style="color:#f92672">=</span>
	[
	<span style="color:#e6db74">&#34;./nerves.pub&#34;</span>
	]
	<span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>filter(<span style="color:#f92672">&amp;</span>amp;<span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>exists?<span style="color:#f92672">/</span><span style="color:#ae81ff">1</span>)

<span style="color:#66d9ef">if</span> keys <span style="color:#f92672">==</span> [],
	<span style="color:#e6db74">do</span>:
	<span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span><span style="color:#66d9ef">raise</span>(<span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">	No SSH public keys found in ~/.ssh. An ssh authorized key is needed to
</span><span style="color:#e6db74">	log into the Nerves device and update firmware on it using ssh.
</span><span style="color:#e6db74">	See your project&#39;s config.exs for this error message.
</span><span style="color:#e6db74">	&#34;&#34;&#34;</span>)

config <span style="color:#e6db74">:nerves_firmware_ssh</span>,
	<span style="color:#e6db74">authorized_keys</span>: <span style="color:#a6e22e">Enum</span><span style="color:#f92672">.</span>map(keys, <span style="color:#f92672">&amp;</span>amp;<span style="color:#a6e22e">File</span><span style="color:#f92672">.</span>read!<span style="color:#f92672">/</span><span style="color:#ae81ff">1</span>)

node_name <span style="color:#f92672">=</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span>env() <span style="color:#f92672">!=</span> <span style="color:#e6db74">:prod</span>, <span style="color:#e6db74">do</span>: <span style="color:#e6db74">&#34;eink&#34;</span>

<span style="color:#75715e"># Note: adapt to your configuration, see the networking section</span>
config <span style="color:#e6db74">:nerves_init_gadget</span>,
	<span style="color:#e6db74">ifname</span>: <span style="color:#e6db74">&#34;usb0&#34;</span>,
	<span style="color:#e6db74">address_method</span>: <span style="color:#e6db74">:dhcpd</span>,
	<span style="color:#e6db74">mdns_domain</span>: <span style="color:#e6db74">&#34;nerves.local&#34;</span>,
	<span style="color:#e6db74">node_name</span>: node_name,
	<span style="color:#e6db74">node_host</span>: <span style="color:#e6db74">:mdns_domain</span></code></pre></div>
  </div>


<p>We also need a small code-change to respect the new Inky config. Where we start the Inky GenServer, we need add the
    optional configuration that will plug a new hardware abstraction into Inky:</p>


  <div class="code  elixir "  data-file="/eink/lib/eink.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/lib/eink.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">..</span>
	{<span style="color:#e6db74">:ok</span>, pid} <span style="color:#f92672">=</span> <span style="color:#a6e22e">Inky</span><span style="color:#f92672">.</span>start_link(<span style="color:#e6db74">:phat</span>, <span style="color:#e6db74">:red</span>, %{
		<span style="color:#e6db74">hal_mod</span>: <span style="color:#a6e22e">Application</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">:inky</span>, <span style="color:#e6db74">:hal_module</span>, <span style="color:#a6e22e">Inky.RpiHAL</span>)
	})
<span style="color:#f92672">..</span></code></pre></div>
  </div>


<p>Now to get everything in order for host development:</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#75715e"># Set the mix target, this will change our config/config.exs targeting variable</span>
export MIX_TARGET<span style="color:#f92672">=</span>host
<span style="color:#75715e"># Get the host only deps</span>
mix deps.get</code></pre></div>
  </div>


<p>And to run it:</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix run --no-halt
<span style="color:#75715e"># OR</span>
iex -S mix</code></pre></div>
  </div>


<p>You should see a small window with a similar pattern to what is probably still being displayed on your eInk display.
    This allows you to do the same things the Inky can do, simulated using the native Erlang wxWidgets libraries. A neat
    toy for developers.</p>
<h2>The good stuff - Using Scenic to render text &amp; images</h2>
<p>The Inky library only allows you to push pixels to the device, it doesn't know anything about text, images, geometric
    shapes or any of that interesting stuff. The Python library uses the Python imaging libraries to allow images, I
    haven't checked how they do text really. We use the Scenic framework. Boyd has done hard work so I don't have to
    care about font rendering and I'm definitely going to leverage that.</p>
<p>We need the scenic driver for inky, which is a separate library since it isn't really needed to push pixels to the
    device and it also only works on the pi devices. Of course, on host you can use scenics glfw driver to render. Time
    to make some more changes to our stuff.</p>
<p>First, our dependencies. We can remote inky_host_dev, it doesn't work with the driver and scenic provides a perfectly
    good alternative. Aside from the normal nerves stuff our deps should be:</p>


  <div class="code  elixir "  data-file="mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">..</span>
{<span style="color:#e6db74">:scenic</span>, <span style="color:#e6db74">&#34;~&gt; 0.10&#34;</span>},
{<span style="color:#e6db74">:inky</span>, <span style="color:#e6db74">&#34;~&gt; 1.0&#34;</span>},
{<span style="color:#e6db74">:scenic_driver_inky</span>, <span style="color:#e6db74">&#34;~&gt; 1.0.0&#34;</span>, <span style="color:#e6db74">targets</span>: [<span style="color:#e6db74">:rpi</span>, <span style="color:#e6db74">:rpi0</span>, <span style="color:#e6db74">:rpi2</span>, <span style="color:#e6db74">:rpi3</span>, <span style="color:#e6db74">:rpi3a</span>]},
{<span style="color:#e6db74">:scenic_driver_glfw</span>, <span style="color:#e6db74">&#34;~&gt; 0.10&#34;</span>, <span style="color:#e6db74">targets</span>: <span style="color:#e6db74">:host</span>},
<span style="color:#f92672">..</span></code></pre></div>
  </div>


<p>A few config changes in different files:</p>


  <div class="code  elixir "  data-file="/eink/config/host.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/config/host.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">use</span> <span style="color:#a6e22e">Mix.Config</span>

<span style="color:#75715e"># Note: We don&#39;t need the hal_module config anymore</span>
config <span style="color:#e6db74">:eink</span>, <span style="color:#e6db74">:viewport</span>, %{
	<span style="color:#e6db74">name</span>: <span style="color:#e6db74">:main_viewport</span>,
	<span style="color:#e6db74">default_scene</span>: {<span style="color:#a6e22e">Eink.Scene.Main</span>, <span style="color:#66d9ef">nil</span>},
	<span style="color:#e6db74">size</span>: {<span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">104</span>}, <span style="color:#75715e"># Match these to your inky display</span>
	<span style="color:#e6db74">opts</span>: [<span style="color:#e6db74">scale</span>: <span style="color:#ae81ff">1.0</span>],
	<span style="color:#e6db74">drivers</span>: [
	  %{
		<span style="color:#e6db74">module</span>: <span style="color:#a6e22e">Scenic.Driver.Glfw</span>
	  }
	]
}</code></pre></div>
  </div>



  <div class="code  elixir "  data-file="/eink/config/device.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/config/device.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">config <span style="color:#e6db74">:eink</span>, <span style="color:#e6db74">:viewport</span>, %{
	<span style="color:#e6db74">name</span>: <span style="color:#e6db74">:main_viewport</span>,
	<span style="color:#e6db74">default_scene</span>: {<span style="color:#a6e22e">Eink.Scene.Main</span>, <span style="color:#66d9ef">nil</span>},
	<span style="color:#e6db74">size</span>: {<span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">104</span>}, <span style="color:#75715e"># Note: Match these to your inky display</span>
	<span style="color:#e6db74">opts</span>: [<span style="color:#e6db74">scale</span>: <span style="color:#ae81ff">1.0</span>],
	<span style="color:#e6db74">drivers</span>: [
		%{
		<span style="color:#e6db74">module</span>: <span style="color:#a6e22e">ScenicDriverInky</span>,
		<span style="color:#e6db74">opts</span>: [
			<span style="color:#75715e"># Note: Match these to your Inky display</span>
			<span style="color:#e6db74">type</span>: <span style="color:#e6db74">:phat</span>,
			<span style="color:#e6db74">accent</span>: <span style="color:#e6db74">:red</span>,
			<span style="color:#e6db74">opts</span>: %{
				<span style="color:#e6db74">border</span>: <span style="color:#e6db74">:black</span>
			}
		]
		}
	]
}</code></pre></div>
  </div>



  <div class="code  elixir "  data-file="/eink/lib/eink/application.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/lib/eink/application.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Eink.Application</span> <span style="color:#66d9ef">do</span>
    <span style="color:#a6e22e">@target</span> <span style="color:#a6e22e">Mix</span><span style="color:#f92672">.</span>target()

    <span style="color:#f92672">use</span> <span style="color:#a6e22e">Application</span>

    <span style="color:#66d9ef">def</span> start(_type, _args) <span style="color:#66d9ef">do</span>
    opts <span style="color:#f92672">=</span> [<span style="color:#e6db74">strategy</span>: <span style="color:#e6db74">:one_for_one</span>, <span style="color:#e6db74">name</span>: <span style="color:#a6e22e">Eink.Supervisor</span>]
    <span style="color:#a6e22e">Supervisor</span><span style="color:#f92672">.</span>start_link(children(<span style="color:#a6e22e">@target</span>), opts)
    <span style="color:#66d9ef">end</span>

    <span style="color:#66d9ef">def</span> children(_target) <span style="color:#66d9ef">do</span>
    main_viewport_config <span style="color:#f92672">=</span> <span style="color:#a6e22e">Application</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">:eink</span>, <span style="color:#e6db74">:viewport</span>)

    [
    {<span style="color:#a6e22e">Eink.Display</span>, []},
    {<span style="color:#a6e22e">Scenic</span>, <span style="color:#e6db74">viewports</span>: [main_viewport_config]}
    ]
    <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>And a new folder (optionally) and a new module file:</p>


  <div class="code  elixir "  data-file="/eink/lib/scenes/main.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/lib/scenes/main.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Eink.Scene.Main</span> <span style="color:#66d9ef">do</span>
    <span style="color:#f92672">use</span> <span style="color:#a6e22e">Scenic.Scene</span>
    <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Scenic.Graph</span>

    <span style="color:#f92672">import</span> <span style="color:#a6e22e">Scenic.Primitives</span>

    <span style="color:#a6e22e">@graph</span> <span style="color:#a6e22e">Graph</span><span style="color:#f92672">.</span>build(<span style="color:#e6db74">font_size</span>: <span style="color:#ae81ff">32</span>, <span style="color:#e6db74">font</span>: <span style="color:#e6db74">:roboto_mono</span>, <span style="color:#e6db74">theme</span>: <span style="color:#e6db74">:light</span>)
           <span style="color:#f92672">|&gt;</span> rectangle({<span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">32</span>}, <span style="color:#e6db74">fill</span>: <span style="color:#e6db74">:red</span>)
           <span style="color:#f92672">|&gt;</span> rectangle({<span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">64</span>}, <span style="color:#e6db74">t</span>: {<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">32</span>}, <span style="color:#e6db74">fill</span>: <span style="color:#e6db74">:white</span>)
           <span style="color:#f92672">|&gt;</span> rectangle({<span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">8</span>}, <span style="color:#e6db74">t</span>: {<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">32</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">64</span>}, <span style="color:#e6db74">fill</span>: <span style="color:#e6db74">:red</span>)

    <span style="color:#66d9ef">def</span> init(_, _) <span style="color:#66d9ef">do</span>
      state <span style="color:#f92672">=</span> %{
        <span style="color:#e6db74">graph</span>: <span style="color:#a6e22e">@graph</span>
      }

      {<span style="color:#e6db74">:ok</span>, state, <span style="color:#e6db74">push</span>: <span style="color:#a6e22e">@graph</span>}
    <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>Let's try it locally:</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">    export MIX_TARGET<span style="color:#f92672">=</span>host
    mix deps.get
    mix scenic.run</code></pre></div>
  </div>


<p>Some red, some white. We are making primitives happen! If you encounter
    the
    following error when trying to run it, there is a known issue with
    scenic 0.10:</p>


  <div class="code  shell " >
    <div class="meta">
      <span class="language">shell</span>
      
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sh: line 0: exec: /Users/lawik/projects/eink/_build/dev/lib/scenic_driver_glfw/priv/prod/scenic_driver_glfw:
    cannot execute: No such file or directory
16:43:30.983 <span style="color:#f92672">[</span>error<span style="color:#f92672">]</span> dirty close</code></pre></div>
  </div>


<p>Fix it with fire:</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">rm -rf _build/dev/lib/scenic_driver_glfw
mix scenic.run</code></pre></div>
  </div>


<p>With that done, let's try it on the device, again the dirty close issue above may show its head, so we will run with
    the MIX_ENV set to prod, which seems to help:</p>


  <div class="code  shell "  data-file="/eink" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/eink</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#75715e"># Note: Adjust for your device target</span>
export MIX_TARGET<span style="color:#f92672">=</span>rpi0
<span style="color:#75715e"># Fixes an issue in scenic 0.10, shouldn&#39;t be necessary otherwise</span>
export MIX_ENV<span style="color:#f92672">=</span>prod
mix deps.get
mix firmware
./upload.sh</code></pre></div>
  </div>


<p>After a brief look at the dark boot screen, with a chance to see the corner of a Raspberry Pi boot image it should
    refresh again to show the same geometries.</p>
<h3>Rendering text</h3>
<p>Scenic allows us to render text. We won't get perfect results because Scenic relies on NanoVG (a minimal C-library
    that does OpenGL stuff) and NanoVG doesn't have an option to disable anti-aliasing of text. So we can't get
    pixel-perfect representation. But we will get text, and we don't have to screw around with fonts. If you want faked
    half-tones we also have a dithering option in the driver. It looks funny but might help you. Let's change our code
    to have some text:</p>


  <div class="code  elixir "  data-file="/eink/lib/scenes/main.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">/eink/lib/scenes/main.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">Eink.Scene.Main</span> <span style="color:#66d9ef">do</span>
  <span style="color:#f92672">use</span> <span style="color:#a6e22e">Scenic.Scene</span>
  <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Scenic.Graph</span>

  <span style="color:#f92672">import</span> <span style="color:#a6e22e">Scenic.Primitives</span>

  <span style="color:#a6e22e">@font</span> <span style="color:#e6db74">:roboto</span>
  <span style="color:#a6e22e">@font_size</span> <span style="color:#ae81ff">20</span>

  <span style="color:#66d9ef">def</span> init(_, _) <span style="color:#66d9ef">do</span>
    graph <span style="color:#f92672">=</span>
      <span style="color:#a6e22e">Graph</span><span style="color:#f92672">.</span>build(<span style="color:#e6db74">font_size</span>: <span style="color:#a6e22e">@font_size</span>, <span style="color:#e6db74">font</span>: <span style="color:#a6e22e">@font</span>, <span style="color:#e6db74">theme</span>: <span style="color:#e6db74">:light</span>)
      <span style="color:#f92672">|&gt;</span> rectangle({<span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">32</span>}, <span style="color:#e6db74">fill</span>: <span style="color:#e6db74">:red</span>)
      <span style="color:#f92672">|&gt;</span> rectangle({<span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">64</span>}, <span style="color:#e6db74">t</span>: {<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">32</span>}, <span style="color:#e6db74">fill</span>: <span style="color:#e6db74">:white</span>)
      <span style="color:#f92672">|&gt;</span> rectangle({<span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">8</span>}, <span style="color:#e6db74">t</span>: {<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">32</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">64</span>}, <span style="color:#e6db74">fill</span>: <span style="color:#e6db74">:red</span>)
      <span style="color:#f92672">|&gt;</span> do_aligned_text(<span style="color:#e6db74">&#34;HELLO&#34;</span>, <span style="color:#e6db74">:white</span>, <span style="color:#a6e22e">@font_size</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">6</span>, <span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">20</span>)
      <span style="color:#f92672">|&gt;</span> do_aligned_text(<span style="color:#e6db74">&#34;my name is&#34;</span>, <span style="color:#e6db74">:white</span>, <span style="color:#a6e22e">@font_size</span> <span style="color:#f92672">-</span> <span style="color:#ae81ff">8</span>, <span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">28</span>)
      <span style="color:#f92672">|&gt;</span> do_aligned_text(<span style="color:#e6db74">&#34;Inky&#34;</span>, <span style="color:#e6db74">:black</span>, <span style="color:#a6e22e">@font_size</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">32</span>, <span style="color:#ae81ff">212</span>, <span style="color:#ae81ff">80</span>)

    state <span style="color:#f92672">=</span> %{
      <span style="color:#e6db74">graph</span>: graph
    }

    {<span style="color:#e6db74">:ok</span>, state, <span style="color:#e6db74">push</span>: graph}
  <span style="color:#66d9ef">end</span>

  <span style="color:#66d9ef">defp</span> do_aligned_text(graph, text, fill, font_size, width, vpos) <span style="color:#66d9ef">do</span>
    text(graph, text,
      <span style="color:#e6db74">font_size</span>: font_size,
      <span style="color:#e6db74">fill</span>: fill,
      <span style="color:#e6db74">translate</span>: {width <span style="color:#f92672">/</span> <span style="color:#ae81ff">2</span>, vpos},
      <span style="color:#e6db74">text_align</span>: <span style="color:#e6db74">:center</span>
    )
  <span style="color:#66d9ef">end</span>
<span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>Then mix the firmware and dot slash upload it, wait for the beautiful result. Okay, parts are somewhat unreadable,
    Retina it ain't. I'm sure you can improve it.</p>
<p><a class="image" href="assets/images/blog/hello-my-name-is-inky.jpg" target="_blank"><img alt="Fingers holding eInk display with text: Hello, my name is Inky" class="blog-image" src="assets/images/blog/hello-my-name-is-inky.jpg"/></a></p>
<p>But this is as far as we go this time, old friend. We've been through a lot. You now have the power to do weird
    things with your Inky display using the glorious tooling of Nerves and the fanciness of Scenic. If you want to keep
    in touch after this perilous journey I'm available at <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p>
<p>My final code is available <a href="https://github.com/lawik/tutorial_eink">here</a> if you need to check something
    or want to refer back.
</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Inky library released!</title>
      <link>https://underjord.io/inky-library-release.html</link>
      <pubDate>Thu, 04 Jul 2019 10:38:00 +0000</pubDate>
      
      <guid>https://underjord.io/inky-library-release.html</guid>
      <description>Me and nyaray finally finished up our work on the Inky eInk display library to a level where we are happy to release it. So Inky 1.0.0 is now out on Hex! Docs are on there too.
This is a small library and what started as a simple almost line-by-line port of the Python library on my part is now testable and tested. It has a simulator for on-host development (separate library, inky_host_dev).</description>
      <content:encoded><![CDATA[ 
<p>Me and <a href="https://github.com/nyaray">nyaray</a> finally finished up our work on the Inky eInk display library
    to a level where we are happy to release it. So <a href="https://hex.pm/packages/inky/">Inky 1.0.0</a> is now out on
    Hex! Docs are <a href="https://hexdocs.pm/inky/Inky.html">on there</a> too.</p>
<p>This is a small library and what started as a simple almost line-by-line port of the Python library on my part is now
    testable and tested. It has a simulator for on-host development (separate library,
    <a href="https://github.com/pappersverk/inky_host_dev">inky_host_dev</a>). It has a Scenic
    driver (separate library, <a href="https://github.com/pappersverk/scenic_driver_inky">scenic_driver_inky</a>). It
    has a little
    bit of CircleCI and all that good stuff. I'll
    primarily credit nyaray with raising the bar on me and I'll take credit for getting the ball rolling, making the
    driver and host dev libraries happen.</p>
<p>This has been a lot of fun. And if you need slightly colorful eInk display in your Pi Zero project (or the bigger
    wHAT) this is a neat display. Obviously all the good and bad of eInk applies. Slow refresh. So slow. But also,
    doesn't need power to sustain the pixels so you can see your last debug attempt on the device for days, even when
    the device is off, until you finally fix it and manage to push new pixels to the device. Or it will stare at you.
    Forever. So yeah, great device :)</p>
<p>There are a few more things we want to do along the way. Mostly to have perfect support-parity with the original
    Python
    library from Pimoroni. Nothing major.</p>
<p>We've tried to provide everything you need to get started. So check out our pretentious european
    art-collective/github organisation <a href="https://github.com/pappersverk">pappersverk</a> where you will find a
    couple of sample repositories to get you started, beyond the readmes and docs. If you find problems, give us issues
    on GitHub, if you want to talk about this stuff we're usually in #nerves on the Elixir-lang Slack.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Revitalizing valuable legacy systems</title>
      <link>https://underjord.io/revitalizing-valuable-legacy-systems.html</link>
      <pubDate>Mon, 24 Jun 2019 11:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/revitalizing-valuable-legacy-systems.html</guid>
      <description>Do you have a system that is vital to your business that your development team seems to have given up on? Do they consider it old, slow, complicated or impossible to work with? Are they pushing heavily for a rewrite?
They are probably right. It has probably become old, slow and difficult to work with. And it might be fair that they are trying to sell you on a rewrite.</description>
      <content:encoded><![CDATA[ 
<p>Do you have a system that is vital to your business that your development team seems to have given up on? Do they consider it old, slow, complicated or impossible to work with? Are they pushing heavily for a rewrite?</p>
<p>They are probably right. It has probably become old, slow and difficult to work with. And it might be fair that they are trying to sell you on a rewrite. They see a possibility to make something significantly better, unburdened by existing solutions. Unfortunately, it is incredibly common to <strong>underestimate the cost and effort</strong> of such a rewrite. Joel Spolsky famously named it as one of <a href="https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/">things you should never do</a>. You can add a bunch of asterisks to that, but it is generally sound advice. What is already built already has value.</p>
<h2>What can you do?</h2>
<p>So as a decision maker who has to keep business needs in mind, what are your options? Well, I have some good news with the bad. The good news. You have <strong>a proven system that is working</strong>, generating value right now. The bad news. If it is not continuously kept up to date and maintained it will reach a point where it is hopelessly behind and no longer a net benefit. To avoid this, you need a strategy. You need a plan. And you need to get your team on-board.</p>
<p>Make no mistake, improving the quality of your system, establishing the processes to prevent degeneration and modernizing what must be modernized requires investment. It will not be free and it will likely require diverting some time from new development. But unless your business process and the business system have diverged and no longer line up, it should all still be cheaper than a rewrite and have a much higher likelihood of a good outcome. If it is already making money, it should keep making money along the way.</p>
<h2>How do you get people on board?</h2>
<p><strong>Your team is not unreasonable</strong> for desiring a rewrite. They see an opportunity to build something with the best and most modern tools, something that will be less work to maintain, easier to extend and won't be a convoluted mess because this time it will be done right!</p>
<p>They want to build a great system for you and your business. But the system you have might already be great. It just happens to be very hard to work with for your team of developers. Or maybe the user interface that your employees use is old and outdated. Maybe it has become a bit of a mess during a few intense development efforts. Maybe a previous employee made a mess.</p>
<h3>Appeal to the craft</h3>
<p>What I would suggest is investing in improvement. Focusing on the craftsmanship of software development, rather than the cutting edge design and concepts.</p>
<p>Is the system fragile? Is it easy to accidentally break things?</p>
<p>Create solid test coverage. Invest in making reliable tests and the infrastructure necessary to make them work, such as mocks and generators for test data. When you have tests for all the hardest parts. Make sure <strong>the tests run in an automated fashion whenever a change is made</strong>. Go as far as is feasible towards Continuous Integration.</p>
<h3>Remove sources of stress</h3>
<p>Is deployment complicated and involved? Is there only one person that can do it? Automate it. Put the time in and make sure manual work is reduced to the absolute minimum. Make sure the knowledge of using the deploy system is spread through the team. Make sure deployments can be reverted in a reasonable manner.</p>
<p>Are there recurring outages, disturbances, performance issues? Make sure there is <strong>automated tracking of key metrics</strong> and make sure your dashboard for these are green. Set real goals for your system: What percentage of requests should we be able to respond to? What is an acceptable average response-time for requests? Measure these, alert when the system can't deliver according to the goals and fix those issues.</p>
<h3>Re-establish pride in the system</h3>
<p>Is the system reliant on incredibly outdated software packages and the road to updating scary? With tests in place and a steady hand this can generally be fixed. And your team gets to establish routines for how to keep these things up to date.</p>
<p>These things are not always joyful. But they are part of the craft of software development. Your team should be able to shoulder the challenge of turning your working system into a system they can continue to work with. It might not be work that is particularly glamorous but there is a deep satisfaction to be found in fixing something that is in bad shape. The pride of bringing craftsmanship to something ill-maintained or even dilapidated.</p>
<p>With tests and infrastructure in place it becomes more and more possible to <strong>tease apart the complicated parts</strong> of your system without breaking it. Perhaps you'll replace parts that are genuinely bad. Or maybe there should just be better separation of conerns. This is where joys of software design can come back in. Bit by bit you can fix the bad parts of a legacy system.</p>
<h3>Begin moving forward anew</h3>
<p>Maybe this is the time to fix the UI, or to break out a part into a new service. Or maybe bring a service that was extracted badly back into the fold. When your system is more maintainable and changes are less risky there is so many more useful things your team can do. By taking command of your legacy system your team have conquered it. Made it their own. And can actually work with it towards a continuous future.</p>
<h2>In closing</h2>
<p>Getting here isn't easy. There is usually a lot of skepticism to work past. There is a lot of work to do. But as a business it means not throwing out a working solution to your business problems in favor of a golden ideal that only exists in the mind of an enthusiastic developer. And in many cases that is the better choice.</p>
<h2>Need help?</h2>
<p>To get the ball rolling on this kind of project it is often worthwhile to bring someone in to ask some questions and investigate how this would work for your particular system. <strong>Someone that can meet your developers where they work</strong> and understand their concerns. This means a third party takes a look and you get someone who can keep an eye on the business goals to give a reasonable evaluation of your options and suggestions on what path to take going forward. If this is something you think that you or your business needs, feel free to get in touch at <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Why am I interested in Elixir?</title>
      <link>https://underjord.io/why-am-i-interested-in-elixir.html</link>
      <pubDate>Tue, 11 Jun 2019 17:30:00 +0000</pubDate>
      
      <guid>https://underjord.io/why-am-i-interested-in-elixir.html</guid>
      <description>The juicy bits  OTP &amp;amp; the BEAM Phoenix Presence Phoenix LiveView Nerves Scenic Rustler  I’ve had Elixir on the brain recently. And by recently I probably mean 2 years. In my defense I think it is fair to say it is blooming right now. I haven’t had much need of it, or opportunity for it, in my day-to-day of maintaining a Python legacy system, renewing another legacy or optimizing Elasticsearch.</description>
      <content:encoded><![CDATA[ 
<h2>The juicy bits</h2>
<ul>
<li><a href="#otp">OTP &amp; the BEAM</a></li>
<li><a href="#presence">Phoenix Presence</a></li>
<li><a href="#live-view">Phoenix LiveView</a></li>
<li><a href="#nerves">Nerves</a></li>
<li><a href="#scenic">Scenic</a></li>
<li><a href="#rustler">Rustler</a></li>
</ul>
<p>I’ve had Elixir on the brain recently. And by recently I probably mean 2 years. In my defense I think it is fair to say it is blooming right now. I haven’t had much need of it, or opportunity for it, in my day-to-day of maintaining a Python legacy system, renewing another legacy or optimizing Elasticsearch. So I’ve tried it with a few hobby projects I’ve spent time on and that was fun. But mostly I really just watched the community and what they did with a feeling of “Shiiiit, I want in on some of that!”. I'll primarily touch on BEAM, OTP, Phoenix Presence, Phoenix LiveView, Nerves, Scenic and Rustler.</p>
<p>Why? What makes it so interesting to me? It is just a programming language and I try not to be religious in these matters. I’ll try to outline what attracted me to Elixir, made me keep yearning for it and what I’ve been digging into recently as I’ve made time to actually get more comfortable with it.</p>
<h2 id="otp">OTP, The BEAM and the Erlang roots</h2>
<p>Elixir is built on the foundation laid by Erlang/OTP and it runs on the same VM. It is called BEAM or sometimes The BEAM. Why does the VM matter?</p>
<p>For a long time, doing web stuff primarily, I didn’t care much about the underlying runtime environment I was in. Then I was involved in a micro-service implementation and I did a lot of multi-threading. With Python. The story there is fairly well-known, there is a lock, the GIL. It is a solution to thread safety with some considerable, rather pragmatic, trade-offs. My experiences with this made me pay attention to concurrency, parallelism and core usage more. In addition, it sort of bothers me that a lot of desktop applications are bad at using multiple cores when it would make sense. Specifically considering the CPU core counts in modern machines. Anyway.</p>
<p>The BEAM is built to run in a distributed fashion, I believe this came before it was really used for multiple cores, it just happened to carry over particularly well. It has its own scheduler and a facility to spawn very light-weight processes that are not OS threads. Processes are isolated, they communicate through message passing. A bit more copying of data, a lot less problems. As I understand it a BEAM-based application launches the VM with (by default) a number of threads equal to the core count on your machine. Your processes will be scheduled across these threads.</p>
<p>The things that make the BEAM do really interesting things generally come from OTP. The Open Telecom Platform was designed to build telecom systems at Ericsson. For that purpose it has a high focus on resilience, recovery and some features I've never heard of elsewhere. Such as hot updates.</p>
<p>For resilience and recovery there is the idea of supervision trees. The idea is that you have a hierarchy of supervisor processes monitoring the parts of your application, the parts can also be supervisors with an underlying structure of processes and supervisors. So it branches out. Like a tree of supervision. A supervision tree, if you will.</p>
<p>When something goes wrong, if a process crashes, it is the supervisor of that part that is responsible for making sure we recover to a good state. If it can’t, or shouldn't, the supervisor itself can crash, passing the buck up the tree to some supervisor that may know more about how to recover.</p>
<p>This builds on the thought that errors should surface to the level that can handle them and it saves your simple code from having to handle every potential error it could suffer. At some point up the tree there is a correct place to handle the problem. And that's where it should be handled.</p>
<p>This has given birth to the saying "let it crash" which may be terrible in general marketing but might sound interesting to tech people. There is much nuance but I think supervision trees make you think about the architecture of an application in a healthy and layered way. You build the tree with a stable trunk and usually more ephemeral leaves. It also translates into some interesting thinking as we will see with Scenic.</p>
<p>Another rare feature that few need but that is incredibly rad is hot code updates. This is a way in which you can update an application while it is running by replacing which version of a module is running. I haven’t dug into it, seems rad. I guess it was annoying to bring down the telephone switches just to update the software. Not used very much in simpler systems I think, but fascinating.</p>
<p>The concurrency, robustness, resilience and distribution model that Erlang/OTP and the BEAM brings is making some things happen that I don’t believe quite exists in any other programming environment. Not from what I’ve seen at least. This makes for a very interesting runtime. Let's get into projects that leverage this.</p>
<h2 id="phoenix">Phoenix</h2>
<p>When Elixir showed up on my radar it was all about Phoenix and Ecto. Let's put a pin in Ecto for now. Phoenix is a web framework that is to Elixir as Rails was to Ruby. Or that’s the idea. Phoenix is not as batteries-included or auto-magical as Rails, it has a different focus. Phoenix definitely feels more explicit and less magical. I like what it provides but it doesn't seem to over-provide. I haven’t worked with it a ton. Seems to be a solid project and most languages need a strong web framework. Phoenix fits that bill.</p>
<p>I find it more interesting what it has allowed people to do. Chris McCord and the merry crew he conspires with has been doing some brain-warping work to make very compelling ideas land as optional libraries for Phoenix. The ones I have in mind are called Presence and LiveView.</p>
<h2 id="presence">Phoenix Presence</h2>
<p>This allows tracking of who is currently online, which different devices they are on and similar metadata to be provided to a distributed system in an eventually consistent way. It uses a particular <a href="https://en.m.wikipedia.org/wiki/Conflict-free_replicated_data_type" title="Conflict-free Replicated Data Types">CRDT</a> to achieve this which limits the amount of data that needs to be passed around while allowing reliable, distributed, reporting of client connections for the Phoenix Channels system (a layer on top of WebSockets).</p>
<p>The typical use-case is a chat service. Who is online, who went offline, who can I actually contact right now and are they on mobile or active on desktop? How do you distribute this data so that eventually everyone gets the memo that “John has disconnected”. Even if server #5 goes down, comes back up and actually missed the memo? Well, you use Phoenix Presence and you basically don’t have to worry about it.</p>
<p>This could have been done with other languages and runtimes I imagine. But with the BEAM we already have distribution. The system is already made to be run in more than one place. Having that in place is likely a large part of why this was done, if you want to build a presence functionality you need to make it work in a distributed fashion or you're not living up to the stated goals of the ecosystem.</p>
<h2 id="live-view">Phoenix LiveView</h2>
<p>Only recently made available to the public and not quite in a release state last time I checked. Fully working though. This is a clever and curious one. Chris wanted to save backend developers from needing to write javascript for every trivial little piece of interactivity. Most of us know the drill, we need some progressive enhancements, such as form validation. Suddenly we need frontend javascript, backend support for that and suddenly: complexity.</p>
<p>LiveView is a way of rendering a page, it functions as a basic page or form. But if you have JS active it will hook up the LiveView javascript with the LiveView backend endpoint, it talks over Phoenix Channels and voila. You have a lot of frontend-scripting from the backend.</p>
<p>I saw Chris <a href="https://m.youtube.com/watch?v=8xJzHq8ru0M">demo this live</a> at ElixirConf 2019. What a treat! I already knew what it was supposed to be and do. And I was blown away by the details, the cleverness, the minimal overhead.</p>
<h2>Ecto</h2>
<p>This is a mostly a database library that can also be used for other validation as we’ve come to see. It is independent from its SQL roots these days but generally you will encounter it in Phoenix for database operations. I haven’t more than scratched the surface in actual use. But it approaches the ideas of a database library in a novel way I haven’t quite seen before. I recommend reading up on it. I should too.</p>
<h2 id="nerves">Nerves</h2>
<p><a href="https://nerves-project.org">The Nerves Project</a></p>
<p>Oh this one I love. Take the resilience of the BEAM, put it on a minimal Linux image. Add your Elixir/Erlang application. Burn it as a firmware image to an SD card. Plug it into a computer, typically an embedded-level device, such as a Raspberry Pi and you are ready to have some fun.</p>
<p>Nerves was built to create connected devices (IoT, if you will) and try to provide more developer convenience along with a more resilient and less frustrating state of affairs. The BEAM is good at recovery and resilience. IoT is not famed for its durability. I would say Nerves definitely moves the needle in this area. Instead of running everything a Raspbian installation includes on your device you are starting from a minimal Linux and a bundle of Elixir code. The BEAM is basically your operating system, along with any parts of Linux you feel the need to add.</p>
<p>The project allows you to get started building more serious software projects, more durable solutions, with less work and a nicer workflow. The team is incredibly responsive on the Elixir Slack #nerves channel as well.</p>
<p>The support for Raspberry Pi-devices is great, all the common hobby-electronics-needs have libraries (I2C, GPIO, SPI, UART), networking in many shapes and forms. And importantly <strong>you can push firmware over SSH</strong>. I cannot stress enough how useful this is.</p>
<p>I’ve actually ported the Pimoroni Inky (an eInk display) library to Elixir, using Nerves heavily in the process. Some people have joined up to help get it presentable and useful. You can find our version of Inky <a href="https://github.com/pappersverk/inky">here</a>. I’ve also experimented with using Nerves as a sensor hub. An ongoing project, hampered mostly by my incompetent soldering.</p>
<p>Nerves is an incredibly useful project for making something beyond a toy or small Linux server on the Raspberry Pi. Though it does work great for toy projects too. It has been an absolutely fantastic resource for me to learn more about working with hardware, Linux and Buildroot. The people in the community channel are incredibly dedicated and helpful.</p>
<p>From what I’ve heard there is nothing quite like it out there for building embedded devices at single board computer size and up. There are also people that use it to push applications to the cloud as new firmware, which seems fun.</p>
<p>The Nerves team is also developing NervesHub which is a solution for managing a fleet of these devices with secure firmware updates, encryption hardware and that sort of thing. Typically difficult and expensive infrastructure to develop for your IoT devices. Seems very straight forward.</p>
<h2 id="scenic">Scenic</h2>
<a href="https://github.com/boydm/scenic">Scenic on GitHub</a>
<p>An independent, functional, UI framework. I say independent because it runs as close to basic OpenGL as it can on the given platform. I believe it only depends on glfw to get things going on desktop operating systems. It has a custom driver for the Raspberry Pi official touch-screen. Not QT, Electron or whatever else we do cross-platform UI with these days.</p>
<p>This is something I look for every now and then. Native or close-to-native UI that does not require a web-browser. Chromium is slow on a Pi. A web-browser is not always the best experience and it means an open port, it means a lot of software beyond what you need. Because the web browser is an immense platform compared to what most applications need, especially for an embedded situation.</p>
<p>Scenic is focused towards embedded devices and fixed-size screens. It is quite minimalist but well-featured and has an interesting design that I believe in.</p>
<p>Components of the UI are arranged as a supervision tree, a graph of components and primitives. So it uses the resilience features of OTP to create a UI where a specific component can crash without bringing the whole UI with it. It should recover, it might lose some data, but the problem is isolated.</p>
<p>There are a bunch of cool aspects to its design that allows things such as multiple viewports/displays and the potential to do wild things in the future. You’ll get the best overview from <a href="https://youtu.be/dgllQmf5DXk">Boyd’s own talk</a> (Code Beam Sto 2019, good conf).</p>
<p>It is still early days for Scenic. But you can use it right now. It is fast and friendly, and the plans Boyd has for it are fascinating. I’m saying early days, because there is so much more to come.</p>
<p>If you are interested in getting in on the ground floor, the #scenic channel on the Elixir Slack is a good place to drop in. Good people, let us know if you want to contribute somehow or just try running it and come in and share your joys and confusion.</p>
<h2 id="rustler">Rustler</h2>
<a href="https://github.com/rusterlium/rustler">Rustler on GitHub</a>
<p>Elixir and the BEAM have some limitations. There are moments when the FP paradigm is limiting or you want to call out to some native functionality that is not implemented in an Elixir-friendly way. There are a few escape-hatches for integrating with other languages and other systems built into the BEAM. The most powerful one is probably the NIF. Native Implemented Functions. Generally a way to write a function in C when you gotta go fast, NIFs are a bit risky in that they can actually cause problems with the BEAM. If you write yourself a memory issue in your NIF you’ve a memory issue in your BEAM and that nice supervision tree is unlikely to save you.</p>
<p>I’ve only barely touched Rust. But to my understanding it does away with a lot of the riskiest parts of systems languages while allowing most of the efficiencies. Sonny Scroggin held a talk that really piqued my interest on this at Code Beam Sto this year, sadly the video doesn’t seem to be release yet. Rustler is a project he and a cool Norwegian named Hans is working on that seems quite mature and makes NIFs using Rust work nicely and easily. Keeping your BEAM safe. Hopefully.</p>
<p>I have a thing where I’m likely to use it to bridge over into ALSA territory. We’ll see, looking forward to trying it out.</p>
<p>Hans is also doing some additionally mind-bending (he calls it <a href="https://github.com/eirproject" title="Erlang ecosystem compiler Infrastructure Project">Eir</a>) stuff concerning the intermediate representations of BEAM code and making that work with LLVM. He held a talk about that which boils down to what was two proof-of-concepts. Niffy, which allows him to smack a small annotation on a function in Elixir and have it be compiled to a NIF instead of running on the BEAM. As far as I understand it at least. The other was Whirl which would let BEAM languages run as WebAssembly. Which seems wild and fun to me.</p>
<h2>In summary</h2>
<p>The BEAM and OTP as a runtime and platform provides some very cool possibilities that I have not seen elsewhere. Cheap and easy both parallelism and concurrency. Beyond that you have distribution, which is important both for resilience and scalability.</p>
<p>From this base, especially with the appearance of Elixir, which is often considered a more approachable than Erlang, you see these interesting uses of the tech pop up. Nerves and Scenic just set my mind on fire with possibilities. It is a fascinating ecosystem that is springing up around Elixir. I don't know anything quite like it. That is not intended as hyperbole or a slight to your favored language. I am simply enthusiastic and possibly ignorant of the much cooler toys you have access to. Tell me!</p>
<p>Do you know of languages/runtimes that do actually provide similar tools? Or have you seen similarly singular and fantastic potential in the open source space? Or did I pass over your revolutionary thing in the Elixir space? I’d love to hear about it. Get in touch at <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Scenic - Getting started from scratch</title>
      <link>https://underjord.io/scenic-getting-started.html</link>
      <pubDate>Mon, 20 May 2019 18:00:00 +0000</pubDate>
      
      <guid>https://underjord.io/scenic-getting-started.html</guid>
      <description>This post covers setting up a Scenic project in the Elixir programming language. It briefly covers the default method but largely dives into adding Scenic to an existing project, which covers the different parts that Scenic requires to run.
The official approach Requirements  Elixir 18, OTP 21 (I recommend using asdf to install and use the right version) Scenic 0.10 (this is covered in the guide)  A brand new project Scenic, the OpenGL-based and very tasty UI framework for Elixir (&amp;amp; friends) has a very reasonable project generator.</description>
      <content:encoded><![CDATA[ 
<p>This post covers setting up a Scenic project in the Elixir programming language. It briefly covers the default method
  but largely dives into adding Scenic to an existing project, which covers the different parts that Scenic requires to
  run.</p>
<h2>The official approach</h2>
<h3>Requirements</h3>
<ul>
<li>Elixir 18, OTP 21 (I recommend <a href="https://hexdocs.pm/nerves/installation.html#all-platforms">using asdf to
      install and use the right version</a>)</li>
<li>Scenic 0.10 (this is covered in the guide)</li>
</ul>
<h3>A brand new project</h3>
<p><a href="https://github.com/boydm/scenic">Scenic</a>, the OpenGL-based and very tasty UI framework for Elixir (&amp;
  friends) has a very reasonable project generator. So if you feel like trying it out. That is a great starting point.
</p>


  <div class="code  shell "  data-file="/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix archive.install hex scenic_new
mix scenic.new my_app</code></pre></div>
  </div>


<p>With this. You will have the app, my_app and some instructions for how to download and install dependencies. For
  completeness:</p>


  <div class="code  shell "  data-file="/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">cd my_app
mix deps.get
mix scenic.run</code></pre></div>
  </div>


<p>Et voila, you have a very simple, slightly boring view and your terminal is reporting events as you move your
  cursor. All you could want. This is what you get from the official docs in the section <a href="https://hexdocs.pm/scenic/getting_started.html">Getting started</a>. Since I can't resist poking things and
  had some special requirements I did things differently. So the rest of the guide is more focused on doing it from
  scratch and getting to know the pieces required.</p>
<h2>Adding Scenic to an existing project</h2>
<h3>Using a basic project</h3>
<p>I had a project and it was a Nerves project using the nerves_init_gadget and I felt like I wanted to know whatever
  was brought into it and understand my dependencies and config a bit. So I started from a generated Nerves project
  which I'm pretty familiar with. Yes, because I keep starting them and never completing them, don't you start with
  me, it is my spare time, I waste it however I like. But for simplicity in this guide I will start from a minimal
  Elixir project.</p>


  <div class="code  shell "  data-file="/" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mix new my_elixir
cd my_elixir
mix test</code></pre></div>
  </div>


<p>Oh hell, that probably worked! So what do we do to get going with Scenic? A solid first step is to install it.
  Note: If you get lost at any point in this guide or simply cannot get a part to work you can check the code in <a href="https://github.com/lawik/scenic-guide-getting-started">this repository</a> which has the completed thing.</p>
<h3>Dependencies</h3>
<p>We add Scenic to our dependencies. We also want to add the driver library that allows it to work on the average
  desktop computer which is based on glfw.</p>
<p>In your project, edit mix.exs, add the two dependencies:</p>

  <div class="code  elixir "  data-file="mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir"><span style="color:#f92672">..</span>
<span style="color:#66d9ef">defp</span> deps <span style="color:#66d9ef">do</span>
    [
        {<span style="color:#e6db74">:scenic</span>, <span style="color:#e6db74">&#34;~&gt; 0.10&#34;</span>},
        {<span style="color:#e6db74">:scenic_driver_glfw</span>, <span style="color:#e6db74">&#34;~&gt; 0.10&#34;</span>, <span style="color:#e6db74">targets</span>: <span style="color:#e6db74">:host</span>},
    ]
<span style="color:#66d9ef">end</span>
<span style="color:#f92672">..</span></code></pre></div>
  </div>


<p>To install them after adding them, in the project, in your shell:</p>


  <div class="code  shell "  data-file="/my_elixir" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/my_elixir</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">    mix deps.get</code></pre></div>
  </div>


<p>This does absolutely nothing aside from making Scenic available. But we want things happening. First we need a
  bit of configuration for Scenic to know what to do.</p>
<h3>Configuration</h3>
<p>In your project find config/config.exs and open it for editing, make it match the following:</p>


  <div class="code  elixir "  data-file="config/config.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">config/config.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">    <span style="color:#f92672">use</span> <span style="color:#a6e22e">Mix.Config</span>

    <span style="color:#75715e"># Configure the main viewport for the Scenic application</span>
    config <span style="color:#e6db74">:my_elixir</span>, <span style="color:#e6db74">:viewport</span>, %{
      <span style="color:#e6db74">name</span>: <span style="color:#e6db74">:main_viewport</span>,
      <span style="color:#e6db74">size</span>: {<span style="color:#ae81ff">700</span>, <span style="color:#ae81ff">600</span>},
      <span style="color:#e6db74">default_scene</span>: {<span style="color:#a6e22e">MyElixir.Scene.Home</span>, <span style="color:#66d9ef">nil</span>},
      <span style="color:#e6db74">drivers</span>: [
        %{
          <span style="color:#e6db74">module</span>: <span style="color:#a6e22e">Scenic.Driver.Glfw</span>,
          <span style="color:#e6db74">name</span>: <span style="color:#e6db74">:glfw</span>,
          <span style="color:#e6db74">opts</span>: [<span style="color:#e6db74">resizeable</span>: <span style="color:#66d9ef">false</span>, <span style="color:#e6db74">title</span>: <span style="color:#e6db74">&#34;my_app&#34;</span>]
        }
      ]
    }</code></pre></div>
  </div>


<p>Lets go through it, shall we? <code>use Mix.Config</code> is just standard stuff. So we add a
  <code>config :my_elixir, :viewport</code> in which <code>:my_elixir</code> references our app and
  <code>:viewport</code> references that this configures a Scenic Viewport. Let's not dwell on what that is. With
  this we add a map with a bunch of keys. Let's run through them briefly.</p>
<ul>
<li>
<strong>name</strong><br/>
    Should make things prettier in observer. Supposedly optional.
  </li>
<li>
<strong>size</strong><br/>
    This is the resolution/size of the scenic window. This you can change for fun and profit.
  </li>
<li>
<strong>default_scene</strong><br/>
    It has to start somewhere, a scene in Scenic is a specific view in your application. This is what it shows by
    default.
  </li>
<li>
<strong>drivers</strong><br/>
    This comes with some sub-configuration which we needn't go into now. But the module key tells us what driver
    we are configuring. This would be different if you were targeting the official touch screen for Raspberry Pi
    for example. This driver was provided by the <code>scenic_driver_glfw</code> package we installed earlier.
  </li>
</ul>
<p>At this point, running will still do nothing. We have not added any running code.</p>
<h3>The supervision tree</h3>
<p>Let's start a supervision tree. Classic move to run anything in this day and age.</p>
<p>Open lib/my_elixir.ex in your editor and make it look like this:</p>


  <div class="code  elixir "  data-file="lib/my_elixir.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/my_elixir.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">    <span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyElixir</span> <span style="color:#66d9ef">do</span>
      <span style="color:#66d9ef">def</span> start(_type, _args) <span style="color:#66d9ef">do</span>
        <span style="color:#75715e"># load the viewport configuration from config</span>
        main_viewport_config <span style="color:#f92672">=</span> <span style="color:#a6e22e">Application</span><span style="color:#f92672">.</span>get_env(<span style="color:#e6db74">:my_elixir</span>, <span style="color:#e6db74">:viewport</span>)

        <span style="color:#75715e"># start the application with the viewport</span>
        children <span style="color:#f92672">=</span> [
          {<span style="color:#a6e22e">Scenic</span>, <span style="color:#e6db74">viewports</span>: [main_viewport_config]}
        ]

        <span style="color:#a6e22e">Supervisor</span><span style="color:#f92672">.</span>start_link(children, <span style="color:#e6db74">strategy</span>: <span style="color:#e6db74">:one_for_one</span>)
      <span style="color:#66d9ef">end</span>
    <span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>Still doesn't do a single thing. Why? Well, we aren't loading the module. So we still are not running any code.
  Go back to mix.exs and fix this:</p>


  <div class="code  elixir "  data-file="mix.exs" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">mix.exs</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">    <span style="color:#f92672">..</span>
    <span style="color:#66d9ef">def</span> application <span style="color:#66d9ef">do</span>
        [
          <span style="color:#e6db74">mod</span>: {<span style="color:#a6e22e">MyElixir</span>, []},
          <span style="color:#e6db74">extra_applications</span>: [<span style="color:#e6db74">:logger</span>]
        ]
    <span style="color:#66d9ef">end</span>
  <span style="color:#f92672">..</span></code></pre></div>
  </div>


<p>Our app has a mod now! Or rather, our application knows which module we want to start at. <strong>I do not
    recommend starting it now.</strong> We have not built the default scene yet. So things will break in a rather
  annoying way where the supervisor will start a window over and over again because it keeps dying until you
  murder your app. So lets avoid that shall we?</p>
<h3>Creating our first scene</h3>
<p>Create a folder named scenes in lib and add a file named home.ex in that scenes folder. Open it in your editor
  and do something like this:</p>


  <div class="code  elixir "  data-file="lib/scenes/home.ex" >
    <div class="meta">
      <span class="language">elixir</span>
      <span class="filename">lib/scenes/home.ex</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-elixir" data-lang="elixir">    <span style="color:#66d9ef">defmodule</span> <span style="color:#a6e22e">MyElixir.Scene.Home</span> <span style="color:#66d9ef">do</span>
      <span style="color:#f92672">use</span> <span style="color:#a6e22e">Scenic.Scene</span>

      <span style="color:#f92672">alias</span> <span style="color:#a6e22e">Scenic.Graph</span>
      <span style="color:#75715e"># alias Scenic.ViewPort</span>

      <span style="color:#f92672">import</span> <span style="color:#a6e22e">Scenic.Components</span>
      <span style="color:#75715e"># import Scenic.Primitives</span>

      <span style="color:#a6e22e">@text_size</span> <span style="color:#ae81ff">24</span>

      <span style="color:#66d9ef">def</span> init(_, _opts) <span style="color:#66d9ef">do</span>
        graph <span style="color:#f92672">=</span>
          <span style="color:#a6e22e">Graph</span><span style="color:#f92672">.</span>build(<span style="color:#e6db74">font</span>: <span style="color:#e6db74">:roboto</span>, <span style="color:#e6db74">font_size</span>: <span style="color:#a6e22e">@text_size</span>)
          <span style="color:#f92672">|&gt;</span> button(<span style="color:#e6db74">&#34;Click me&#34;</span>, <span style="color:#e6db74">id</span>: <span style="color:#e6db74">:sample_button_1</span>, <span style="color:#e6db74">t</span>: {<span style="color:#ae81ff">32</span>, <span style="color:#ae81ff">32</span>})

        {<span style="color:#e6db74">:ok</span>, graph, <span style="color:#e6db74">push</span>: graph}
      <span style="color:#66d9ef">end</span>

      <span style="color:#66d9ef">def</span> filter_event({<span style="color:#e6db74">:click</span>, <span style="color:#e6db74">:sample_button_1</span>}, _from, state) <span style="color:#66d9ef">do</span>
        new_text <span style="color:#f92672">=</span> <span style="color:#a6e22e">DateTime</span><span style="color:#f92672">.</span>to_iso8601(<span style="color:#a6e22e">DateTime</span><span style="color:#f92672">.</span>utc_now())

        state <span style="color:#f92672">=</span>
          state
          <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">Graph</span><span style="color:#f92672">.</span>modify(<span style="color:#e6db74">:sample_button_1</span>, <span style="color:#f92672">&amp;</span>amp;button(<span style="color:#f92672">&amp;</span>amp;<span style="color:#ae81ff">1</span>, new_text))

        {<span style="color:#e6db74">:halt</span>, state, <span style="color:#e6db74">push</span>: state}
      <span style="color:#66d9ef">end</span>
    <span style="color:#66d9ef">end</span></code></pre></div>
  </div>


<p>This has it all, a scene, a graph, a component and even some UI event which triggers graph modification. Oh
  yeah. So the general ideas are covered better by <a href="https://hexdocs.pm/scenic/overview_general.html#mental-model">the docs</a>. But basically, init sets up
  a graph that contains a component, specifically a button. The graph is "pushed" as init returns. And the scene
  is rendered for you in beautiful free-range GL. For more information on the ideas in Scenic I'd recommend <a href="https://www.youtube.com/watch?v=1QNxLNMq3Uw">Boyd's talk</a> at ElixirConf 2018 where he covers most of
  what Scenic does and why it is cool.</p>
<p>Well, time to run it and see what we've made.</p>


  <div class="code  shell "  data-file="/my_elixir" >
    <div class="meta">
      <span class="language">shell</span>
      <span class="filename">/my_elixir</span>
    </div>
    <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">  iex -S mix</code></pre></div>
  </div>


<p>You should see a window with a button that updates to show the timestamp when you press it. Nothing fancy. But
  a start. And that is where I will end this. If you want to review the resulting code it is found <a href="https://github.com/lawik/scenic-guide-getting-started">here</a>.</p>
<p>If you have any inquiries about this or my business, get in touch at <a href="mailto:lars@underjord.io">lars@underjord.io</a>.</p> ]]></content:encoded>
    </item>
    
  </channel>
</rss>