<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Mike on Medium]]></title>
        <description><![CDATA[Stories by Mike on Medium]]></description>
        <link>https://medium.com/@mikec123?source=rss-dde4c0200fe9------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*dmbNkD5D-u45r44go_cf0g.png</url>
            <title>Stories by Mike on Medium</title>
            <link>https://medium.com/@mikec123?source=rss-dde4c0200fe9------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 05 Nov 2026 04:25:52 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@mikec123/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Golang: recursive types v2]]></title>
            <link>https://medium.com/@mikec123/golang-recursive-types-v2-a21d03b1686a?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/a21d03b1686a</guid>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Tue, 03 Nov 2026 07:06:09 GMT</pubDate>
            <atom:updated>2026-11-03T07:06:09.005Z</atom:updated>
            <content:encoded><![CDATA[<p>In the previous post, I wrote about recursive types: maps, slices, structs, and channels. Structs, maps, and slices show up in real data. <br>A recursive channel is mostly an interview topic.</p><p>Let’s continue.</p><p>Did you know this signature is valid?</p><pre>type Func func() Func</pre><p>A function type that refers to itself.</p><p>How do you use that in real code? Is it useful?</p><p>Yes, if you don’t like if-else spaghetti.</p><p>In the previous post, I mentioned PBEM (Play-by-Email) games <br>and noticed that each step had no World. Let’s fix that.</p><p>First, the world and a step over it:</p><pre>type World struct {<br>  TurnID     int<br>  HasWinner  bool<br>  NextPlayer string<br>}<br><br>type StepFn func(*World) StepFn</pre><p>Then, a few steps:</p><pre>// waiting for the email move<br>func waitForEmailStep(w *World) StepFn {<br>  fmt.Printf(&quot;[Turn %d]: Waiting for %s&#39;s email move...\n&quot;, w.TurnID, w.NextPlayer)<br>  return processMoveStep<br>}<br><br>func processMoveStep(w *World) StepFn {<br>  fmt.Println(&quot;[Engine]: Calculating ... etc.&quot;)<br><br>  if w.TurnID &gt;= 3 {<br>    w.HasWinner = true<br>    return endGameStep<br>  }<br><br>  w.TurnID++<br>  if w.NextPlayer == &quot;Player #1&quot; {<br>    w.NextPlayer = &quot;Player #2&quot;<br>  } else {<br>    w.NextPlayer = &quot;Player #1&quot;<br>  }<br>  return sendNotificationsStep<br>}<br><br>// jump back to waiting<br>func sendNotificationsStep(w *World) StepFn {<br>  fmt.Printf(&quot;[Email]: Sending new save game file to %s ...\n&quot;, w.NextPlayer)<br>  return waitForEmailStep<br>}<br><br>// base case: nil breaks the loop<br>func endGameStep(w *World) StepFn {<br>  fmt.Println(&quot;[Game Over]: FINAL RESULTS.&quot;)<br>  return nil<br>}</pre><p>Nothing magic here. These are just the actions the game needs in order to progress.</p><p>And the driver:</p><pre>func main() {<br>  world := &amp;World{TurnID: 1, NextPlayer: &quot;Player #1&quot;}<br><br>  state := waitForEmailStep<br><br>  for state != nil {<br>    state = state(world)<br>  }<br>}</pre><p>The useful part is isolation, and how small the control flow stays. <br>There is no package-level state. The only shared state is the World itself. <br>You can easily recombine the functions or insert a new step. New game release!</p><p>Full code is <a href="https://go.dev/play/p/IbLj1CvoT9m">here</a>.</p><p>This is a more or less real example. <br>For the same idea in the standard library, <br>see <a href="https://github.com/golang/go/blob/master/src/text/template/parse/lex.go#L110">text/template/parse/lex.go</a> where Rob Pike uses type stateFn <em>func(*lexer) stateFn</em> to build a clean state machine for the template lexer.</p><p>Happy coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a21d03b1686a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Golang: recursive types]]></title>
            <link>https://medium.com/@mikec123/golang-recursive-types-7753ec066948?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/7753ec066948</guid>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[golang-development]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Mon, 26 Oct 2026 08:37:16 GMT</pubDate>
            <atom:updated>2026-10-26T08:37:16.437Z</atom:updated>
            <content:encoded><![CDATA[<p>Did you know that Go has recursive types?</p><p>Recursive types are also a fit for nested JSON, but the type needs a place for a leaf value. This works:</p><p>A recursive type is a type that refers to itself. That is useful for data structures:</p><pre>type Node struct {<br>    Next *Node<br>}</pre><p>This shape is a list. A tree is the same idea with more than one link, for example Left and Right.</p><pre>type Node struct {<br>    Value any<br>    Kids  []Node<br>}</pre><p>A map or a slice can be recursive too:</p><pre>type M map[string]M<br>type S []S</pre><p>Those compile, and every element is another map or slice.</p><p>There is no field for a string or a number, so they are not enough to parse real JSON on their own.</p><p>The same recursion is legal for a channel:</p><pre>type Ch chan Ch</pre><p>A value of type Ch carries only another Ch.</p><p>I can see how to use the map and slice forms. I still do not see a use for the channel.</p><p>The only ideas I can come up with are actors and games.</p><p>Here is my toy example:</p><pre>package main<br><br>import &quot;fmt&quot;<br><br>type Step chan Step<br><br>const maxJumps = 3<br><br>func play(self Step, next Step, done Step) {<br> for range maxJumps {<br>  self &lt;- self<br> }<br><br> next &lt;- self<br> done &lt;- next<br>}<br><br>func main() {<br> step := make(Step)<br> next := make(Step)<br> done := make(Step)<br><br> named := map[Step]string{step: &quot;step&quot;, next: &quot;next&quot;, done: &quot;done&quot;}<br><br> go play(step, next, done)<br><br> for {<br>  select {<br>  case from := &lt;-step:<br>   fmt.Println(named[from], &quot;-&gt;&quot;, &quot;step&quot;)<br>  case from := &lt;-next:<br>   fmt.Println(named[from], &quot;-&gt;&quot;, &quot;next&quot;)<br>  case from := &lt;-done:<br>   fmt.Println(named[from], &quot;-&gt;&quot;, &quot;done&quot;)<br>   return<br>  }<br> }<br>}</pre><p><a href="https://go.dev/play/p/9nYSHEpSWQk">Output</a>:</p><pre>step -&gt; step<br>step -&gt; step<br>step -&gt; step<br>step -&gt; next<br>next -&gt; done</pre><p>Each channel is a stone. Sending on a channel means landing on that stone, and the value sent is the stone you jumped from.</p><p>After a few jumps on the same stone (3 here), the player jumps to next and then to done.</p><p>I could use this in a PBEM game (have you ever played one?) or for a small FSM. One caveat: a PBEM turn carries the whole state of the world, and here there is no world at all, only the addresses of stones.</p><p>Still, I don’t think it’s for everyday programming: the same thing can be written in a more ordinary style.</p><p>Recursive maps, slices, and structs describe data. A recursive channel describes communication: what you pass is not data but an address to talk to next.</p><p>This is the core idea of the π-calculus, where processes send channel names over channels.</p><p>P.S. It’s also a great topic to bring up in interviews.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7753ec066948" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A tiny Oh My Zsh plugin that copies the MR link on push]]></title>
            <link>https://medium.com/@mikec123/a-tiny-oh-my-zsh-plugin-that-copies-the-mr-link-on-push-60ce895cb213?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/60ce895cb213</guid>
            <category><![CDATA[zsh]]></category>
            <category><![CDATA[oh-my-zsh]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Thu, 01 Oct 2026 13:40:00 GMT</pubDate>
            <atom:updated>2026-10-01T13:40:00.293Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi there.</p><p>I didn’t plan to write here, but today I learned something xD At least I remembered bash!</p><p>More than 12 years ago I wrote some bash scripts for deployments, checks, and other little things.</p><p>Look at the tricks I picked up: `”$@”` gets the arguments of a function. You pass something in, and `$@` holds all of it. You can also take arguments one by one with `$1`, `$2`. You’ve seen that syntax somewhere, right? ;)</p><p>Also `head -n X`. One of the first commands you learn on Linux, right? (`cd` is probably the first.)</p><p>But the coolest part is copying to the clipboard. It looks like real magic. What do we have here? `wl-copy`, `xclip`, `xsel`. Oh My Zsh already has an abstraction for that: `clipcopy`. Use it!</p><p>And `grep`! I haven’t written a regular expression since 2018, or earlier. Really.</p><p>So I joined copy + grep + head + git + zsh. Did you know that git writes to stderr? (`2&gt;&amp;1` sends stderr to stdout.)</p><p>You probably think I made it up.</p><p>I wrote a function that copies the MR link when I push. So instead of copying it with the mouse, I just open the browser and hit `Ctrl+V`. Voylla.</p><p>This post has more text than the function itself :)<br>If you use <a href="https://ohmyz.sh">Oh My Zsh</a>, install it and use it. It’s free!</p><p>Oh, and also: not all Git hosts send the link. Keep that in mind!</p><p><a href="https://github.com/fntz/gpush_and_copy.plugin.zsh">Use it.</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=60ce895cb213" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Outbox Pattern: State Machines, SKIP LOCKED, and Kafka Keys]]></title>
            <link>https://medium.com/@mikec123/outbox-pattern-state-machines-skip-locked-and-kafka-keys-a1e2d669612c?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/a1e2d669612c</guid>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[outbox-pattern]]></category>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Wed, 19 Aug 2026 08:20:33 GMT</pubDate>
            <atom:updated>2026-08-19T08:20:33.784Z</atom:updated>
            <content:encoded><![CDATA[<p>In a world of events, how do we guarantee delivery of messages?</p><p>One everyday, battle-tested pattern is the outbox.</p><h3>Why do we need it?</h3><p>Because a DB transaction does not cover producing the message to the bus.</p><p>How to do it? <em>Decompose the insert from the produce.</em></p><p>What does that mean?</p><p>First of all we need to store our outbox messages in a separate event table. I won’t write a full</p><p>table DDL here, but in my practice the important fields are</p><p>- <strong>obj_id</strong> — ref to the original object (convenient for searching if you want to look things up or have an admin panel/backoffice)<br>- <strong>topic</strong> — where the event needs to be sent<br>- <strong>payload</strong> — bytes/text, it depends on your taste<br>- <strong>status</strong> — here we want to see pending, processing, published, or error</p><p>Of course in real life there are a lot of extra fields for <strong>metadata</strong>: headers, correlation_id, retry_count, error_text, and so on.</p><p>Here I see one mistake: do not make obj_id UNIQUE in the outbox. Want only one order? UNIQUE on orders, not on outbox — they share the same transaction anyway. And one object often means many events, so UNIQUE on <strong>obj_id</strong> is the wrong constraint even when the main row is unique.</p><p>Another very important part: do the business work in the same transaction as storing the outbox event:</p><pre>// good<br>begin_tx<br>store_order<br>store_outbox_event<br>commit_tx<br><br>// bad<br>store_order<br>store_outbox_event</pre><p>In the `bad` part we lose the A — atomicity — from <a href="https://en.wikipedia.org/wiki/ACID">ACID</a>, because the two writes are not one transaction. Otherwise you know — that does not work at all.</p><p>Then the main and complicated part: how to read new rows for parallel processing, without a long-running transaction.</p><p>So the base code is something like this:</p><pre>begin_tx<br>rows = `select * from outbox_events where status = &#39;pending&#39; order by created_at ASC limit :limit`<br><br>for row in rows {<br>    err = send_to_bus(row)<br>    if err != nil {<br>        mark_as_error(row)<br>    } else {<br>        mark_as_published(row)<br>    }<br>}<br><br>commit_tx</pre><p>See the troubles here?</p><p>I count them:</p><p>- one long-running transaction with bus-involved code (Kafka, Rabbit, …); in case of a network glitch you need to wait 2–5 seconds to complete all events<br>- this SELECT has no lock: two workers fetch the same pending rows and both publish — that is duplicate delivery, not parallelism</p><h3>How to solve it?</h3><p>I prefer to have state-machine processing in three steps.</p><p>Step 1 — claim. One short transaction: lock a batch, mark processing, commit. No bus I/O inside.</p><pre>begin_tx<br><br>rows = update outbox_events SET status = &#39;processing&#39;, updated_at = NOW()<br>where id in (<br>    select id from outbox_events<br>        where status = &#39;pending&#39;<br>        order by created_at ASC limit :limit<br>        FOR UPDATE SKIP LOCKED -- &lt;----------- see here<br>    )<br>returning *<br><br>commit_tx</pre><p><em>`FOR UPDATE SKIP LOCKED</em>` prevents two workers from fetching the same IDs at the same time.</p><p>Step 2 — send. Outside any DB transaction.</p><p>Step 3 — final status. A separate micro-transaction per row. The DB still has to open a short TX for that UPDATE; it is just not wrapped around the bus send.</p><pre>// after commit_tx of step 1<br>for row in rows {<br>    // step 2 - no DB transaction<br>    err = send_to_bus(row)  // Kafka key = obj_id<br><br>    // step 3 - micro-transaction, status only<br>    if err != nil {<br>        db.execute(&quot;UPDATE outbox_events SET status = &#39;error&#39; WHERE id = :id&quot;)<br>    } else {<br>        db.execute(&quot;UPDATE outbox_events SET status = &#39;published&#39; WHERE id = :id&quot;)<br> }</pre><p>One more nuance from “do not make obj_id UNIQUE”: one order can emit <em>CREATED</em>, then a second later <em>PAID</em>. Worker 1 SKIP LOCKED a batch that has <em>CREATED</em>; worker 2 in the same millisecond takes the next batch with <em>PAID</em> for the same order. They send in parallel, and because of the network <em>PAID</em> can land in Kafka before <em>CREATED</em>.</p><p>Fix: when producing, use obj_id (order_id) as the Kafka message key. Kafka then puts both messages on the same partition and keeps order inside that partition even if they arrived with a microsecond skew.</p><p>If a worker dies, a scheduler resets status back to pending. One catch: pick the interval carefully — if the main sender runs every 1 second, run the scheduler on something like 30 seconds, and only reset rows whose updated_at is old enough that you do not overlap a live worker.</p><p>The same for error: increment retry_count and retry after some interval.</p><p>Here I highlight the issue I want to solve with this code: the long-running transaction, nothing more.</p><p>SKIP LOCKED also stops two workers from grabbing the same row at the same time — that duplicate we do want to kill (network load, event races).</p><p>It does not make delivery exactly-once. If the worker dies after send_to_bus and before published, the scheduler retries and the bus sees the message twice. Inbox is still needed. Unique/integrity stays on the main table, not on this read path.</p><p>Using the outbox is not a hard pattern, but it is useful in a world of events.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a1e2d669612c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[omhs 0.1.0 — back after ~5 years.]]></title>
            <link>https://medium.com/@mikec123/omhs-0-1-0-back-after-5-years-c17816b104a5?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/c17816b104a5</guid>
            <category><![CDATA[scala-3]]></category>
            <category><![CDATA[scala]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Fri, 14 Aug 2026 09:06:21 GMT</pubDate>
            <atom:updated>2026-08-14T09:06:21.096Z</atom:updated>
            <content:encoded><![CDATA[<p>A long time ago I wrote a tiny <a href="https://netty.io">Netty</a> wrapper because I didn’t want to pull heavy dependencies into a project — just <a href="https://sinatrarb.com">Sinatra</a>-like routing. And now, five years later, it’s on Scala 3.</p><p>I tried before and dropped it (life got busy). This time it’s done.</p><p>What’s in this release:</p><ul><li>Scala 3 alongside 2.13 — same DSL</li><li>Netty 4.1.137, refreshed play-json / circe / jsoniter modules</li><li>Maven Central (omhs-dsl + JSON support)</li><li>CI on both Scala versions</li></ul><p>The hard part was the routing macros. On Scala 2, ~&gt; expanded into an anonymous ExecutableRule subclass. Scala 3 quoted macros don’t generate that cleanly, so ExecutableRule now takes a runner: List[ParamDef[_]] =&gt; AsyncResult, and the macro only builds that function. Same DSL, different codegen.</p><p>Artefacts are <a href="https://central.sonatype.com/namespace/com.github.fntz">here</a>.</p><p>How to use:</p><pre>// scala<br>import com.github.fntz.omhs._<br>import com.github.fntz.omhs.playjson.JsonSupport<br>import RoutingDSL._<br>import play.api.libs.json.{Format, Json}<br>import scala.concurrent.Future<br><br>case class Person(id: Int, name: String)<br><br>@main def main(): Unit = {<br>  implicit val personJson: Format[Person] = Json.format[Person]<br>  implicit val personBodyReader: BodyReader[Person] = JsonSupport.reader[Person]()<br>  implicit val personBodyWriter: BodyWriter[Person] = JsonSupport.writer[Person]()<br><br>  import AsyncResult.Implicits._<br>  import scala.concurrent.ExecutionContext.Implicits.global<br><br>  val simpleGetRoute = get(&quot;ping&quot;) ~&gt; { () =&gt;<br>    &quot;pong&quot;<br>  }<br><br>  val getWithParamsRoute = get(&quot;bar&quot; / string) ~&gt; { (x: String) =&gt;<br>    s&quot;given: $x&quot;<br>  }<br><br>  val postPersonRoute = post(&quot;person&quot; &lt;&lt;&lt; body[Person]) ~&gt; { (p: Person) =&gt;<br>    val x = p.copy(name = &quot;new&quot;)<br>    Future(x)<br>  }<br><br>  val route1 = new Route().addRules(<br>    simpleGetRoute,<br>    getWithParamsRoute,<br>    postPersonRoute<br>  )<br><br>  val server = OMHSServer.init(9000, route1.toHandler(Setup.default), None)<br><br>  server.start()<br><br>  sys.addShutdownHook(server.stop())<br>  new java.util.concurrent.CountDownLatch(1).await()<br>}</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c17816b104a5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real iter example]]></title>
            <link>https://medium.com/@mikec123/real-iter-example-785c90e9e234?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/785c90e9e234</guid>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[golang-tutorial]]></category>
            <category><![CDATA[kafka]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Fri, 31 Jul 2026 08:50:13 GMT</pubDate>
            <atom:updated>2026-07-31T08:50:13.436Z</atom:updated>
            <content:encoded><![CDATA[<p>Some time ago, I wrote a post about the `iter` package.</p><p>All my examples were related to slices, without a real application in production code.</p><p>Let’s fix it!</p><p>Do you use Kafka or another message broker? Almost everyone does now.</p><p>So, let’s look at an example from <a href="https://github.com/confluentinc/confluent-kafka-go">confluent-lib</a>:</p><pre>consumer, err := kafka.NewConsumer(ToConsumerConfig(cfg, &quot;earliest&quot;))<br>if err != nil {<br>    log.Fatalf(&quot;Failed to create consumer: %v&quot;, err)<br>}<br><br>defer consumer.Close()<br><br>err = consumer.SubscribeTopics([]string{topic}, nil)<br>if err != nil {<br>    log.Fatalf(&quot;Failed to subscribe to topics: %v&quot;, err)<br>}<br><br>for {<br>    msg, err := consumer.ReadMessage(1 * time.Second)<br>    if err == nil {<br>        fmt.Println(&quot;Received message: &quot;, string(msg.Value))<br>    } else if !err.(kafka.Error).IsTimeout() { <br>        log.Fatalf(&quot;Consumer error: %v&quot;, err)<br>    }<br>}</pre><p>What do we see here? Standard setup around the topic and then an infinite loop</p><p>(in real systems, of course, it is usually controlled by shutdown logic).</p><p>If we transform this example into an iter-style version:</p><pre>for msg, readErr := range consumeMessagesIter(consumer, 1*time.Second) {<br>  if readErr != nil {<br>   log.Fatalf(&quot;Consumer error: %v&quot;, readErr)<br>  }<br>  fmt.Println(&quot;Received message: &quot;, string(msg.Value))<br> }<br><br><br>func consumeMessagesIter(consumer *kafka.Consumer, timeout time.Duration) iter.Seq2[*kafka.Message, error] {<br>  return func(yield func(*kafka.Message, error) bool) {<br>   for {<br>     msg, err := consumer.ReadMessage(timeout)<br>     if err != nil {<br>       var kafkaErr kafka.Error<br>       if errors.As(err, &amp;kafkaErr) &amp;&amp; kafkaErr.IsTimeout() {<br>         continue<br>       }<br>       if !yield(nil, err) {<br>         return<br>       }<br>       return<br>     }<br><br>     if !yield(msg, nil) {<br>      return<br>     }<br>    }<br>  }<br>}</pre><p>What is the difference? In this Kafka case, there is no memory win from iter, because the original version is already streaming and does not build large intermediate slices either.</p><p>So one classic <strong>iter</strong> advantage does not apply here. Still, iter helps with composition, reuse, and cleaner separation between reading and processing.</p><p>For example, composition here means we can build a pipeline like:</p><p>`consumeMessagesIter -&gt; parse JSON -&gt; filter by event type -&gt; handle business logic`</p><p>Each step can be a small reusable function, and we can chain them without rewriting the Kafka read loop every time.</p><p>Full example <a href="https://gist.github.com/fntz/c205aadc81a1f048050adc3732083551">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=785c90e9e234" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Go 1.27 is coming soon, and it is bigger than it looks :)]]></title>
            <link>https://medium.com/@mikec123/go-1-27-is-coming-soon-and-it-is-bigger-than-it-looks-07aff2a723b0?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/07aff2a723b0</guid>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Thu, 04 Jun 2026 15:22:04 GMT</pubDate>
            <atom:updated>2026-06-04T15:22:04.651Z</atom:updated>
            <content:encoded><![CDATA[<p>The main change for me is generic methods. If you want to build a monad library, though, this is still not enough :)</p><h3>1. What we have now</h3><pre>// Generic function<br>func foo[T any](x T) {<br> fmt.Println(x)<br>}<br><br>// Generic type with a method that uses the type parameter<br>type Printable[T any] struct{}<br><br>func (f Printable[T]) Print(x T) {<br>   fmt.Println(x)<br>}</pre><h3>2. What we will have in Go 1.27</h3><pre><br>type Printable struct{}<br><br>func (f Printable) Print[T any](x T) {<br>   fmt.Println(x)<br>}</pre><h3>3. What we still want</h3><pre><br>// Not valid in Go today<br>type Printable[T any] interface {<br>   Print[R any](x T) R<br>}</pre><p>In the first example, even if the struct itself does not need `T`, you still have to put `T` on the type declaration to make method APIs generic.</p><p>In the second example (Go 1.27), you can keep the struct non-generic and put type parameters directly on the method.</p><p>The third example is what I still want: generic interface methods.</p><h3>Why is this still hard?</h3><p>The Go proposal says generic concrete methods are manageable, but generic interface methods are much harder to implement efficiently because interface dispatch is dynamic, and the compiler/runtime cannot know in advance which method instantiations will be needed.</p><h3>So is Go 1.27 bad, or does it add nothing?</h3><p>Of course not. It is a useful step forward, especially for cleaner chained APIs.</p><pre>Example:<br>type Option[T any] struct {<br> v  T<br> ok bool<br>}<br><br>func (o Option[T]) Map[R any](f func(T) R) Option[R] {<br> if !o.ok {<br>  return Option[R]{}<br> }<br> return Option[R]{v: f(o.v), ok: true}<br>}<br><br>// Signature here is simplified just to show the chaining idea.<br>func (o Option[T]) Transform[R any](f func(T) R) Option[R] {<br> if !o.ok {<br>  return Option[R]{}<br> }<br> return Option[R]{v: f(o.v), ok: true}<br>}<br><br>// chaining:<br>// out := someInt.<br>//  Map(toString).<br>//  Transform(SomeFunc)<br>//<br>// current style (list :))<br>// out := Transform(Map(someInt, toString), SomeFunc)</pre><p>What is still missing for full monad-style abstractions:</p><pre>// Not valid today:<br>// generic method inside an interface.<br>type Functor[T any] interface {<br>   Map[R any](func(T) R) Functor[R]<br>}</pre><p><a href="https://github.com/golang/go/issues/77273">Read spec here</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=07aff2a723b0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Microservices building blocks]]></title>
            <link>https://medium.com/@mikec123/microservices-building-blocks-3cad384e9312?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/3cad384e9312</guid>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[microservices]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Thu, 23 Apr 2026 08:47:00 GMT</pubDate>
            <atom:updated>2026-04-23T08:47:00.822Z</atom:updated>
            <content:encoded><![CDATA[<p>Most teams don’t struggle with microservices themselves — they struggle with everything around them.<br>I’m not going to argue whether microservices are good or bad. Instead, let’s talk about the building blocks they rely on.</p><p><strong>Microservices don’t live in isolation.</strong> Every service:<br>- talks to a database<br>- publishes or consumes messages (queues, topics)<br>- depends on infrastructure: health checks, lifecycle management, metrics, tracing</p><p>And these integrations are rarely unique. Across services, you keep solving the same problems:<br>- DB and queue clients<br>- configuration and bootstrap<br>- health endpoints<br>- logging, metrics, tracing</p><p>That’s why teams inevitably end up with shared building blocks.</p><p>So what are the options?</p><p>In practice, it’s usually a mix of:<br>- copy-paste<br>- service templates (boilerplate)<br>- shared/platform libraries</p><p>Copy-paste works — until it doesn’t. Rolling out changes across dozens of services quickly turns into a mess.</p><p>That’s why I usually lean toward a platform library.</p><p>A typical structure might look like (here golang):</p><pre>builder/  common/   consts/  health/  logging/  redis/     tracing/<br>bus/      configs/  grpc/    kafka/   metrics/  security/</pre><p>This approach makes it easier to:<br>- reuse integrations<br>- test things once<br>- evolve infrastructure consistently</p><p>It also pairs well with service templates, so developers can focus on business logic instead of wiring.</p><p>But there’s a trade-off.</p><p>Without strict versioning and compatibility rules, a platform library easily turns into a “<strong>god library</strong>” — tightly coupled, hard to upgrade, and slowing everyone down.</p><h4>Rule of thumb:</h4><p>If you have more than a handful of services, start with shared building blocks — but treat them like a product, not a dump of utilities.</p><p>P.S. Multi-language environments break this model in interesting ways — but that’s a separate headache.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3cad384e9312" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Go iter pipelines: fast… until they escape to heap]]></title>
            <link>https://medium.com/@mikec123/go-iter-pipelines-fast-until-they-escape-to-heap-eb77a84f2d6b?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/eb77a84f2d6b</guid>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[benchmark]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Tue, 07 Apr 2026 09:25:47 GMT</pubDate>
            <atom:updated>2026-04-07T09:25:47.411Z</atom:updated>
            <content:encoded><![CDATA[<p>I wrote about <strong>iter</strong>, but what about reality?</p><p>The <strong>iter</strong> package gives us a DSL-like style: we can build a small, readable pipeline instead of combining a few <strong>for</strong> loops. It can also be memory-friendly because it streams values instead of materializing intermediate slices.</p><p>Let’s benchmark something closer to a “production-ish” scenario. (I also don’t want to compare a single one-liner.)</p><p>We will use these small helper functions:</p><pre>func square(x int) int {<br>  return x * x<br>}<br><br>func filter(x int) bool {<br>  return x%2 == 0<br>}<br><br>func sum(acc int, x int) int {<br>  return acc + x<br>}</pre><p>And these iterator helpers: <strong>Generate</strong>, <strong>Map</strong>, <strong>Filter</strong>, <strong>Reduce</strong>.</p><p>The flow is:<br>- generate numbers from `0..max-1`<br>- square each number<br>- keep only values that pass `filter` (in this case: keep even values)<br>- reduce (sum) them into a single number</p><pre>func Pipeline(max int) int {<br>  result := Reduce(<br>    Filter(<br>      Map(<br>        Generate(max), square),<br>      filter), 0,<br>    sum)<br><br>  return result<br>}</pre><p>And compare it with a direct loop:</p><pre>func ForLoopPipeline(max int) int {<br>  acc := 0<br>  for x := range max {<br>    tmp := square(x)<br>    if !filter(tmp) {<br>      continue<br>    }<br>    acc = sum(acc, tmp)<br>  }<br><br>  return acc<br>}</pre><p>If we run <strong>go test -bench=.</strong> I get:</p><pre>BenchmarkPipeline-4              1364      887367 ns/op<br>BenchmarkForLoopPipeline-4         924     1213157 ns/op</pre><p>So, on my machine, the iterator pipeline is faster than <strong>ForLoopPipeline</strong> by about <strong>~1.37</strong> times. <br>If we run with <strong>-benchmem</strong>, we can also see that both versions can run with **0 allocations**.</p><p>For an experiment, I preallocate a slice and compare again:</p><pre>func ForLoopWithArrayPipeline(xs []int) int {<br>  acc := 0<br>  for _, x := range xs {<br>    tmp := square(x)<br>    if !filter(tmp) {<br>      continue<br>    }<br>    acc = sum(acc, tmp)<br>  }<br><br>  return acc<br>}</pre><p>Results:</p><pre>BenchmarkPipeline-4                         1274      956065 ns/op<br>BenchmarkForLoopPipeline-4                   967     1225687 ns/op<br>BenchmarkForLoopWithArrayPipeline-4          328     3807070 ns/op</pre><p>Here the iterator pipeline is <strong>~4x</strong> faster than the slice-based loop.</p><p>This is expected: the slice-based version must load 1_000_000 ints from memory. <strong>Generate(max)</strong> produces numbers without reading a large array, so it puts less pressure on caches and memory bandwidth.</p><h3><strong>What else?</strong></h3><p>You may ask why not write the pipeline in a step-by-step style (with intermediate variables), like this:</p><pre>func PipelineEscape(max int) int {<br>  xs := Generate(max)<br>  xs = Map(xs, square)<br>  xs = Filter(xs, filter)<br>  result := Reduce(xs, 0, sum)<br>  return result<br>}</pre><p>Here is the answer:</p><pre>BenchmarkPipelineEscape-4                    100    10368198 ns/op       192 B/op        6 allocs/op<br>BenchmarkPipeline-4                         1270      929701 ns/op         0 B/op        0 allocs/op<br>BenchmarkForLoopPipeline-4                  1094     1506114 ns/op         0 B/op        0 allocs/op<br>BenchmarkForLoopWithArrayPipeline-4          289     3900064 ns/op         0 B/op        0 allocs/op</pre><p><strong>PipelineEscape</strong> does 6 allocations and is <strong>~12x</strong> slower than the “nested” <strong>Pipeline</strong>.</p><p>If we run escape analysis:</p><pre><br>go test -run=none -gcflags=all=&#39;-m -m&#39; 2&gt;&amp;1 | rg &#39;PipelineEscape&#39;</pre><p>we will see, for example:</p><pre>cannot inline PipelineEscape: function too complex: cost 144 exceeds budget 80</pre><p>More importantly, we will see messages like <strong>func literal escapes to heap</strong> and <strong>parameter yield leaks</strong>. That means the compiler must allocate closure-related state on the heap (instead of keeping it on the stack), which adds allocations and typically slows the benchmark down.</p><p>In the end: <strong>iter</strong> can be competitive (and sometimes faster than a direct loop), but performance depends on the exact code shape and what optimizations the compiler can apply. If you want to adopt the new iterator style, do it — but benchmark your code before and after.</p><h3><strong>Takeaway</strong></h3><p>- The iterator style can be fast, but <strong>performance depends on code shape</strong>, not just on the abstraction.</p><p>- A “step-by-step” pipeline with intermediate variables may trigger <strong>escape to heap</strong>, causing <strong>allocs/op</strong> and a big slowdown.</p><p>- A “nested” pipeline expression may allow better <strong>inlining</strong> and <strong>stack allocation</strong>, resulting in <strong>0 allocs/op</strong>.</p><p>- A loop over a prebuilt slice often loses due to <strong>memory/cache pressure</strong> (reading a large array), not because iterators are inherently slow.</p><p>- Always look at <strong>-benchmem</strong> and confirm hypotheses with <strong>escape analysis</strong> (<strong>-gcflags=all=’-m -m’</strong>).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eb77a84f2d6b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Golang: iter MORE!]]></title>
            <link>https://medium.com/@mikec123/golang-iter-more-8ac264bc573b?source=rss-dde4c0200fe9------2</link>
            <guid isPermaLink="false">https://medium.com/p/8ac264bc573b</guid>
            <category><![CDATA[golang-development]]></category>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Mike]]></dc:creator>
            <pubDate>Tue, 17 Feb 2026 18:44:40 GMT</pubDate>
            <atom:updated>2026-02-17T18:44:40.820Z</atom:updated>
            <content:encoded><![CDATA[<p>iter.Seq is like a Unix pipe for Go code - lazy, composable, and surprisingly powerful. And the best part? It’s really just a function.</p><p>Do you still remember from the previous post that iter.Seq is a type alias for a function?<br>So, I might repeat myself:</p><pre>type Seq[T] = func(yield func(T) bool)</pre><p><strong>IT’S A FUNCTION!!!</strong></p><p>If it’s a function, you can do many interesting things with it, such as writing your own Map:</p><pre>func Map[T any](xs iter.Seq[T], mapper func(T) T) iter.Seq[T] {<br>    return func(yield func(T) bool) {<br>        for x := range xs {<br>            tmp := mapper(x)<br>            if !yield(tmp) {<br>                return<br>            }<br>        }<br>    }<br>}</pre><p>Or even add Filter:</p><pre>func Filter[T any](xs iter.Seq[T], filter func(T) bool) iter.Seq[T] {<br>    return func(yield func(T) bool) {<br>        for x := range xs {<br>            if !filter(x) {<br>                continue<br>            }<br>            if !yield(x) {<br>                return<br>            }<br>        }<br>    }<br>}</pre><p>It’s that simple. What we have here are <a href="https://en.wikipedia.org/wiki/Higher-order_function">higher-order functions</a> -Map and Filter.<br>With these, we could even recreate our own version of <a href="https://github.com/samber/lo">lo</a>!</p><p>But let’s get back to the main point: what’s the best thing you can do with functions?</p><p>Function composition (<strong>pipelining</strong>):</p><pre>xs := []int{1, 2, 3, 4, 5, 6}<br><br>// Square each element<br>ms := Map(slices.Values(xs), func(x int) int { return x * x })<br><br>// Keep only even numbers<br>fs := Filter(ms, func(x int) bool { return x%2 == 0 })</pre><p>Here we chain Map and Filter together - the output of one becomes the input to the next. This is the essence of functional composition: building complex transformations by combining simple building blocks.</p><p>And for dessert - terminators (functions that consume the sequence):</p><pre>func Reduce[T any](xs iter.Seq[T], zero T, acc func(T, T) T) T {<br>    for x := range xs {<br>        zero = acc(zero, x)<br>    }<br>    return zero<br>}</pre><p><strong>The Takeaway</strong></p><pre>type Seq[T] = func(yield func(T) bool)</pre><p>It’s just a function. But because it’s a function, we get:</p><ul><li>Lazy evaluation — compute only what you need</li><li>Composability — build complex pipelines from simple pieces</li><li>Zero allocations — no intermediate slices, no memory overhead</li><li>Infinite sequences — generate values on the fly</li><li>First-class citizenship — pass them around, return them, store them</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8ac264bc573b" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>