<?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[Storytel Tech - Medium]]></title>
        <description><![CDATA[Storytel Tech Blog - Medium]]></description>
        <link>https://storytel.tech?source=rss----af0b7b8d131---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Storytel Tech - Medium</title>
            <link>https://storytel.tech?source=rss----af0b7b8d131---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 23 Apr 2026 19:55:46 GMT</lastBuildDate>
        <atom:link href="https://storytel.tech/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[Mentoring Matters: Navigating Life’s Challenges Together]]></title>
            <link>https://storytel.tech/mentoring-matters-navigating-lifes-challenges-together-394b8facefa0?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/394b8facefa0</guid>
            <category><![CDATA[mentoring]]></category>
            <category><![CDATA[personal-growth]]></category>
            <category><![CDATA[personal-development]]></category>
            <dc:creator><![CDATA[Betty Moreschini]]></dc:creator>
            <pubDate>Tue, 07 Nov 2023 13:17:22 GMT</pubDate>
            <atom:updated>2023-11-07T13:17:22.111Z</atom:updated>
            <content:encoded><![CDATA[<h3>Mentoring Matters: A Journey of Transformation</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*xT3KQihB5Z0qpQ6-gmAgvQ.jpeg" /></figure><h3>Introduction</h3><p>Welcome to the world of mentorship, a place where we share knowledge, nurture growth, and build connections. Our journey starts with a simple letter that unexpectedly set me on this path, eventually leading me to become a mentor myself.</p><p>In this article, we’ll explore the profound impact of mentorship. We’ll dive into my personal experiences and uncover the valuable lessons and motivations that make mentorship so remarkable.</p><p>Mentorship isn’t just about passing on wisdom. It’s a partnership where people of different ages come together to learn, grow, and shape their futures. It’s an exciting adventure filled with inspiring stories, moments of empathy, and the sheer joy of watching others thrive.</p><p>So, get ready for this adventure. Together, we’ll navigate the joys, challenges, and motivations that make mentorship a powerful force for positive change. Strap in, and let’s explore the magic of mentorship, where we can nurture growth and create brighter futures.</p><figure><img alt="Hobbit gif “i’m going on an adventure”" src="https://cdn-images-1.medium.com/max/245/0*UFaLPDyYTMYOMHeP" /></figure><h3>The Power of Being Mentored</h3><ul><li>Where it all began</li></ul><p>Let’s rewind to where my mentorship journey started, back when I was the one looking for guidance. But don’t worry, I’ll keep this part simple and engaging.</p><p>After high school, I received a letter that changed everything. No, it wasn’t an acceptance letter to a magical school like Hogwarts, but it was intriguing. The letter came from an organization that noticed I graduated with honors. Their mission? To help students from less privileged backgrounds who were doing well in school. They offered to connect me with a mentor who could guide me through higher education and the professional world.</p><p>At first, I couldn’t help but wonder, “Am I really underprivileged?”. I then realized that I was the first in my family to graduate from high school. Studies show that children from working class families are less likely to pursue (let alone graduate from) higher studies, compared to kids from privileged backgrounds¹. I soon realized that I was entering uncharted territory, and a little help might go a long way.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*nnfrc4OzRCBSl0zY" /><figcaption><strong><em>Educational attainment by background (from </em></strong><a href="https://assets.publishing.service.gov.uk/media/5a78d3f5e5274a2acd18a19a/Higher-Education.pdf"><strong><em>UK study</em></strong></a><strong><em>)</em></strong></figcaption></figure><ul><li>Into the world of preparatory classes</li></ul><p>Skipping ahead, instead of going to a regular university, I plunged into preparatory classes. These classes aren’t your typical ones; they’re two intense years preparing for entrance exams to engineering schools. Think of it as a world where your exam rankings determine your future, and you’re surrounded by brilliant minds. This is probably a good time to mention I’m from France, hence the slightly different flavoring of higher studies.</p><p>Preparatory classes are known for being demanding, often labeled as “elitist” because many students come from privileged backgrounds and prestigious high schools. Meanwhile, my high school focused more on getting students to graduate than preparing us for this challenging journey. I felt like I was playing catch-up, trying to keep pace with students who seemed like seasoned pros.</p><ul><li>Enter Jean, my mentor</li></ul><p>About two months into this whirlwind, I met Jean, my mentor. He was no stranger to preparatory classes, aced them, and made his way to one of France’s top engineering schools. He also had a successful career and had recently become a father.</p><p>Our mentorship was meant to last throughout my studies, but at the beginning, I wasn’t sure what to expect. I approached it with an open mind, not fully grasping how important mentorship could be.</p><ul><li>The uphill battle</li></ul><p>Now, picture the early days of preparatory class: Stress levels soaring, confidence plummeting. In high school, I was a top student, but here I found myself in the middle of the pack (yes, they ranked us on top of grading). My daily two-hour commute didn’t help, and exhaustion became my daily bread (because I’m French, get it? 🥖). It was a frustrating cycle; despite my dedication, I couldn’t see the progress I longed for. At one point, I even considered giving it all up, dreaming of a dual degree in math and Italian, with fantasies of teaching math in the scenic Italian countryside (a true story).</p><figure><img alt="Gif of Sheldon from TBBT breathing into a paper bag" src="https://cdn-images-1.medium.com/max/250/0*wdIGcSbfPQ6rTnKR" /></figure><ul><li>The turning point</li></ul><p>That’s when Jean, my mentor, stepped in. He shared his own struggles during preparatory classes and the coping strategies he had developed to handle the stress. It was like having a wise guide show me the way through this challenging terrain. Thanks to his guidance, I finally found an approach that worked for me, and I could see light at the end of the tunnel. Long story short, I managed to succeed in the exams and secure a spot in a school that perfectly aligned with my aspirations (with some assistance from Jean in ranking my choices).</p><p>In this whirlwind of mentorship, I experienced firsthand its transformative power. Having a mentor wasn’t just about surviving; it was about thriving, discovering untapped potential, and having someone believe in me when I needed it most. My journey from being mentored to becoming a mentor is a testament to mentorship’s enduring impact and its ability to shape lives.</p><p>Now, as we continue through this article, I invite you to explore the layers of mentorship — the motivations, experiences, and lessons that have shaped my mentorship story. Together, let’s uncover the richness of mentorship’s impact and its incredible potential for each of us.</p><blockquote><em>[1] Study on higher education in France depending on family background: </em><a href="https://www.inegalites.fr/L-enseignement-superieur-se-democratise-t-il"><em>https://www.inegalites.fr/L-enseignement-superieur-se-democratise-t-il</em></a><em> <br>Similar study in the UK: </em><a href="https://commonslibrary.parliament.uk/research-briefings/cbp-9195"><em>https://commonslibrary.parliament.uk/research-briefings/cbp-9195</em></a><em> </em><br>“Pupils eligible for free school meals are much less likely than other pupils to go into higher education, particularly to more prestigious universities. They are also almost twice as likely to drop out before the start of their second year in higher education.”</blockquote><h3>The Motivation to Give Back</h3><p>When I was in engineering school and things were more quiet, I took the time to reflect on that experience. I realized a few things, which ended up making me want to mentor when the time came.</p><ul><li>Luck and gratitude</li></ul><p>The first thing I realised was that without this person in my life at that time, I would probably have dropped out and definitely not be where I was then. To me, it felt like I had been incredibly lucky to have had that person placed on my way when I needed them. But then, reflecting even more, luck was only a part of that. The major part was that somebody had been willing to give their time to help someone that they did not know. I was grateful for that, and soon realized that I was able to create that kind of “luck” for someone else as well.</p><figure><img alt="Gif of a blonde lady crossing fingers on both hands for good luck" src="https://cdn-images-1.medium.com/max/288/1*XHl5RKy10KjrdIcx-271Hg.gif" /></figure><ul><li>Imposter syndrome</li></ul><p>The second thing I realized was that it did not necessarily take specific skills to mentor someone. Sometimes all you need to provide is a listening ear, and you’re already helping a lot. Sure, my mentor knew a lot about a lot of things, had a few degrees, a solid job, etc. But the main thing he helped me with was motivation, listening to me, sharing his personal story, and giving me some advice based on what he had seen in the professional world.</p><p>Also, do you remember the person you were when you were fresh out of high school? I’m pretty sure that no matter what your life looks like right now, there are many lessons you have learned since then. All these lessons and the life experiences that you have gathered, they make you more than able to be useful to somebody who has so many things to figure out.</p><ul><li>Ripple effect</li></ul><p>Mentorship has this incredible superpower — it’s like throwing a pebble into a calm pond, and suddenly, ripples spread far and wide. Remember how my mentor changed my life? Well, they didn’t just change mine; they changed the lives of everyone I’ve mentored, and hopefully, the lives of those they’ll mentor in the future. It’s like a chain reaction of positive change, and I want to keep that chain unbroken. Seeing the influence of my guidance extend to others is what keeps me excited about being a mentor.</p><ul><li>Pride and joy</li></ul><p>Another exciting aspect of mentoring is that you get to see people grow. Watching someone you’re mentoring go from “I’m not sure if I can do this” to “I’ve got this!” is an amazing feeling. Helping a mentee tackle challenges, learn new stuff, and grow in confidence is like watching your favorite team score a game-winning goal. That sense of pride and joy in their progress is what makes mentoring such a heartwarming experience.</p><ul><li>Representation</li></ul><p>One last thing I considered as a motivation to get into mentoring, was to provide more representation. Being a woman in the tech world, I’ve noticed that it can sometimes feel like we’re the unicorns in a forest of horses². That scarcity of women in tech has given me a big push to be a mentor. I want to show young women that tech isn’t just a boys’ club — it’s a place where we can thrive too. When girls see someone like them making it in tech, it’s like a green light saying, “You can do this too!”. And when I get assigned young men as mentees, I like to think that I am normalizing for them to see women in these positions. So representation is a big deal to me, and I think that I am making my small part of this big piece of work with my mentoring.</p><figure><img alt="Gif of a unicorn running in a field" src="https://cdn-images-1.medium.com/max/318/1*0tDxzNdtsNTQYxApk_K1BA.gif" /></figure><p>In conclusion, my motivation to give back as a mentor boils down to a few simple yet powerful things: gratitude for the mentors who lit up my path, a belief in the ripple effect of mentorship, the thrill of watching others grow, and a desire to be a beacon of representation, especially for women in tech. These motivations guide me through the exciting world of mentorship, reminding me daily why I’m passionate about this journey and why I’m committed to making a positive impact in the lives of those I mentor.</p><blockquote><em>[2] From </em><a href="https://www.womentech.net/women-technology-statistics"><em>https://www.womentech.net/women-technology-statistics</em></a><em> (2023): </em><br>“Women hold 28% of all jobs in computer and mathematical occupations, and 15.9% of jobs in engineering and architecture occupations.<br>In the European Union, women make up only 19.1% of the ICT (information and communication technology) sector.”</blockquote><h3>The Art of Mentoring</h3><p>Welcome to the heart of mentorship, where we dig deep into the different sides of this mentoring gig. Mentoring isn’t just about giving advice; it’s an art, and sometimes, it can feel a bit like an adventure. So, let’s dive into the world of mentoring and see what makes it tick.</p><ul><li>One-on-one or group mentoring</li></ul><p>Mentoring doesn’t come in a one-size-fits-all package. Some mentors like the one-on-one approach, where you focus on a single mentee. That’s what I do because it worked well for me as a mentee, so I figured it’d be my thing as a mentor too. Some other mentors prefer mentoring a group. That brings more points of view to the discussion, and you can also see more people being impacted by your actions. It’s like choosing between solo karaoke in the shower or a group sing-along — both are fun, but it depends on your style!</p><ul><li>Set expectations</li></ul><p>Here’s a vital tip: lay down clear expectations from the get-go. It’s like drawing a map for your mentoring journey. Talk about your goals, boundaries, and how you’ll measure success. Even when the goal seems crystal clear, like helping someone find an internship, how you’ll actually provide that help varies for different mentees. Having this chat from the start ensures everyone’s on the same page and sets the stage for a smooth ride.</p><ul><li>The Power of Listening</li></ul><p>Mentoring isn’t just about talking; it’s about being a good listener. Sometimes, the best thing you can do is lend an ear. Let your mentees spill the beans about their thoughts, hurdles, and dreams. It’s a bit like being a detective, uncovering the hidden gems of their potential. Ask open-ended questions, encourage reflection, and be that sounding board they need. You’ll be surprised by the insights they’ll find on their own.</p><figure><img alt="Gif of detective Pikachu" src="https://cdn-images-1.medium.com/max/445/1*REWxdW2CcmZkhWKzS7G2mw.gif" /></figure><ul><li>Guidance, Not Control</li></ul><p>Being a mentor isn’t about being a know-it-all who calls all the shots. It’s more like being a helpful navigator on a road trip. You’re there to provide support and guidance, not to take the wheel. While you can offer advice and share your experiences, it’s essential to empower your mentees to make their own decisions and learn from their choices. It’s like being a co-pilot, offering directions while letting them take the lead in their journey.</p><ul><li>The Patience Game</li></ul><p>Mentoring can sometimes feel like watching a pot of water boil. It takes time. Be patient, and remember that growth isn’t always linear. Celebrate small wins, and don’t be discouraged by setbacks. It’s like tending to a garden; with care and patience, you’ll see those mentees bloom.</p><ul><li>Empathy</li></ul><p>Empathy is a superpower in mentoring. It means putting yourself in your mentee’s shoes, understanding where they’re coming from, and offering support without judgment. It’s a bit like being a great friend who truly listens and cares about what’s happening in their life. Empathy allows you to connect on a deeper level and offer the kind of support that makes a real difference in their journey.</p><p>So, there you have it. In the mentoring world, it’s not about strict rules or climbing metaphorical mountains; it’s about finding your groove with your mentee. Remember, effective mentoring is a mix of sharing your know-how while empowering your mentees to chart their own path. It’s about patience, listening, and personal growth.</p><h3>What I Gain as a Mentor</h3><p>Let’s get real about something — being a mentor isn’t just about giving; it’s also about receiving in the most unexpected ways. So, what’s in it for me? Here are a few examples on how this is an all-round positive experience.</p><ul><li>A Fresh Perspective</li></ul><p>Here’s a fun aspect of mentoring: it keeps me on my toes. Every time I mentor someone, I’m not just sharing what I know; I’m also learning from them. Their fresh ideas, unique experiences, and questions often challenge my own thinking and push me to see things from new angles.</p><ul><li>Rediscovering the Basics</li></ul><p>Have you ever tried explaining something you’re good at to someone who’s just starting? It’s a wild ride! It forces me to break down complex concepts into bite-sized, understandable chunks. This process of simplification makes me revisit the basics of what I do, and in doing so, I often discover nuances and insights I had long forgotten.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/250/1*GjHWja8j9F_Rr1d3V949Rg.gif" /></figure><ul><li>Personal Growth</li></ul><p>Being a mentor is like a personal development bootcamp. It hones my communication, patience, and leadership skills like nothing else. It forces me to be empathetic, a better listener, and a more effective communicator. I’ve seen my own growth mirrored in the growth of those I mentor, and it’s an exhilarating ride of self-improvement.</p><ul><li>The Feel-Good Vibes</li></ul><p>Last but not least, the feel-good vibes I get from mentoring are off the charts. Watching a mentee overcome challenges, achieve their goals, or simply grow in confidence is a high like no other. It’s like a power-up for my soul, and it keeps me coming back for more.</p><figure><img alt="Gif of the guys from the office doing a celebratory dance" src="https://cdn-images-1.medium.com/max/300/1*vVGNYCRr_2Z4Lf5CvlCq-Q.gif" /></figure><p>So, you see, being a mentor isn’t just about altruism; it’s a two-way street of learning, personal growth, and those unbeatable feel-good vibes. It’s a win-win, and I wouldn’t trade it for anything else in the world.</p><h3>Conclusion</h3><p>In the world of mentorship, I’ve gone from being the one seeking help to being the one offering a helping hand. It’s a change that’s not only shaped my life but also the lives of those I’ve had the honor to mentor. When I look at mentorship, I’m reminded of its incredible impact, its power to lift, empower, and inspire.</p><p>From the days when I received that letter from an association to surviving the challenges of preparatory classes, mentorship was like my guiding star. My mentor, Jean, came into my life at a critical moment, offering not just advice but also unwavering support and understanding. Together, we faced the difficulties, and I emerged from it all even stronger.</p><p>Now, as a mentor myself, I’ve had the privilege of passing on the guidance and inspiration I received. Every interaction, every mentee, is a chance to make a positive difference, to be that encouraging force that propels someone toward their dreams.</p><p>Mentorship is a fantastic journey, like a dance of learning, growth, and shared experiences. It’s about helping each other, lending a hand when things get tough, and celebrating every victory, no matter how small. It’s about shaping the future, one mentoring relationship at a time.</p><p>So, whether you’re thinking about being mentored or becoming a mentor, remember that mentorship is a powerful way to bring about change. It’s about creating a ripple effect of knowledge, support, and empowerment that goes far beyond our individual paths.</p><p>As I wrap up this exploration of mentorship, I invite you to take a step forward, to embrace the transformation that mentoring can bring to your life. It’s a journey filled with gratitude, growth, and a chance to make a real impact.</p><h3>Resources for Aspiring Mentors</h3><p>Mentoring is a rewarding journey, and if you’re inspired to become a mentor, there are plenty of resources available to help you get started on this meaningful path. I should say that I am working with none of these so far, I’m mentoring through the association that mentored me back in the day (<a href="https://www.dema1n.org/">Dema1n</a>), and as it is French-speaking, I’m assuming it’s not the most relevant here. Here are some valuable resources and other organizations to explore:</p><ul><li><a href="https://www.mentoring.org">MENTOR</a>: MENTOR is a global advocate for youth mentoring. They offer resources, training, and support for mentoring programs worldwide.</li><li>Mentorship Programs at Local Universities: Many universities in Sweden have mentoring programs for students. If you’re an alum or have expertise in a particular field, consider reaching out to universities to explore mentoring opportunities. For example, see the KTH mentoring program <a href="https://www.kth.se/en/alumni/engagemang/kth/mentorprogram-i-stoc/engagera-dig-som-mentor-1.956096">here</a>.</li><li>LinkedIn and Meetup: These platforms often have mentoring groups and communities where you can connect with potential mentees or mentors who share your interests and goals.</li><li><a href="https://mentor.se/volontar/">Mentor.se</a>: Mentor Sverige is a Swedish organization dedicated to promoting mentoring in various fields, including education and career development. They offer mentoring programs and resources for both mentors and mentees.</li><li><a href="https://www.mydreamnow.se/">My Dream Now</a>: A nonprofit organization that aims to inspire and support young people in Sweden, particularly those from disadvantaged backgrounds, in pursuing their dreams and career goals. They offer mentorship programs and workshops.</li></ul><p>Remember, the most effective mentoring relationships often come from a genuine desire to share knowledge and support others in their growth. Whether you choose a formal mentoring program or informally mentor someone in your network, your guidance can make a significant impact on someone’s life.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=394b8facefa0" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/mentoring-matters-navigating-lifes-challenges-together-394b8facefa0">Mentoring Matters: Navigating Life’s Challenges Together</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Continuous delivery and DevEx]]></title>
            <link>https://storytel.tech/continuous-delivery-and-devex-fa89996fe0e6?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/fa89996fe0e6</guid>
            <category><![CDATA[developer-experience]]></category>
            <category><![CDATA[continuous-delivery]]></category>
            <category><![CDATA[continuous-deployment]]></category>
            <category><![CDATA[continuous-integration]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Henric Englund]]></dc:creator>
            <pubDate>Wed, 30 Aug 2023 09:45:36 GMT</pubDate>
            <atom:updated>2023-08-30T09:58:17.737Z</atom:updated>
            <content:encoded><![CDATA[<p><em>This is a story about how we at Storytel tied our delivery procedures and tools together to massively level up our developer experience.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9p6m3t3Z-tjHmYkdxExRMA.png" /></figure><h3>But first some background</h3><p>We were coming out of a phase of organic growth and our processes weren’t as polished as they could and should have been.</p><p>Over the years we accumulated a range of different tools and had a very manual and somewhat tedious process for how code got into production. Without going into too much detail, we can just say that the journey to production consisted of around 10-<em>ish</em> manual steps.</p><p>To tackle this debt, we decided to consolidate all our delivery-related tools into a fully automated, streamlined, and opinionated delivery pipeline, with next-level developer experience built in from the get-go.</p><p>We didn’t only want to optimize for the usual suspects, the short feedback loops, and the small but frequent production deploys, we also wanted to make the whole thing <em>“fun” </em>to work with.</p><h3>Guiding principles</h3><p>We started off by gathering our team of platform engineers and fleshed out some guiding principles for how we envisioned our opinionated delivery pipeline to work and behave.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xZ70oMiRYoTvLJvDn_jtGA.png" /><figcaption>Danger: Genius at work.</figcaption></figure><h4><strong>🤖 As little human involvement as possible</strong></h4><p>We wanted the path to production to be as <strong>frictionless as absolutely possible</strong>, i.e. eliminate all unnecessary chores and automate everything that can be automated.</p><p>The core of this was to trigger the delivery pipeline <strong>on every commit</strong> to the main branch, and for the delivery pipeline to take code changes <strong>all the way</strong> to production, every time.</p><p>This would then ensure that:</p><ul><li>The main branch is at any given time reflecting what’s running in production.</li><li><strong>All</strong> changes, small or big, go out in production minutes after they have been merged.</li><li>The feedback loop from code merge to production is as short as possible.</li></ul><h4>🚧 Build once and run everywhere</h4><p>We have several environments (production, development, local) and it’s generally considered good practice to <a href="https://12factor.net/dev-prod-parity"><strong>have as much parity</strong></a><strong> as possible</strong>.</p><p>In this case, this meant that the same container image should be used in all environments, i.e., build it once in the pipeline and run the same image in production as in all pre-production environments.</p><h4>🔎 Pipeline visibility</h4><p>Visibility and insight are crucial and we wanted our engineers (and others) to have <strong>full visibility</strong> into how their code is <strong>progressing through the delivery pipeline</strong>. At the same time, we didn’t want people to jump between many different tools to gather that insight.</p><p>What we really wanted was for all relevant events from all the different parts of the pipeline to be consolidated in a single “view”, where the relevant information is beautifully presented, approachable, and without adding any unnecessary noise.</p><h4>🔒 Secure by default</h4><p>It wouldn’t make a whole lot of sense for us to give away the keys to our castle to a third party, especially considering all the leaks that have been going on over the last couple of years.</p><p>You generally don’t want anyone but yourselves to have access to our production environment, meaning that whatever is integrated with our environments should be owned and managed by ourselves.</p><h3>Execution</h3><p>With our guiding principles in place, we went on to sketch out a solution.</p><p>We knew we wanted to build upon and leverage the tooling we already had, only introduce new technologies if absolutely necessary, and package everything as a streamlined pipeline instead of as individual tools.</p><p>To tie everything together into a cohesive experience, we decided to emit events from all relevant parts of the pipeline and implement an event aggregating service responsible for piping and presenting the progress of a pipeline run in Slack, in a beautiful way.</p><p>The following image shows how we envisioned this to work:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vcXcuQpRbPQNbdMWxlN_cA.png" /><figcaption>Masterplan</figcaption></figure><h4><em>Continuous Delivery vs. Continuous Deployment</em></h4><p>CD is one of the more confusing acronyms because it can mean both continuous <strong><em>delivery</em></strong> and continuous <strong><em>deployment</em></strong>. The difference between the two is that <em>continuous deployment</em> takes a code change all the way to production with no human intervention whatsoever, while with <em>continuous delivery</em> the very last step, the production deploy, is manually triggered. It’s still a fully automated process, it’s just that the trigger is manual.</p><p>We’ll revisit this and go full <em>continuous deployment</em> at a later stage but for us, it made the most sense to start with <em>continuous delivery</em> for the time being.</p><p>Anyhow, that’s why there’s a box in the drawing above saying Approve production deploy<em>, </em>it’s a button in Slack, the manual trigger.</p><h4>🤹 Implementation</h4><p>The pipeline starts with a GitHub Actions workflow going off on each and every commit to the main branch.</p><p>This can be seen as the <em>continuous integration</em> phase of the pipeline.</p><p>In this phase, we’re kicking off tests, compiling the code, running security scanning and we’re putting together a Docker image. All of these tasks fire off their own events, such as Tests started when the tests start running and Tests finished when they finish successfully.</p><p>As mentioned earlier, all these events are piped to and rendered in Slack through our <em>event aggregation service.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MXyXu015pUM_8aAFC13y_g.png" /></figure><p>We’re using ArgoCD for GitOps and we’re keeping all our Kubernetes manifests for all workloads in a centralized repo. The last part of the GitHub Actions workflow is to push a commit to this repo saying that there’s a new image version available for the workload we’re currently building.</p><p>We’re now entering the <em>continuous delivery</em> phase.</p><p>At this point we only want the change to go out in our development environment so those are the only manifests we’re touching. This commit will also fire off an event Rollout started.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ta2EfRkUNwspVC5r467x4Q.png" /></figure><p>Shortly after, ArgoCD will pick up the updated manifests and apply the changes according, effectively rolling out our newly built images in our development environment and sending out a Rollout finished event.</p><p>With the code change out in the development environment we can move on to the last phase of the pipeline, the production deploy. The one step of the process that has a manual element.</p><p>We’re showing a button in our Slack bot with the text Promote to production. When a user clicks this button (as they should) we’re kicking off another GitHub Actions workflow that is updating the production version of the Kubernetes manifests. This is firing a new Rollout started event, but this time for production.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RzvzBrIjBEWacVvS_LLXfQ.png" /></figure><p>Again, ArgoCD will shortly pick up the updated manifests, apply the changes to the production environments, and emit the corresponding Rollout finishedevent.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ETcUgElmR6FBqilm0ZtSsQ.png" /></figure><p>That’s it. Our code change is now running in production and the journey to get there was both smooth, secure, and fun. Mission accomplished! 💪</p><p>That’s the full delivery pipeline implemented with GitHub Actions, ArgoCD, Slack, and some clever glue code.</p><p>What’s explained above is obviously the happy path but as we all know things can and will go wrong, so there are also corresponding X failed events that are picked up and acted on accordingly by the event aggregation service, basically notifying the user that something broke and needs to be looked into.</p><h3>Wrap-up</h3><p>We went into this with the ambition to massively reduce the amount of manual toil and improve our developer experience, and we can safely say that we managed to achieve that. The number of steps required to ship something to production <strong>was reduced from 10-<em>ish </em>to just 2.</strong></p><p>Great success! <strong>🎉</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*THozkrBpm2Q6bVkBKT68jQ.png" /><figcaption>Before vs. after</figcaption></figure><p>In addition, we kept the complexity to a minimum by re-using what we already had, with the true key being that all relevant tooling was extended to emit events.</p><p>All in all, this gave us a full-fledged and streamlined continuous delivery pipeline that is not only optimized for short feedback loops but is also fun to work with and beautiful to look at.</p><p>In a future episode, we’ll dive deeper into the learnings and the impact all of this had on our engineering teams, so stay tuned!</p><p>Cheers and thanks for reading! 🍻</p><p>All kudos go to the great platform engineering team at Storytel who did all the hard work. 🌟</p><p><em>Credits: </em><a href="https://pixelspeechbubble.com"><em>Pixelspeechbubble.com</em></a><em> and </em><a href="https://www.avatarsinpixels.com"><em>Avatarsinpixels.com</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fa89996fe0e6" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/continuous-delivery-and-devex-fa89996fe0e6">Continuous delivery and DevEx</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Turbocharge: Storytel’s Story of weekly mobile app releases]]></title>
            <link>https://storytel.tech/turbocharge-storytels-story-of-weekly-mobile-app-releases-ebbc3691ec16?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/ebbc3691ec16</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[release-management]]></category>
            <dc:creator><![CDATA[David Božjak]]></dc:creator>
            <pubDate>Thu, 27 Oct 2022 15:06:18 GMT</pubDate>
            <atom:updated>2022-10-27T15:06:18.506Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QCvQh0fuFJyCveDmfQv32g.jpeg" /><figcaption>In 2022Q1 Storytel started releasing our Android and iOS apps every week; Project name Turbocharge</figcaption></figure><p><a href="http://storytel.com/">Storytel</a> made a big leap forward with the mobile apps’ <a href="https://cloud.google.com/blog/products/devops-sre/using-the-four-keys-to-measure-your-devops-performance">DORA metrics</a> in 2021Q1. We have transformed our internal processes and found an innovative way to reduce the app release cycle from three weeks to a single week without driving our engineers mad. Below I want to share some of our learnings — and yes, some burns — along the way. And of course, we are not done, the last section will share some ideas and idealistic plans of where Storytel is going next.</p><p><em>Once upon a time</em>, in Storytel’s early days, its mobile app releases were on a “spontaneous” release cadence. As most startups do early on, the team simply released whenever it felt it had something important to bring to the users, whether it was a new feature or an important bug fix. This could happen frequently or rarely but whenever it did it was accompanied by a flutter of activity: <strong>testing, fixing, stress, panic</strong>. It was towards the end of 2018 when we made the leap to the “trains” approach, <strong>having a release every 3 weeks</strong> <em>whether we needed it or not</em>. This added a lot of needed structure and predictability and was generally regarded as a very positive change.</p><p>And then… mostly nothing happened. For a long while. Despite knowing that it is not a terribly good cadence we fell deep into the “good enough” trap. It wasn’t awful, and it mostly worked for us. And despite expressing ambitions to improve on this already in 2019, we never done much about it nor made any concrete commitments to future investments. During all this time our processes (both those inside and outside the Tech department) synced their dance with the “Storytel Beat” dictated by the mobile app releases, making it an axiom. It felt that with every passing quarter it would be harder to change it.</p><blockquote>Computer Scientists have been using this hack since at least 1985 to solve problems just out of their reach</blockquote><p>This is the part where I tell you that vacations and disconnecting are truly important. It’s cliché but it’s true. Let me paint you a picture: It’s November 2021, I’m enjoying an extended weekend in a spa. I am wearing a goofy kimono, attending one of those group relaxation sessions, and yet work challenges are on my mind. And while I was drifting in and out, somehow my mind landed on that <em>hack </em>Computer Scientists have been using since at least 1985 to solve problems just out of their reach: <strong>Pipelines!</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*NMEu6LvZbUJBVP8Q" /><figcaption>Pipelines as traditionally depicted in a manufacturing setting: Boosting factory throughput without shortening total manufacturing time of a unit</figcaption></figure><p>And suddenly, lying there in a dippy kimono, I knew: Instead of fighting uphill to shorten our many release-related processes, adding time pressure and making everyone stressed, I simply needed us to have <strong>more ongoing release processes at the same time</strong>. This way we can create, effectively, weekly releases without our processes having to change much at all. Of course, hopefully the processes would get lighter, more effective, more streamlined and all in all better eventually anyway, but the key insight was that all this doesn’t need to happen first. <strong>We do not have to wait.</strong></p><p>The release processes at the time took 3 weeks and had roughly 3 stages so it was easy to draft into a 3-stage pipeline.</p><ul><li>Stage 1: “<strong>Freeze</strong>”: We branch off from the development branch, creating a new Release Candidate (RC) and starting a QA process on it. The code enters “code freeze” where only approved quality-related changes are accepted. This takes about a week.</li><li>Stage 2: <strong>“Beta”</strong>: The RC is considered mostly stable now and it enters a “Beta” period. This involves exposing it to a wide audience including external Beta users. The main focus of this stage is <strong>monitoring</strong>. If some truly critical problems are detected code changes can still be made, but the aim is to avoid any further changes. During this stage we formally send the RC for the final review by Apple/Google stores.</li><li>Stage 3: <strong>“Rollout”</strong>: We use a gradual rollout and have our finger on the Abort button in case of severe bad news coming from <a href="https://firebase.google.com/products/crashlytics/">Crashlytics</a> or Customer Support. This stage is not laborious, but it can be very impactful, potentially saving a lot of pain for a lot of users and Customer Support. Normally this takes most of the work week, with the rollout completing at 100% at the latest on a Friday.</li></ul><p>All “Project Turbocharge” was set to achieve was allow doing these processes in parallel instead of in sequence, and not aim to change or improve any of the stages. By the time project would complete we would have one release candidate in every phase each week, for a total of 3 parallel release candidates.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*qg4AeiOMcXzR7DQj" /><figcaption>Pipelines — interleaving releases — allows Storytel to release Android and iOS apps every week.</figcaption></figure><p>To make it happen I did a fair bit of selling and there my most important finding was that <strong>analogies are key</strong>. Even for very technical employees it was hard to make the pipelines mindset “click”. I threaded carefully to be respectful at all times and found that the established “9 couples can’t make a baby in 1 month, but they can make 9 babies in 9 months” analogy was helpful to many, even if it can be uncomfortable to mention in a work setting.</p><p>I took out one of my favorite tools: I wrote an <a href="https://en.wikipedia.org/wiki/Request_for_Comments">RFC</a> for our whole Tech community. I shared a document that clearly stated how I wanted our release process to look and what the benefits and drawbacks were. I allowed quite a long time to collect feedback on it, allowing everyone to gather their thoughts and provide suggestions. In the end most of the feedback I received was positive and most of the rest could be earmarked as “it sounds like more work for me”. The rest I were able to address, so altogether after this stage I was quite confident there weren’t any real, big, hard technical issues that would prevent us from going through with the plan.</p><p>At this point in the story the winter-break hits, so I gave one last encouragement to think about it over the holidays. As soon as we were back in January, I pulled the trigger: <strong>We are going to do this</strong>, and we are going to do it by the end of Q1.</p><p>Perhaps this seems drastic, but I am a big believer in <a href="https://en.wikipedia.org/wiki/Parkinson%27s_law">Parkinson’s Law</a>: Work expands to fill the available time. Therefore, when putting this plan into motion I put one of my favorite quotes to good use:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*t8yEpm2WTpM70_lC" /></figure><p>I admit I wasn’t truly confident that such a big change to our ways of working could really be achieved within a single quarter. But I was sure that it wouldn’t happen unless I was bold and set the expectations very high.</p><blockquote>There is only one way to start up a pipeline: you insert the first item, and then insert the next item into phase 1 after the exact time that matches throughput you expect to see at the other end.</blockquote><p>Even as the dev teams did a lot of work to make this happen, mostly within the scope of Automation efforts and scripting on our CI/CD, I did a lot of thinking about how we might actually make this happen when the time is right. When the insight finally came, it was as simple as one might hope: It is a pipeline, and there is only one way to start one of those up: you insert the first item, and then insert the next item into phase 1 after the exact time that matches throughput you expect to see at the other end (in our case, 1 week).</p><p>Suddenly the 7 weeks we had allowed for preparation were up, and it was time to start filling the pipeline. As you might expect, despite my best efforts to make this clear and communicate the plan well in advance, when we actually got started, plenty of people were surprised. I’ve heard plenty of “<em>Oh is that this week?</em>”, “<em>oh, I’ve misunderstood that</em>” and my favorite “<em>oh I was reading the schedule backwards</em>”. There are definitely a lot of learnings when it comes to communication, but…:</p><p><strong>In the end it worked!</strong> March 21 and March 28 were our first “turbocharge releases” with only one week in between them. We have been working on that same cadence ever since, freezing on each Friday, and starting the release rollout every Monday. 31 releases at the time of this writing and counting!</p><blockquote>It is important to remember that introducing change to any established process is going to also bring some short-term negative consequences.</blockquote><p>The story doesn’t quite end there though. I think it is important to remember that introducing change to any established process is going to also bring some short-term negative consequences: there will be confusion, mistakes, and many lessons will be learned the hard way. All of this is necessary before the gains of the new process can be felt by the organization. As luck would have it, it was during this time I’ve learned that there is a term for this: “<a href="https://getnave.com/blog/j-curve-effect/">The J Curve</a>”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ya4b-7cFh_i7SZYI" /></figure><p>One of the core principles of our new delivery strategy was that when we have a problem, we no longer focus on fixing the problem in the current version and deploying it (aka no “hotfixing”). Instead, the process it to abort the current rollout and make sure we fix it in the version that is up next. This is very important, as it breaks the negative feedback cycle we often previously experienced when the developers and testers needed to focus on the current problematic release, not spending their time on the next release, making it even more likely that the next release will also be problematic in some way or another.</p><p>It didn’t take that long until we had to test if the new principle was only good on paper, or did it also work in practice? Our 6th “turbocharge release” we got a non-trivial crash in our iOS signup flow, and it was not something we could fix by adjusting a <a href="https://learn.microsoft.com/en-us/dotnet/architecture/cloud-native/feature-flags">feature flag</a>. We kept a cool head and followed the plan: We halted the current rollout and fixed the issue in the release for next week. Guess what? This worked! The following release was a success and we succeeded in explaining the lack of hotfix to internal stakeholders, even as it meant that some users had to wait longer or business plans had to change.</p><p>To sum it up, here is a short list of benefits we see with pipelines now after we’ve been living the dream for a full half-year:</p><ul><li>More frequent updates to the user</li><li>Less risk associated with deploying mobile app code to production</li><li>No hotfixes, leading to much less “ad hoc” work, making work more predictable and stable for everyone working with mobile apps in some capacity.</li><li>More flexibility when it comes to planning, validating and experimentation for PMs and the product teams</li></ul><p>Of course, we do not consider the journey to be done. That is the pinnacle of continuous improvement, we do not want to settle. We do not want to find ourselves back in the <em>good enough trap</em>. Now after running with this system for 6+ months, we find ourselves on the “right side of the J-Curve”. We are hoping future improvements will come in two main areas:</p><ul><li>Get even more value from our <a href="https://www.makeuseof.com/what-is-dogfooding/">dogfooding program</a> by distribute daily builds from the develop branch to a wide internal audience. We seek to further increase the value we get from this by being even better and faster at triaging the feedback that comes in.</li><li>Shortening the stabilization period: Having less release candidates in the oven at the same time, therefore simplifying, and further improving time to market.</li></ul><h3><strong>Stay Tuned!</strong></h3><p><em>Does any of the above sound interesting to you? We’re hiring!<br>Check out all the interesting opportunities at </em><a href="https://jobs.storytel.com/"><em>jobs.storytel.com</em></a><em>, including within the iOS and Android Platform Teams that played a big part in making all of the above happen.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ebbc3691ec16" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/turbocharge-storytels-story-of-weekly-mobile-app-releases-ebbc3691ec16">Turbocharge: Storytel’s Story of weekly mobile app releases</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Image-based themes with Jetpack Compose]]></title>
            <link>https://storytel.tech/image-based-themes-with-jetpack-compose-711fcd5ed650?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/711fcd5ed650</guid>
            <category><![CDATA[colors]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[design]]></category>
            <dc:creator><![CDATA[Mario Ferreira Vilanova]]></dc:creator>
            <pubDate>Mon, 19 Sep 2022 12:44:54 GMT</pubDate>
            <atom:updated>2022-09-19T14:19:28.984Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article I’m going to walk you through how we at Storytel´s Android platform team approached the creation of dynamic color themes based on images and implemented it with Jetpack Compose. Material-You does something very similar, but this approach works on Material or any custom system you might have, like the one we use. These themes contain the same set of colors that a regular theme would do, all generated from a source image.</p><p>This has allowed our designers to create UI with the same set of colors they normally use (Surface, Primary, Secondary, etc), knowing that if we apply an Image-Based theme to that piece of UI, those generated colors will work well with each other. Our developers then can implement that UI in a theme-agnostic way, by using<em> Theme.colors.Primary</em>, <em>Theme.colors.Surface</em> and so on, which helps produce clean and maintainable UI code.</p><blockquote>A note to the reader: this article turned out to be quite long. It’s split into 2 parts. The first one goes through our approach on achieving a theme along with some color theory. The second part uses Jetpack compose to implement that theory.</blockquote><h3>The theory</h3><p>Let’s start by analysing a piece of UI from the app. Here we have one of the blocks we show in our main page. It consist of an image, a card, some text, and a rating star. The card uses our <em>Surface02</em> color, texts use <em>Neutral03</em> and the rating star uses <em>Primary01</em>. These colors work well with each other but are not related to the image, which is what we want to change.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dHJ2-hEmw0_R6U586Wg9LQ.jpeg" /><figcaption>Same UI block in light and dark mode</figcaption></figure><p>Our previous approach to achieve image theming was through Google’s Palette library. This library extracts a series of colors, each of one them including a suitable text color. This works fairly well on simple UI blocks, but it has a few drawbacks. The main one is that designers don’t know what to expect. Colors might be very vibrant in some images and dull on others. It works very well in a piece of UI that uses a single extracted color, but not so well when combining several. Our <em>Primary</em> colors are designed to have enough contrast and be used on top of all <em>Surface</em> colors, but the extracted ones won’t work well in most cases on top of each other. Let’s see how this same block would look using colors straight out from the Palette library.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tu2Mp4XNK_jpzgXETbnVlg.jpeg" /><figcaption>Same Ui using colors straight from Google’s Palette library</figcaption></figure><p>Using these directly is a bit of a lottery. If you compare the new colors with our main theme, you can see how text have much less contrast towards surfaces, and the color of the rating star is much less saturated. <em>Muted</em> and <em>Vibrant</em> colors don’t necessarily work well together either, as you can see again in the rating star. The main problem with this approach is that the Palette library does not guarantee that you’ll always get all possible swatches, it only guarantees to provide the <em>Dominant</em> color of the image. This required us to add a bunch of logic in order to know what to pick depending on theme, and fallbacks if those colors were not available. This in turn made our code harder to read, error prone and less maintainable.</p><p>Before we start creating the theme, let’s talk about HSL. Just as RGB, HSL is a way of representing colors, but instead of describing them in terms of <strong>R</strong>ed, <strong>G</strong>reen and <strong>B</strong>lue, it does so in terms of <strong>H</strong>ue, <strong>S</strong>aturation and <strong>L</strong>ightness. We will use this because our approach to create a theme will be to tint the existing ones with extracted colors. This tinting is achieved by replacing the Hue of a color, while maintaining its Saturation and Lightness values intact. Let’s see an example of how this works for our <em>Surface</em> and <em>Neutral</em> colors:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*34Tmmf80EJO7XrGFHNB60A.gif" /><figcaption>Surface02 being tinted</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ce2CZ7dl0s19jvmiO67QAg.gif" /><figcaption>Neutral03 being tinted</figcaption></figure><p>As you can see, by replacing just the <strong>H</strong>, we get a new color with the same properties. <em>Surface02 </em>is still pretty light and unsaturated and <em>Neutral03</em> remains nearly black (look closely, it’s not pure black). This would be really hard to do in RGB, while in HSL is a simple value substitution. The result is that any of the tinted versions of <em>Neutral03</em> would have a good contrast on top of any of the tinted <em>Surface02</em>, just like in our base themes.</p><p>Now that we know how tinting works, we need to decide towards which hues we want to tint to. We will choose hues out of colors extracted from an image. In our app we pick 3 different hues to do this, but in order to keep this article shorter, we will only choose 2 here, but he principle is the same. In our app themes, <em>Surface</em> and <em>Primary</em> colors have very similar hues, so we will tint those using the same Hue target, and use a second Hue for tinting the Neutral colors. Let’s go back to the Palette library and see what we get from it:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0BN7ezLD1NXad9bIA_MLVg.jpeg" /><figcaption>Colors extracted using Google’s Palette library</figcaption></figure><p>Notice how for each color we have an optional name, provided by the library, its representation in HSL and RGB. We will now put them all in a color wheel, based on their Hue values, which are always a number between 0 and 360:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6f7ayFjOGR_Ceyfx9-8OMA.jpeg" /><figcaption>Interestingly enough, we can see this image uses a <a href="https://en.wikipedia.org/wiki/Color_scheme#Triadic">Triadic color scheme</a>.</figcaption></figure><p>We will pick the <em>Dominant</em> color as our first Hue. This is because, as its name suggests, it’s the color with most presence in the image, and it’s the only color that the Palette library guarantees to provide. As second Hue, we will look for the one that has the maximum angular distance to the <em>Dominant’s</em> color Hue. This is so we get a good Hue spread in the generated theme. The formula for calculating angular distance, in Kotlin, is:</p><pre>fun Float.angularDistanceTo(beta: Float): Float {<br>    val phi = (<em>abs</em>(beta - this) % 360).toInt()<br>    val distance = if (phi &gt; 180) 360 - phi else phi<br>    return distance.toFloat()<br>}</pre><p>This is easily translatable to any other programming language. For more info, check the <a href="https://en.wikipedia.org/wiki/Angular_distance">wikipedia page</a>. We will run this formula from the <em>Dominant’s</em> Hue towards all others, and pick the one that gives us the longest distance. Here is the result:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FCAlUub2ZB3LmD1RMQc3-g.jpeg" /><figcaption>The selected 2 colors</figcaption></figure><p>Great. Now that we finally have our 2 target Hues, it’s just a matter of replacing the H in our theme colors with those values:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*emT-TB0ZdhEzr3VJJ6ikGQ.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qn2HbasyuKFpAuNXOeei2w.jpeg" /></figure><p>What’s so powerful about this approach is that since you are tinting an existing theme, you will get one with the same <em>Lightness</em> and <em>Saturation</em>. You’ll get dark and light themes with the exact same calculations. In fact, in our app, we have 6 themes that are used in different situations (main, kids and monochromatic, all with their light and dark versions). This method works just as well with any of them. Imagine the amount of code needed for picking colors manually for each one.</p><p>The result is much more pleasing to the user’s eye and predictable for designers. Let’s compare the original block with the tinted version we generated:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dHJ2-hEmw0_R6U586Wg9LQ.jpeg" /><figcaption>Original UI using our main themes</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NfHudFC1yB1MzXqzLelb3Q.jpeg" /><figcaption>Resulting UI block, using the image-based theme</figcaption></figure><p>And this is it for the theory. This, as you can imagine is not perfect. Contrast ratios are not maintained 100% since some hues have less intrinsic contrast than others with certain colors, with the most problematic being yellow. But with a bit more logic these problems can easily be resolved.</p><p>Needless to say, all this theory is applicable to any platform.</p><h3>The code</h3><p>If you’ve made it here, good job, I know all this is a bit heavy. But theory is important before we can start coding. Now let’s do some Compose and see how we can get this to work. Consider a basic theme setup that provides a color theme:</p><pre>fun YourTheme (<br>    content: @Composable () -&gt; Unit<br>){<br>    val colors = <em>remember </em><strong>{<br>        </strong><em>getThemePalette</em>(<br>            isDarkModeOn = isDark,<br>            isKidsModeOn = isKids, <br>            otherParams...<br>        ) <br>    <strong>}</strong></pre><pre>    CompositionLocalProvider (<br>        LocalColors provide colors<br>    ){<br>        content()<br>    }</pre><pre>} </pre><p>Here we have a function <em>getThemePalette</em> that provides a <em>ThemePalette</em> object. This object contains different color values depending on whether we are in dark mode, kids mode or a combination of other factors. We want to replace that setup with one that accepts an optional url (new code in bold):</p><pre>fun YourTheme (<br>    <strong>url: String?,</strong><br>    content: @Composable () -&gt; Unit<br>){<br>    val colors = <em>remember </em><strong>{<br>        </strong><em>getThemePalette</em>(<br>            isDarkModeOn = isDark,<br>            isKidsModeOn = isKids, <br>            otherParams...<br>        ) <br>    <strong>}</strong></pre><pre>    <strong>if (url.isNotNullOrBlank()){<br>        GenerateImageBasedPalette(<br>            basePalette = colors,<br>            url = url,<br>            onGenerated = { generatedColors -&gt;<br>                colors.update(newTheme = generatedColors)<br>            }            <br>        )<br>    }</strong></pre><pre>CompositionLocalProvider (<br>        LocalColors provide colors<br>    ){<br>        content()<br>    }</pre><pre>}</pre><p>Our theme accepts now an optional url, which will trigget a call to <em>GenerateImageBasedPalette </em>if provided. Since this requires image loading, we will run that function in a background thread and post the result in a callback (<em>onGenerated</em>). Once that’s done, we’ll update the values of the colors in our <em>ThemePalette</em> object.</p><p>While all this is happening, colors are being provided through the <em>CompositionLocalProvider</em>. The UI will be displayed with whatever base theme you have, and the colors will update once we receive the generated one. Another advantage of this approach is that if for some reason we can’t load or generate the theme, we can rely on our base theme. Let’s look at the code that generates the colors:</p><pre>@Composable<br>fun GenerateImageBasedPalette(<br>    fallbackPalette: ThemePalette,<br>    imageUrl: String,<br>    onGenerated: (ThemePalette) -&gt; Unit<br>) {<br>    <strong>val hash = <em>imagePaletteGenerationHash</em>(<br>        fallbackPalette = fallbackPalette,<br>        imageUrl = imageUrl<br>    )</strong><br>    <em>ExtractImageColors</em>(<br>        imageUrl = Uri.parse(imageUrl),<br>        sideEffectKey = hash<br>    ) <strong>{ </strong>extractedColors <strong>-&gt;<br>        </strong>extractedColors?.<em>let </em><strong>{<br>            </strong>val imageBasePalette = <em>imageBasedColors</em>(<br>                fallbackPalette = fallbackPalette,<br>                imagePalette = extractedColors<br>            )<br>            onGenerated.invoke(imageBasePalette)<br>        <strong>}<br>    }<br></strong>}</pre><pre>@Composable<br>private fun ExtractImageColors(<br>    imageUrl: Uri,<br>    sideEffectKey: String = imageUrl.toString(),<br>    onExtracted: (Palette?) -&gt; Unit<br>) {<br>    val imageLoader = <em>LocalContext</em>.current.<em>imageLoader<br>    </em>val request = ImageRequest.Builder(<em>LocalContext</em>.current)<br>        .data(imageUrl)<br>        .allowHardware(false)<br>        .build()<br><br>    <em>LaunchedEffect</em>(key1 = sideEffectKey) <strong>{<br>        </strong><em>launch </em><strong>{<br>            </strong>val palette = imageLoader.execute(request).drawable?.<em>let </em><strong>{<br>                </strong>Palette.from(it.<em>toBitmap</em>()).generate()<br>            <strong>}<br>            </strong>onExtracted.invoke(palette)<br>        <strong>}<br>    }<br></strong>}</pre><p>In these two functions we load the image and use Google’s Palette library to get the first color selection. Notice that we are creating a hash that is unique for the combination of current <em>theme+url</em>, and we use this as key in our <em>LaunchedEffect</em>. This is to prevent us doing this once per recomposition.</p><pre>fun Palette.themeSwatchSelection(): List&lt;Palette.Swatch&gt;? {<br>    val primary = <em>dominantSwatch </em>?: return null<br><br>    var secondaryAngularDistance = 0f<br>    var secondary = primary<br><br>    <em>swatches</em>.<em>forEach </em><strong>{ </strong>secondaryCandidate <strong>-&gt;<br>        </strong>if (primary.<em>hsl</em>.<em>hue</em>() != secondaryCandidate.<em>hsl</em>.<em>hue</em>()) {<br>            val primaryHue = primary.<em>hsl</em>.<em>hue</em>() ?: 0f<br>            val angularDistance = primaryHue.<em>angularDistanceTo</em>(<br>                beta = secondaryCandidate.<em>hsl</em>.<em>hue</em>() ?: 0f<br>            )<br><br>            if (angularDistance &gt; secondaryAngularDistance) {<br>                secondaryAngularDistance = angularDistance<br>                secondary = secondaryCandidate<br>            }<br>        }<br>    <strong>}<br><br>    </strong>return <em>listOf</em>(primary, secondary)<br>}</pre><p>This code gives us the 2 swatches needed to start the tinting. After all this is done, we do the Hue switching, like we did in the theory.</p><pre>private fun imageBasedColors(<br>    fallbackPalette: ThemePalette,<br>    imagePalette: Palette<br>): ThemePalette {<br><br>    val selection = imagePalette.<em>themeSwatchSelection</em>() ?: return fallbackPalette<br><br>    val primarySwatch = selection[0]<br>    val primarySwatchHue = primarySwatch.<em>hsl</em>.<em>hue</em>()<br>    val surfaceHue = primarySwatch.<em>hsl</em>.<em>hue</em>()<br><br>    val secondarySwatch = selection[1]<br>    val neutralHue = secondarySwatch.<em>hsl</em>.<em>hue</em>()<br><br>    return fallbackPalette.copy(<br>        bgTheme = fallbackPalette.bgTheme.<em>hsl</em>().<em>withValues</em>(hue = surfaceHue).<em>toColor</em>(),<br>        surface01 = fallbackPalette.surface01.<em>hsl</em>().<em>withValues</em>(hue = surfaceHue).<em>toColor</em>(),<br>        surface02 = fallbackPalette.surface02.<em>hsl</em>().<em>withValues</em>(hue = surfaceHue).<em>toColor</em>(),<br>        primary01 = fallbackPalette.primary01.<em>hsl</em>().<em>withValues</em>(hue = primarySwatchHue).<em>toColor</em>(),<br>        primary02 = fallbackPalette.primary02.<em>hsl</em>().<em>withValues</em>(hue = primarySwatchHue).<em>toColor</em>(),<br>        primary03 = fallbackPalette.primary03.<em>hsl</em>().<em>withValues</em>(hue = primarySwatchHue).<em>toColor</em>(),<br>        neutral01 = fallbackPalette.neutral01.<em>hsl</em>().<em>withValues</em>(hue = neutralHue).<em>toColor</em>(),<br>        neutral02 = fallbackPalette.neutral02.<em>hsl</em>().<em>withValues</em>(hue = neutralHue).<em>toColor</em>(),<br>        neutral03 = fallbackPalette.neutral03.<em>hsl</em>().<em>withValues</em>(hue = neutralHue).<em>toColor</em>(),<br>    )<br>}</pre><p>For completion, these are the functions that are used to work with HSL colors:</p><pre>fun FloatArray.hue() = <em>getOrNull</em>(0)<br>fun FloatArray.saturation() = <em>getOrNull</em>(1)<br>fun FloatArray.lightness() = <em>getOrNull</em>(2)<br><br>fun FloatArray.withValues(<br>    hue: Float? = null,<br>    saturation: Float? = null,<br>    lightness: Float? = null<br>) = <em>floatArrayOf</em>(<br>    hue ?: get(0),<br>    saturation ?: get(1),<br>    lightness ?: get(2)<br>)<br><br>fun FloatArray.toColor() = <em>Color</em>(ColorUtils.HSLToColor(this))<br><br>fun Color.hsl(): FloatArray {<br>    val result = <em>floatArrayOf</em>(0f, 0f, 0f)<br>    ColorUtils.colorToHSL(<br>        <em>toArgb</em>(),<br>        result<br>    ).<em>also </em><strong>{<br>        </strong>return result<br>    <strong>}<br></strong>}</pre><p>HSL is nothing more that a floatArray of three values, which we can replace as we please.</p><p>Finally, in order to apply this to a screen or UI block, all you need to do is to wrap it inside a new theme where you provide the url of the image, as simple as this:</p><pre>YourTheme(<br>    imageUrl = imageUrl<br>) <strong>{<br>    </strong>YourUIBlock()<br><strong>}</strong></pre><p>And you are done! You can apply this at any level you want. It can be applied to a block within a screen or to the full app. I’ll leave a small video of how we use this in our player too. Before we call it a day, just mention that in order to keep this article relatively short, I’ve omited several colors from our theme generation, like <em>Secondary</em>, <em>Accent</em>, <em>onPrimary</em> and <em>onSecondary</em>, but the process is the same and you can easily extrapolate it. Finally, I’d like to mention that at the time of writing we are looking for passionate android developers at Storytel. <a href="http://jobs.storytel.com/">Check our open positions</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/324/1*8t2G_4PD8jJyeEovCBoP7Q.gif" /><figcaption>How are new player uses this to become more immersive</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=711fcd5ed650" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/image-based-themes-with-jetpack-compose-711fcd5ed650">Image-based themes with Jetpack Compose</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCP integration with PagerDuty using Terraform]]></title>
            <link>https://storytel.tech/gcp-integration-with-pagerduty-using-terraform-257db26363f9?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/257db26363f9</guid>
            <category><![CDATA[terraform]]></category>
            <category><![CDATA[pagerduty]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <dc:creator><![CDATA[Andreas Lundgren]]></dc:creator>
            <pubDate>Wed, 06 Apr 2022 08:16:37 GMT</pubDate>
            <atom:updated>2023-02-15T07:35:46.324Z</atom:updated>
            <content:encoded><![CDATA[<p>This article will show you, with code and example, how Storytel 2022 went from a basic setup with a single global on-call team to a Full-service ownership setup.</p><p>We did this using Terraform to set up PagerDuty. Teams, Services and Event Rules used to integrate with GCP and Stackdriver is constantly synced between Storytel and PagerDuty via Terraform. Big thanks to account managers and support people at both PagerDuty and GCP to help us iron out the details!</p><p>(If you are just interested in how we connected GCP/Stackdriver to PagerDuty Event rules, you can jump directly to the section “<a href="https://medium.com/@andreas.lundgren/257db26363f9#62f9">Integrating with GCP Stackdriver</a>” below.)</p><p><strong>Edit:</strong> This is now available also as a <a href="https://www.youtube.com/watch?v=5aJQQdHHqX8">talk</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/828/1*puWYDcmEjmMlsXQL3nDAsg.jpeg" /></figure><h3>Starting Point</h3><p>Storytel had a single global on-call team with two services (and two escalation policies) in PagerDuty, “The High Urgency Service” and “The Low urgency Service”. The High Urgency Service wakes people up at night, The Low Urgency Service does not.</p><h3>Goal</h3><p>Storytel has not just two, but 100+ services in PagerDuty, each corresponding to a System at Storytel. A System is a key component and examples are book-search, consumption-tracking and content-contract, and these Systems may span over both backend and frontend components. All ~10 teams at Storytel are represented in PagerDuty, and each team monitor their own components 24/7 according to <a href="https://ownership.pagerduty.com/">Full-Service Ownership</a>. Once Systems are added, deprecated or changes team, PagerDuty is automatically updated.</p><h3>Prerequisite</h3><p>At hand, we have management’s commitment and an API where we could get a list of all our Systems and the team owning each one of them. The System API is backed up by a database where we store all our assets from e.g. GCP and Github and map them to a System. Each System is in its turn connected to an Owner. Something like Asset -&gt; System -&gt; Owning Team.</p><h3>Shortcomings</h3><p>I decided to not include Escalation Policies and Schedules in Terraform, but to keep managing them from the PagerDuty webpage. The GUI at PagerDuty for managing (especially) schedules is so much easier to use compared to terraform text files, that it was considered to outweigh the pros of auditing that you can get from Terraform files in combination with some version control.</p><p>Secondly, we decided not to put slack integrations in Terraform at this stage, but it may come later. The reasons are that firstly, you cannot provide slack channel names, but you need the channel ID which makes it less intuitive. Secondly, you cannot operate the PagerDuty API (and thereby Terraform) for slack integrations with the account-level access token, but you need a user level token. This would require us to create a separate account just for this integration.</p><h3>The Terraform setup</h3><p>The following PagerDuty resources should be synced from our Systems API. (More about that later.)</p><ul><li>Teams</li><li>Services</li><li>Ruleset Rules</li></ul><p>The following resources will be managed by manually editing terraform files:</p><ul><li>Rulesets</li><li>Users</li><li>Team membership (The connection between users and teams)</li></ul><p>To make user and team membership easier to handle, I created a module for that. It allowed us to specify users and team membership using a syntax like:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8e4537836ae0f7e602e13dca72b47f20/href">https://medium.com/media/8e4537836ae0f7e602e13dca72b47f20/href</a></iframe><p><em>Gotcha: This provided a way to inline team membership, but it also introduced a dependency that Terraform did not sort out. Teams must be added before putting users in the added team, it cannot be done in the same “apply”. For us, this was however no problem since we add teams by syncing with our Systems API, and we add users on demand via manual Pull Requests to our terraform repository.</em></p><p>I also created the root main.tf file, and files for the different resource types. I’ll show a representation of my file system here, linking the files to Github Gists as I create them during the course of this article:</p><p><strong>| — </strong><a href="https://gist.github.com/andreas-lundgren-storytel/bda31cd4fdcd45e2b8c071c0cd2f586d"><strong>main.tf</strong></a><strong><br></strong>| — modules<strong><br></strong>|. . .+ — users<br><strong>|. . . . . . | —</strong><a href="https://gist.github.com/andreas-lundgren-storytel/0f3cdbda41e7ebb677a33eba10189a62"><strong> main.tf</strong></a><strong><br>| . . . . . + — </strong><a href="https://gist.github.com/andreas-lundgren-storytel/b3151c0300196609520314843c84bdc1"><strong>variables.tf</strong></a><strong><br>+ — </strong><a href="https://gist.github.com/andreas-lundgren-storytel/89a9af7c4e311eed915b84ca6271fa1b"><strong>users.tf</strong></a></p><h4>Exporting the current setup from PagerDuty to Terraform</h4><p>The next step was to export our current setup to terraform. I wrote a script that took data from the Pagerduty API and generated both Terraform resource definitions (.tf files) and import commands to get the current setup into the Terraform state.</p><p>Note that this was the first time I ever worked with Terraform, so my approach was very exploratory. And when I got it to work, I did not care to refine the design considering this was a one time task. So I simply went to the PagerDuty API Explorer page, and got a list of<a href="https://developer.pagerduty.com/api-reference/b3A6Mjc0ODIyMw-list-teams"> all our teams</a> and <a href="https://developer.pagerduty.com/api-reference/b3A6Mjc0ODIzMw-list-users">all our users</a>.</p><p>I put them in two js files. Then I did a small <a href="https://gist.github.com/andreas-lundgren-storytel/cf1a5c52d483101ade740fe252919d0a">nodejs script</a> that gave me what I needed (but nevertheless is a bit embarrassing to show). It did the job, but not very gracefully. :-)</p><p>With this I populated the files users.tf and teams-legacy.tf</p><p>| — <a href="https://gist.github.com/andreas-lundgren-storytel/bda31cd4fdcd45e2b8c071c0cd2f586d">main.tf</a><br>| — modules<br>|. . .+ — users<br>|. . . . . . | — <a href="https://gist.github.com/andreas-lundgren-storytel/0f3cdbda41e7ebb677a33eba10189a62">main.tf</a><br>| . . . . . + — <a href="https://gist.github.com/andreas-lundgren-storytel/b3151c0300196609520314843c84bdc1">variables.tf</a><br><strong>| —</strong><a href="https://gist.github.com/andreas-lundgren-storytel/9030b3c50cdb737ac82612cfe57e82db"><strong> teams-legacy.tf</strong></a><br>+ — <a href="https://gist.github.com/andreas-lundgren-storytel/89a9af7c4e311eed915b84ca6271fa1b">users.tf</a></p><p>At this state, if running “<em>terraform plan”</em>, I can see that terraform wants to add all those teams and users. This is because terraform state is empty and does not know that these resources are already there. To remedy that, I executed all the import statements generated by the same script above. (I did a similar thing for services, but that script I lost. By now, you’ve got the idea anyway. :-) )</p><h4>Introducing the new Services and Teams</h4><p>Finally, I wanted to grab the information from our Systems API and put that into PagerDuty too. Our key components at Storytel are the Systems, so each Storytel System will map to a PagerDuty Service. You will see the term “system” in the API response body below. (We rarely track individual micro-services, our systems are the entities where we need to put incidents. For that reason, we chose this mapping. We have so far not made use of PagerDuty Business Services or Service Dependencies used by the Service Graph, but we hope to extend with that in the future.)</p><p>Our System API response body contains these fields:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1592f50b66795962d14e1e7c07c58fe7/href">https://medium.com/media/1592f50b66795962d14e1e7c07c58fe7/href</a></iframe><p>I use the system label, name, description and escalation policy ID to create my terraform records for <strong>PagerDuty Services</strong>. I use distinct team names to create my terraform records for <strong>PagerDuty Teams</strong>, and I use the system label to create<strong> Ruleset Rules</strong> for the Event Rules. This is done in this very straightforward JavaScript named <em>generate_pagerduty_terraform.js</em> described in the section “<a href="https://medium.com/p/257db26363f9/#efe5">The script and its runtime</a>” below.</p><p>After running that we end up with a file system that looks like this. (The file <em>ruleset-rules-AUTO-GENERATED-DO-NOT-EDIT.tf will be discussed a bit later.)</em></p><p>| — <a href="https://gist.github.com/andreas-lundgren-storytel/bda31cd4fdcd45e2b8c071c0cd2f586d">main.tf</a><br>| — modules<br>|. . .+ — users<br>|. . . . . . | — <a href="https://gist.github.com/andreas-lundgren-storytel/0f3cdbda41e7ebb677a33eba10189a62">main.tf</a><br>| . . . . . + — <a href="https://gist.github.com/andreas-lundgren-storytel/b3151c0300196609520314843c84bdc1">variables.tf</a><br>| —<a href="https://gist.github.com/andreas-lundgren-storytel/45236235e28ac5c6e9e418ebfebfb1fc"> ruleset-rules-AUTO-GENERATED-DO-NOT-EDIT.tf</a><br><strong>| — </strong><a href="https://gist.github.com/andreas-lundgren-storytel/6c9e50631f9d423f6541fcf2140b4ad0"><strong>services-AUTO-GENERATED-DO-NOT-EDIT.tf</strong></a><strong><br>| — </strong><a href="https://gist.github.com/andreas-lundgren-storytel/635befea80d32590342fc6f6eaf0cb41"><strong>teams-AUTO-GENERATED-DO-NOT-EDIT.tf</strong></a><br>| — <a href="https://gist.github.com/andreas-lundgren-storytel/9030b3c50cdb737ac82612cfe57e82db">teams-legacy.tf</a><br>+ — <a href="https://gist.github.com/andreas-lundgren-storytel/89a9af7c4e311eed915b84ca6271fa1b">users.tf</a></p><h4>The Escalation Policy Id</h4><p>You may notice that our API response contains an Escalation Policy ID, that looks a bit out of context. Since we decided not to include Escalation Policies (and Schedules) in Terraform, but to keep managing them from the PagerDuty webpage, Terraform has no record of those and cannot map any resource id to PagerDuty ID. Instead, for each team at Storytel, I had to fill in the Escalation Policy ID by hand in our database. We have only around 10 teams, so it’s not a big deal.</p><p>While a fully <a href="https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/escalation_policy">terraformed setup of Escalation Policies</a> would look something like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/14791fd6298c691d03ec4a9aa7aa98e4/href">https://medium.com/media/14791fd6298c691d03ec4a9aa7aa98e4/href</a></iframe><p>We just skipped the escalation policy part ad did this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d08d41b90f8bc2924f5401fbb31f7818/href">https://medium.com/media/d08d41b90f8bc2924f5401fbb31f7818/href</a></iframe><h3>The script and its runtime</h3><p>Finally, I wrapped that script in a small shell script that will:</p><ol><li>clone our repo with current terraform files</li><li>run the JavaScript mentioned above to generate new files</li><li>use git to see if anything was changed</li><li>If there were changes, make a Pull Request with the updates</li></ol><p>The shell script is packed in Docker and runs as a Cronjob on GKE a couple of times per day. If a Pull Request is opened on Github, my team will get a notice, and if approved, our <a href="https://www.runatlantis.io/">Atlantis plugin</a> will apply the updates on PagerDuty.</p><p>You can see our app here:</p><p>| — <a href="https://gist.github.com/andreas-lundgren-storytel/02490171549f9c9b5bf4045be8af6bc3">app.sh</a><br>| — <a href="https://gist.github.com/andreas-lundgren-storytel/90273c74a60989f0681e4bc0ca42cbb8">Dockerfile</a><br>| —<a href="https://gist.github.com/andreas-lundgren-storytel/539dee78aa57fa37a45d821aaa7a1bf1"> generate_pagerduty_terraform.js</a><br>| —<a href="https://gist.github.com/andreas-lundgren-storytel/cfb7e7218212b8612784f7baeb3e4538"> json_encoder.sh</a> (For GCP structured logging)<br>| — <a href="https://gist.github.com/andreas-lundgren-storytel/48c6da674bcc772207e7a17a86b2faa4">package.json</a><br>+ —<a href="https://gist.github.com/andreas-lundgren-storytel/9715612aa134b5af5730599d8519c4d9"> cronjob.yaml</a></p><h3>Integrating with GCP Stackdriver</h3><p>With over 100 PagerDury Services, it becomes tedious to set up Stackdriver Integrations for each one of them. This calls for work on both PagerDuty and GCP. Instead, the PagerDuty Event Rules Automation was used. I would best describe this (from Storytel point of view) as an “Incident Router”, where incidents from a single integration can be both <strong>routed</strong> and <strong>prioritized </strong>based on incident metadata.</p><p>The most challenging part here is the “incident metadata” needed for the routing. Different integrations (external alerting tools) send this metadata in different ways. For that reason, we decided to set up one Global Event Ruleset <strong>for each integration</strong>. This will focus on the GCP/Stackdriver integration. I created the file rulesets.tf by hand, and it describes our GCP/Stackdriver integration. We chose to have one Global Event Ruleset per integration rather than one per team. This is because they are all managed by terraform anyway and we control access rights via terraform instead. The Ruleset Rules are generated by the script described above. The file system now looks like this:</p><p>| — <a href="https://gist.github.com/andreas-lundgren-storytel/bda31cd4fdcd45e2b8c071c0cd2f586d">main.tf</a><br>| — modules<br>|. . .+ — users<br>|. . . . . . | — <a href="https://gist.github.com/andreas-lundgren-storytel/0f3cdbda41e7ebb677a33eba10189a62">main.tf</a><br>| . . . . . + — <a href="https://gist.github.com/andreas-lundgren-storytel/b3151c0300196609520314843c84bdc1">variables.tf</a><br><strong>| — </strong><a href="https://gist.github.com/andreas-lundgren-storytel/45236235e28ac5c6e9e418ebfebfb1fc"><strong>ruleset-rules-AUTO-GENERATED-DO-NOT-EDIT.tf</strong></a><strong><br>| — </strong><a href="https://gist.github.com/andreas-lundgren-storytel/25b7ab8661eabc2eb2958253af9fe19b"><strong>rulesets.tf</strong></a><br>| — <a href="https://gist.github.com/andreas-lundgren-storytel/6c9e50631f9d423f6541fcf2140b4ad0">services-AUTO-GENERATED-DO-NOT-EDIT.tf</a><br>| — <a href="https://gist.github.com/andreas-lundgren-storytel/635befea80d32590342fc6f6eaf0cb41">teams-AUTO-GENERATED-DO-NOT-EDIT.tf</a><br>| — <a href="https://gist.github.com/andreas-lundgren-storytel/9030b3c50cdb737ac82612cfe57e82db">teams-legacy.tf</a><br>+ — <a href="https://gist.github.com/andreas-lundgren-storytel/89a9af7c4e311eed915b84ca6271fa1b">users.tf</a></p><h4>Stackdriver metadata</h4><p>Finally, on 29/3 2022, just when this article was about to be published, <a href="https://cloud.google.com/monitoring/alerts/labels">Google did a major update</a> in the Stackdriver integration with PagerDuty, and a lot more information is now available. Prior to this, the only metadata that could be controlled by the user available in the alert was the Alert Policy name, and it came in a generic description field mixed in with other data points such as a description of what went wrong etc, all in one big mess. The scripts included in this article still describe a best-effort parsing of that old message type. It still works, but we are now of course looking into more robust ways of parsing what service is affected and with what priority using the new data fields in the updated Stackdriver message.</p><p>Here is an example of a GCP/Stackdriver alert message prior to the update:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eadb2bc20084f4c0b27834599b49aef5/href">https://medium.com/media/eadb2bc20084f4c0b27834599b49aef5/href</a></iframe><p>What we figured out so far is that:</p><ul><li>The JSON message received from Stackdriver integration is not always valid JSON. Some data fields contain unescaped quotation marks and if the metadata contains labels, it has one or two extra closing (right hand) curly brackets for that JSON object, making the full JSON struct unparsable. A ticket has been created.</li><li>When filtering on labels in your alert policy, the labels become a part of the metadata in the alert message to PagerDuty. For example adding “<em>filter (metadata.user_labels.system == ‘consumption-tracking’)</em>” will add a JSON field at <em>“details”.”incident”.”metadata”.”user_labels”.”system”: “consumption-tracking”. </em>We have so far only seen this by trying it out, and not had it verified by any documentation.</li><li>Severity can only be added when describing your Alert Policy via MQL. This makes it possible to do dynamin Severity based on for example how close you are to a certain threshold. It would however have been nice if custom static labels were supported too, for example, to explicitly set our system label or to be able to set a static Severity for example on uptime checks etc.</li></ul><p>Here is an example of a GCP/Stackdriver alert message after to the update 2022–03–13:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8ba19891569e3779913336a4a17234d2/href">https://medium.com/media/8ba19891569e3779913336a4a17234d2/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=257db26363f9" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/gcp-integration-with-pagerduty-using-terraform-257db26363f9">GCP integration with PagerDuty using Terraform</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Accessible app design: how to get started]]></title>
            <link>https://storytel.tech/accessible-app-design-how-to-get-started-552ad5f67a5a?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/552ad5f67a5a</guid>
            <category><![CDATA[apps]]></category>
            <category><![CDATA[accessibility]]></category>
            <category><![CDATA[inclusive-design]]></category>
            <category><![CDATA[user-experience]]></category>
            <category><![CDATA[usability]]></category>
            <dc:creator><![CDATA[Linnea Berggren]]></dc:creator>
            <pubDate>Wed, 30 Mar 2022 08:37:05 GMT</pubDate>
            <atom:updated>2022-03-30T08:37:05.170Z</atom:updated>
            <content:encoded><![CDATA[<h4>We’ll share our story, including wins (and yes, fails) and practical examples that can help you to take the first step towards a compliant and accessible experience for your users.</h4><figure><img alt="Four layouts of an audiobook app displaying book recommendations, a player, different genres, and book reviews by users." src="https://cdn-images-1.medium.com/max/1024/1*FCuisTDWLSEYCPKJSYMi4A.png" /></figure><p><strong>Accessibility guidelines will legally require compliance in the EU by 2025. </strong>Read that sentence one more time. How does that information make you feel? Confident? Worried? Overwhelmed?</p><p><a href="http://www.storytel.com">Storytel</a> is one of the biggest audiobook companies in the Nordics. When we started discussing accessibility among our design colleagues, it became clear that accessibility is important in design and to test with users. In the end, our main goal as designers is to make products and services accessible and inclusive as well as create awesome user experiences.</p><p>At Storytel, we believe that stories should be shared and enjoyed anytime, anywhere by anyone. That sentiment should be reflected in our app and website design. However, making sure that our product is 100% accessible with the current guidelines is a huge project and honestly, we felt a bit anxious about it.</p><blockquote><em>Where do we start? Who should be involved in the process? Who should take responsibility and ownership?</em></blockquote><p>Starting out, we were afraid that we would have to be experts on all accessibility rules and guidelines from the start. We thought that the effort we would have to put into learning would block our progress and innovation. The teams already had full roadmaps, how could we add accessibility on top of everything else?</p><p>We had a lot of questions, but we also had a great desire to learn and a willingness to get started as soon as possible.</p><figure><img alt="A busy dad on rollar skates with his dog, listening to audiobooks, holding his daughter, a take away coffee, a laptop and plant." src="https://cdn-images-1.medium.com/max/1024/1*-22qvF3y4MkSSALXcGzkoQ.gif" /></figure><h3>WCAG 2.1</h3><p>Let’s zoom out a bit. Why has accessibility become such a buzzword today?</p><p><a href="https://www.w3.org/WAI/WCAG21/Understanding/">Web Content Accessibility Guidelines (WCAG) 2.1</a> defines how to make web content accessible for everyone. Websites, apps and other digital products should follow WCAG 2.1 guidelines for both the private and public sector. For the private sector, it will be a legal obligation to follow WCAG 2.1 by no later than <strong>September 2025. </strong>This means that there is not so much time left, especially if you haven’t started the accessibility work for your product yet.</p><p>Let’s look at the problem from a different angle: Is it really the legal obligation and possible financial punishment that should drive this change and make companies work towards more accessible products? User accessibility is a matter of decency, not just a legal obligation. <strong>We do what we do because of our users. </strong>We should make sure that everyone can use our products and services because we all have unique needs.</p><h3>Types of disabilities</h3><p>There are different types of physical and mental variations and not all of them are plainly visible. <strong>Around 20% of all people worldwide have some kind of disability</strong>:</p><ul><li>Visual impairments</li><li>Cognitive variations</li><li>Hearing impairments</li><li>Motor disabilities</li><li>Speech disabilities</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wwyLQqnMV-44UQzOJ9NPmg.png" /></figure><p>There are also temporary disabilities. For example, a person with a broken arm may temporarily have the same difficulties as a person who is born with only one hand. A bartender may have the same difficulties as a person with hearing impairments due to the high volume of music in the nightclub.</p><blockquote><em>In other words, all people in the world could benefit from accessible products.</em></blockquote><p>Storytel is used by more than 2 million users around the world in over 25 different countries. Statistically speaking, around 400 000 (20%) of our users may have some kind of disability. It’s not ethical to exclude anyone. So how did we start improving accessibility in the Storytel app?</p><h3>Our first step: Talking to the users</h3><figure><img alt="An illustration of a family listening to audiobooks." src="https://cdn-images-1.medium.com/max/1024/1*u4lZPdUswwkWNmYR9_PgYw.gif" /></figure><p>We started with the most important and valuable asset we have — our users. We set up an online meeting with a user with visual impairment to understand their usage and interaction with the Storytel app. We asked them to open the app and what we saw surprised us. The app version they used was ancient. The answer to why they have not updated it made us even more curious.</p><blockquote><em>“I know the navigation and functionality by heart, I use the app every day, </em><strong><em>it’s in my muscle memory</em></strong><em>. I don’t want to risk that due to visual design changes and updates.”</em></blockquote><p>This shed light on the problem and sparked an eagerness to pull our sleeves up, put our hair in a messy bun and start working to improve the user experience. We needed to switch mindsets and prioritize functionality over visual design.</p><h4>Our learnings:</h4><ol><li><strong>Don’t wait</strong> with the usability tests until you have a perfect user panel with different disabilities and variations. Just get started.</li><li><strong>Think of UX as muscle memory.</strong> Consistent navigation is super important for users. Make sure you notify them when you implement big design changes.</li><li><strong>Include accessibility in the “definition of done”.</strong> We need to have accessibility as a part of the process and design documentation so that developers can build and test the implementation accordingly. For example, making sure that all design elements have the correct labels and text for screen readers.</li></ol><h3>Further evaluation of the app with the users</h3><h4>Playback speed</h4><p>One of the examples that we would like to share is how we worked with playback speed. It’s a feature in the player that allows users to choose how fast or slow the narrator reads the book.</p><figure><img alt="Two examples of the UI for the playback speed functionality, the old and the new one. In the old UI users could only choose a speed up to 2 times more normal speed, while in the new UI there is a possibility for up to 5 times more normal speed." src="https://cdn-images-1.medium.com/max/1024/1*xWvgfSHIvIOcgv46i22lAQ.png" /></figure><p>While working with playback speed in our app, we had an interview with a blind person. We sat next to her and observed how she was using her phone. But we couldn’t understand a word that her screen reader was saying because she used it at a very high speed, which we weren’t used to. Many people with visual impairments use their screen readers at very high speeds as a way to quickly scan a text.</p><p>The highest audiobook playback speed that we provided in our old design was 2 times the normal speed, which is already fast and is standard in the industry. But, after our interview, we decided to go even higher — up to 5 times the normal speed to make sure that we cover everyone’s needs.</p><p>Surprisingly it didn’t make much of a difference. Even though our intentions were good, our data analysis showed that <strong>no one needed these high speeds.</strong> Visually impaired users are used to them when using screen readers, but when people listen to content like books or podcasts they usually listen at normal speeds. So we put a lot of effort into something that wasn’t needed by our users.</p><p>Our focus was on general behavior (i.e. how people were using their phones and apps). We placed too little focus on how our users were using our products. This has a special meaning for accessibility work because not taking care of and properly understanding users’ behavior can lead to<strong> exclusion</strong>.</p><h4>Our learnings:</h4><ol><li><strong>Don’t go with the general approach and assumptions for accessibility. </strong>Make sure to test the solution in your users context.</li><li><strong>Dig deeper. </strong>Make sure you really understand how your users behave and how they interact with your product.</li></ol><h4>Sleep timer</h4><p>The sleep timer is one of the most used and thus one of the most crucial functions in the player. As a part of the player design work, we also wanted to redesign the sleep timer. The design process was followed diligently and was tested numerous times with various users. Edge cases were taken care of, it was iterated on many times and when we were happy with the result, it was implemented.</p><p>Here you can see two designs, the old one (left) and the new one (right).</p><figure><img alt="The old and the new design of the sleep timer. In the old one the buttons 15 mins, 30 mins, 45 mins, and 60 mins are on a bright white background. The design itself is outdated. In the new design, the background is dark, buttons are modern and visually pleasant, and there is one additional button “Until chapter ends 35 mins” so that users can choose to have the sleep timer on until the chapter ends." src="https://cdn-images-1.medium.com/max/1024/1*UAqzCiO-VWXbhALmzGo7xA.png" /></figure><p>From a design perspective, it may seem like the new version is much better, however, when it comes to accessibility it’s not that simple.</p><p>After the release, we started to get a lot of feedback from our users. Many were really frustrated that they couldn’t use the sleep timer anymore. As you can see in the image below, people got confused with the many dots and all of the abbreviations.</p><figure><img alt="With the new design, it became impossible to use the sleep timer for those users who have enlarged the text size on their phones. The text on the buttons, like 15 mins, 30 mins, 45 mins, the users only see the three dots as the text is to large and doesn’t fit the buttons anymore." src="https://cdn-images-1.medium.com/max/1024/1*iANW0vNtLAQtxVzcP9gWLw.png" /></figure><p>As you may have guessed already, the problem lies in the enlarging text font size. This is usually done in the phone settings, and not in the app settings.</p><p><a href="https://twitter.com/steipete/status/1052589183225815040?lang=en">Around 20–30% of people worldwide</a> use this setting to ease reading. Once people turn this feature on, they tend not to think about it again. In other words, it becomes a standard, a person’s “default” setting.</p><p>In our case, the sleep timer function became unusable for those users who had enlarged the font size on their phones. Numbers and text were automatically replaced with dots and letters which made no sense.</p><h4>Our learnings:</h4><ol><li><strong>Not everything can be tested on a prototype</strong>. A lot of accessibility functions, including dynamic text type features, should be tested just after the implementation, and if possible before the release.</li><li>This situation raised a very important discussion in our company — there was no clear process about when this should be done and<strong> who should own this responsibility</strong>. How should designers, developers, testers and other roles share the responsibility? We recommend starting this discussion as soon as possible because all of us need to take ownership and do our part.</li></ol><h3>Usability testing with screen reader users</h3><figure><img alt="Screenshots of the app where a user is trying to use the progress slider in the player and read a book review." src="https://cdn-images-1.medium.com/max/1024/1*5gWTVK_EPoP3GtKVdhAT2w.png" /></figure><p>Recently, we have tested our app remotely with users that use screen readers to navigate. Usability testing is by far the best way to naturally learn about how your users interact with the app and see and hear what works and what doesn’t work. The users shared their screens with us and we asked them to explore the app by giving them several tasks. We kept close attention to the VoiceOver feedback for the different parts of the app and let the users lead the session by pinpointing the issues they faced. And the result? We learned a LOT!</p><h4>Our learnings:</h4><ol><li><strong>Button labels.</strong> Go through all the clickable elements in the app and listen to the screen reader. Make sure to put correct labels on buttons so that users can understand what they can click and interact with them.</li><li><strong>Button’s additional states.</strong> Make sure to put correct labels on buttons so that users understand their different states. In our case, users didn’t get feedback if some buttons were selected or unselected.</li><li><strong>Feedback.</strong> Make sure the screen reader provides users with feedback when they change something in the app. For example, we found out that our users didn’t get any information from their screen readers when they rated a book or set up a sleep timer in the player.</li><li><strong>Progress bar sliders.</strong> These are really tricky elements for screen readers. Make sure they are easy to use, that they give the right feedback and that it’s easy to revert the changes if needed. For example, we discovered that the progress bar slider in the book player jumped by percentage for screen reader users, which could be confusing. It’s much more user-friendly to read the interval by time out loud instead.</li><li><strong>Information order.</strong> Make sure that the information in the layout is grouped and follows a natural information hierarchy order for screen readers. In our case, we found that our book reviews cards (placed in a horizontal scroll with a name, star rating, and review) were read as “Jonas”, “Sofia”, “Amanda” followed by users’ star ratings and reviews. A more natural order would be the name of the reviewer, their star rating, and the comment before the screen reader continues to the next review.</li><li><strong>Navigation. </strong>Make sure that the scroll position on the previous page is saved for screen reader users as well. We have saved scroll position for users not using any accessibility tools and we should have the same behavior for screen reader users.</li></ol><h3>A new accessible Design System</h3><p>Last year we switched our design tools, moving away from Sketch to our new tool Figma. Right now we are in the middle of setting up our new Design System for Storytel. We decided to take this opportunity, both designers and developers, to build accessibility into the components from the start. By doing this we make sure that the components we use in the app follow WCAG 2.1 standards.</p><h4>These are the things we improved connected to accessibility</h4><p>(that you can steal with pride and get started with right away)</p><ol><li><strong>Components check.</strong> We went through all the clickable elements and buttons in the app to make sure the touch areas are big enough.</li><li><strong>Color contrast check.</strong> We did an inventory of all our colors in the components to make sure they have good enough color contrast.</li><li><strong>Label check for screen readers</strong>. We specified how our components should sound when users use VoiceOver or Talkback in their apps.</li><li><strong>Dynamic text adaptive components.</strong> If users have enlarged text size in their phone settings we make sure that our design and text can adapt accordingly.</li><li><strong>Use of native components.</strong> Apple and Google offer native components which already have built-in accessibility. For example, the wheel where you can scroll to select a time or date. We try to use them as much as possible, for example in the sleep timer the native component works much better with screen readers than if we would have used a custom component.</li></ol><figure><img alt="Buttons with 48 px height to be compliant with touch areas, color contrast check, documentation of VoiceOver copy of the design, designs that works when the text is enlarged by the user and a native time selector as a scroll wheel." src="https://cdn-images-1.medium.com/max/1024/1*87nRpxCouIR9IZ1fCJu0WQ.png" /></figure><h3>Summary</h3><p>These are the key takeaways:</p><ol><li><strong>Start with at least one usability test.</strong> Find <em>one </em>user and just test your product. You will be amazed by how many insights you can get from one single user with a disability.</li><li><strong>Many accessibility features cannot be tested on a design prototype</strong> — like screen readers, dynamic text, etc. But they should still be tested somewhere in the process. For example, after the implementation and before it’s released to the public. Make sure you include this in your Definition of Done, so you don’t forget about it.</li><li><strong>Start small — you don’t have to be an expert.</strong> Even small changes are big wins for many users. Just a quick color contrast check or the implementation of native components instead of custom ones is already a win.</li></ol><p>Good luck on your accessibility journey! 🚀<br>Maybe you have already started? What have you learned so far? We are curious to hear your story in the comments!</p><p>This article was written by <a href="https://medium.com/u/cc935b9ed50e">Linnea Berggren</a> and <a href="https://medium.com/u/b5f1c4d1ba2a">Maria Pustynnik</a>. To our dear colleagues and friends who reviewed the article, thank you very much.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=552ad5f67a5a" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/accessible-app-design-how-to-get-started-552ad5f67a5a">Accessible app design: how to get started</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Boring organizations are the best]]></title>
            <link>https://storytel.tech/boring-organizations-are-the-best-bcd38b95d44c?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/bcd38b95d44c</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[organization-design]]></category>
            <category><![CDATA[organizational-culture]]></category>
            <category><![CDATA[sustainable-development]]></category>
            <category><![CDATA[agile]]></category>
            <dc:creator><![CDATA[Jakob Wolman]]></dc:creator>
            <pubDate>Mon, 10 Jan 2022 16:40:32 GMT</pubDate>
            <atom:updated>2022-01-10T18:24:56.027Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cZxcy5XOPvbPZOQKmwADxQ.png" /></figure><h4>We keep hearing stories about superhero developers, teams of rockstars, and dramatic problems that were solved last minute. These stories are fascinating and inspiring, but you don’t want to rely on this way of working.</h4><p>Inspired by <a href="https://medium.com/u/4c3f4fe11e6b">John Cutler</a>’s <a href="https://cutlefish.substack.com/p/tbm-4452-three-teams-boring-chaotic">post</a> I wanted to write about boring teams. Or as Cutler calls them: <em>boringly effective</em>. In the same spirit, I recall a quote from a Swedish soldier deployed to Afghanistan a few years ago, when describing their mission, and the risks of it:</p><blockquote>A boring day is a good day</blockquote><p>Imagine a boring team. A team where things just work. A team that knows where they are going, knows how to break work down, plan it, build it and ship it. A team that is strong on software craftsmanship, delivers code with high quality that is easy to understand and extend. They write plenty of tests and automate away mundane tasks. If problems are discovered they get nipped in the bud. The team is boring from a no-drama perspective, but they are stable and constantly deliver value to customers. This doesn’t mean it is boring to work in a team like this, rather, it is safe. There can be plenty of big challenges and hard problems to solve. This team will fail plenty, but most failures are small and safe. Failures lead to reflection, learning, and eventually improvements.</p><p>This is the team you want, and the environment your organization should strive for creating.</p><p>When I hear about an organization that celebrates hero behavior, rewards teams for pulling all-nighters, and constantly enter crunch mode to save a project last minute, I know something is wrong. This kind of drama is a sign things are not working well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*qNslBIjwaqFJcO3nHW25zg.jpeg" /></figure><h4>Heroes</h4><p>When an organization relies heavily on heroes to come and save the day, it is a symptom of several problems. I have observed people hoard information, system access, and knowledge, to constantly be needed. This is mostly a system problem. There is a lack of knowledge sharing, lack of investments in removing technical debt that only the heroes know how to deal with. There is a lack of coaching to help people become unstuck from the hero role, and a lack of investment from the organization to get out of the situation. This can be due to a lack of enough senior engineers in the organization, which in turn often is a symptom of a specific hiring strategy.</p><h4>Always busy</h4><p>I have observed both teams, and individuals being measured by how busy they are. We are taught concepts like “important managers have many meetings” and “teams need a full backlog of tasks to work on”. There is a notion that we should always say we are too busy, don’t have time right now, and are working on something more important. Those familiar with lean management and the <a href="https://en.wikipedia.org/wiki/Theory_of_constraints">theory of constraints</a>, know that sweating the assets creates a large work in progress (WIP) and long cycle times (the time it takes from starting work to finishing it). The team will not be able to pick up the most important work when needed. Nobody will be able to work on improvements and polishing, because we are always running for the next project. A system needs slack to be healthy and responsive, and so do individuals. The slack needs to be planned and protected. Priorities need to be clear and constantly updated. Roadmaps need to be regularly revisited and treated as bets or best guesses, rather than prophecies of the future.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*XUo5HWp4EMaVpsxaY8Iu1A.jpeg" /></figure><h4>Crunching</h4><p>Recurringly forcing a team into crunching mode is said to be a symptom of poor planning. My observation is that it is rather how the organization and team deal with the plan, and deviations from it, that creates weekends and long evenings of work. If a team is forced to stick with a plan because of an arbitrary deadline, it is the drama all over again. Admittedly, some teams deal with real deadlines (as opposed to <a href="https://lizkeogh.com/2017/08/31/reflecting-reality/">sadlines</a>, as stated by <a href="https://medium.com/u/9d51133228b5">Liz Keogh</a>) that have to be met. Unexpected events might occur and have to be mitigated, but these should be seldom and exceptions. Teams that constantly work in this mode will not be sustainable, and neither will their work. You want to make sure the team can keep a sustainable pace, and be boringly effective. Identify which dates must be met (because otherwise people, or the company, might die) and which delays are acceptable and mostly will upset someone.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*a4HX0gQ8VdZpi4xE6um31Q.jpeg" /></figure><h4>Taskforces</h4><p>Another common drama is the creation of task forces. This is a symptom of the organization not being able to carry out the most prioritized work in their current structure. Instead, people need to be handpicked for the job, a tiger team created and the organization yet again relies on heroes (or rather the fantastic four) coming to save the day. Once the problem has been solved, the business opportunity met or the feature built people go back to their original place in the organization, often leaving the ownership of what was just created floating around. The disruption of taskforces can be avoided by adapting the organization to the flow of work. Perhaps team structures need to change more often and <a href="https://www.heidihelfand.com/dynamic-reteaming/">reteam</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*gSpBcWM3L8ot3wFyDZgPog.jpeg" /></figure><h3>How to be boringly effective</h3><p>All of the above are symptoms of problems in an organization. The remedies can vary (it depends) and it is all tightly connected to the context you are in. But the general rule of trying to achieve a calm, sustainable pace and boringly effective team is a good goal to strive for. If there is drama, find ways to reduce it. As a <a href="https://en.wikipedia.org/wiki/Systems_theory">system thinker</a>, I strongly believe that the solution lies in the system surrounding the team, and rarely in the team itself, the manager, or the individuals in the team. Instead, focus on adapting things like architecture, organizational design, transparency of work, priorities, work in progress limits, and knowledge sharing. Focus on improving the flow of work. Don’t reward the drama (the heroes, the crunching, the taskforces). Teach teams to put out small fires early, rather than celebrating huge firefighting efforts. Give your organization the goal of becoming boringly effective.</p><h3>Applicable everywhere?</h3><p>During an early review of this text, I got the question whether boring teams really are applicable everywhere. Some companies, like startups, are built around the drama. They don’t have enough resources to have the luxury of being boring. They have real deadlines because missed opportunities might make them irrelevant, or block another round of funding, bringing the whole company down. I agree that there are situations where drama is needed. Where there is enough urgency to get all hands on deck and stop everything else. But the key is to avoid these situations, and this goes for small companies dependent on funding too. We have to build sustainable businesses without constantly relying on drama, heroes, and crunching to meet business goals. My ideas in this area are well reflected in <a href="https://medium.com/u/54bcbf647830">DHH</a> and <a href="https://medium.com/u/c030228809f2">Jason Fried&#39;s</a> books <a href="https://www.storytel.com/se/sv/books/it-doesn-t-have-to-be-crazy-at-work-263716?appRedirect=true">It Doesn’t Have To Be Crazy At Work</a> and the now more than a decade-old <a href="https://basecamp.com/books/rework">Rework</a>. For further reading (or rather listening) I’d also recommend Eliahu Goldratt&#39;s classic <a href="https://www.storytel.com/se/sv/books/the-goal-a-process-of-ongoing-improvement-30th-aniversary-edition-48734?appRedirect=true">The Goal</a> and the brilliant book <a href="https://itrevolution.com/the-phoenix-project/">The Phoenix Project</a>.</p><p>I’m curious about your observations of boringly effective teams, and where drama has damaged companies. But I’m also curious about situations you might have observed where the drama lead to something good, and sustainable. Please share in comments below.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bcd38b95d44c" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/boring-organizations-are-the-best-bcd38b95d44c">Boring organizations are the best</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tips for a Tech Interview from Storytel Engineers]]></title>
            <link>https://storytel.tech/tips-for-a-tech-interview-from-storytel-engineers-4bf269595d6f?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/4bf269595d6f</guid>
            <category><![CDATA[technical-interview]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[coding-interviews]]></category>
            <category><![CDATA[candidates]]></category>
            <category><![CDATA[jobs]]></category>
            <dc:creator><![CDATA[Jenny Rukman]]></dc:creator>
            <pubDate>Tue, 23 Nov 2021 14:41:42 GMT</pubDate>
            <atom:updated>2021-11-23T14:58:48.457Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-2spCd7aX3eSzQSTPohYQg.jpeg" /><figcaption>Our engineers at Storytel. Photo credit: Sofie Svendsen Jensen.</figcaption></figure><p>As a software engineer at <a href="https://jobs.storytel.com">Storytel</a>, I often get to interview candidates for our developer positions at the technical interview stage. I have had so many interesting interactions with our candidates, both good and bad, and over time, gathered some thoughts on what I would have liked to tell them ahead of time, to help bring out the best of them and make the interview a success.</p><p>With this in mind, I sat down with a couple of fellow engineers here at the company to write up our most important tips for a technical interview based on our experience.</p><p>Here’s what we wish we could have told our candidates ahead of time:</p><h3>Brush Up the Relevant Skills</h3><p>We cannot stress this one enough! As a candidate you often know which areas the interview is going to be focused on. If it involves APIs, brush up on the API best practices, response codes etc, security and authentication, or if it’s iOS be prepared to discuss Cocoa design patterns, and data structures and their efficiency are always important for backend positions.</p><p>This is actually one of the most important tips that we can give you, as while you might be familiar with these topics, demonstrating your skills in a tech interview is a whole different thing. You might get nervous, not have enough time, be overwhelmed by another question and most likely will not be able to look something up online to refresh your memory — so do it beforehand. Use this time, of the tech interview, to show the depth and breadth of your knowledge, and make sure you use this time to shine!</p><h3>Communicate Your Thought Process and Tradeoffs</h3><p>One of the most important things that we try to learn about the candidate during the interview is how they think and solve problems. If you remain silent most of the time, providing only your final conclusion as the answer to questions or when explaining something, it is very difficult for us to understand the tradeoffs and your thought process.</p><p>Understanding your thought process can often also be helpful when you don’t have the right answer, we all make mistakes. But if it’s clear to us how you approach a problem then this shines more light on your abilities.</p><p>Same goes for home coding exercises. Invest time in explaining why you made important decisions about your project. For instance: what would be different for a real-world application, what was not implemented due to lack of time or resources, and if you choose a programming language that is not your biggest strength, explain why.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OTn4ofEaJTk1l1c34eapdQ.jpeg" /><figcaption>Photo credit: Sofie Svendsen Jensen</figcaption></figure><h3>Ask for Clarifications</h3><p>Asking the interviewer questions that help you clarify a topic is crucial in more than one way.</p><p>First, it can obviously save you from making mistakes simply because of a misunderstanding or a wrong assumption.</p><p>Second, we like seeing that you approach problems with attention, work carefully and don’t jump into the implementation stage before fully understanding what’s at hand. What’s more, this lets us learn more about how you think and break down a problem. So in general, don’t be afraid to ask questions!</p><p>By the way, this also goes for the end of the interview. Whenever possible, prepare some questions in advance, as this can be another way to show what you’re interested in and show off what you know just before the interview comes to a close.</p><p>Here’s what one of the developers on my team had to say during our discussion:</p><blockquote><strong>“It’s always good to ask the interviewer if the solution should be close to a real-world application, or if it’s mostly meant to demonstrate some basic knowledge. It’s one thing to implement a working solution, but for production applications, one should also consider performance, scalability and maintainability.”</strong></blockquote><h3>Don’t Forget Testing</h3><p>Testing is a topic that is almost always discussed in interviews. We may want to know, how would you test your code, and whether you considered different corner and edge cases. We want to see that you gave this a thought, have testing experience, know about unit tests versus integration tests and have the right approach to it. Be prepared to discuss this in the code interview and always address it in your home coding exercise.</p><p>Here’s an input from one of our iOS developers on the topic:</p><blockquote><strong>“I’d say, write tests, even if you weren’t asked to. This helps to verify that what you submit is working, plus it shows you can do that.”</strong></blockquote><h3>Object-Oriented Programming</h3><p>Demonstrating a good understanding of Object-Oriented Programming is important to us, as these are the languages we mostly use. When creating your implementation, make sure not to mix OOP with Functional programming design, for example, and show strong knowledge of implementing classes, fields, immutability etc.</p><h3>Being Nervous Can Be a Good Thing</h3><p>Very often we notice that the candidates seem nervous, though for us, this isn’t usually a problem — nerves can indicate that the interview matters and that the position is important to the candidate. Besides, we were all on the other side of the interview ourselves at one point or another. If you are feeling nervous during the interview, keep in mind that some interviewers see this as a positive and do not get nervous about being nervous!</p><p>Carry on — we are all human, and try to shift your focus on the topic we are talking about, rather than how you are feeling.</p><p>So don’t let your nerves ruin it for you, and just focus on the topics we discuss.</p><p><strong>On the same topic, next up on our blog is a post on Tips for a Coding Home Project, coming up soon…</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4bf269595d6f" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/tips-for-a-tech-interview-from-storytel-engineers-4bf269595d6f">Tips for a Tech Interview from Storytel Engineers</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The value of values]]></title>
            <link>https://storytel.tech/the-value-of-values-78a883659534?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/78a883659534</guid>
            <category><![CDATA[values]]></category>
            <category><![CDATA[agile]]></category>
            <dc:creator><![CDATA[Johan Asplund]]></dc:creator>
            <pubDate>Tue, 05 Oct 2021 07:39:30 GMT</pubDate>
            <atom:updated>2021-10-05T07:57:10.393Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/743/1*g_L6JEhvT9kKfqVQMp504w.png" /></figure><p>In my early days as an agile coach I worked for a company that was thriving in a really complex and volatile business and we also had really happy employees.</p><p>You know all those things that the agile community says you will get if you do agile. It was just that, we had not implemented agile or even talked about it… So why did we thrive? It became clear to me that what we had was something way more powerful than the agile tools and frameworks, we were living by the agile values. And as you know culture (your values) eats strategy (implementing a framework) for breakfast. In this blogpost I will share my thoughts around values and agile software development.</p><p>The manifesto for agile software development has been around for 20 years! Yes that’s right, the birth of it happened at Snowbird Resort Utah in feb 2001. Isn’t it odd that after all these years the internet is flooded with articles like <em>“How to make the stand up not suck”</em> or “<em>Help my PO is killing team spirit</em>” or anything else centered around that agile is broken and can be fixed.</p><p>Most people think agile software development is something that you do, that it is a process and has tools. You just have to learn it and then you are fine, like going from having all your source code on a network drive to implementing a source control system and applying a PR process. But as you might have guessed I think that is wrong.</p><h3>You can’t do agile you have to be agile</h3><p>As I said I don’t believe that you can do agile, you have to be agile. Being agile means that you know why you are doing the things you are doing and also that you believe in that why, i.e. the daily scrum is not just something you have to do, rather you truly believe that it is the best way to start the day together with your team. Or delivering a feature before it is gold- or even silver plated is nothing you do because the agile manifesto says we should do early and continuous delivery, you do it because you really want to include your stakeholders on the journey and really value the feedback .</p><p>If you embrace that you have to be agile it becomes very clear that it is about values and principles and not about framework and tools. By the way, these values are not the ones that are typically known as the four agile values, these are basic human values.</p><p>In far too many organisations (mostly outside software development organisations these days, since agile frameworks are more or less standard here) it goes something like this.</p><ul><li>Hey I’ve read this book about agile, it says you will get double the value in half the time. We need to do agile.</li><li>OK, I hear you but we have tried so many things so this time we need to do it by the book so we make sure we do it right.</li><li>OK, the book says we should have cross functional teams, have short sprints, do daily meetings and retrospectives.</li><li><em>And so on…</em></li></ul><p>Before you know it you have a full blown organisation by the agile book but you are not getting the results you wished for, instead you get the quotes from the ingress. The reason is that you started at the wrong end. You started by implementing the framework and tools and forgot about making sure that your organisation fosters the values needed to be agile. You are trying to push a square peg through a round hole.</p><p>Instead of the daily stand up creating inclusion and engagement people think it wastes their time because they have to listen to things that will not affect them.</p><p>Instead of creating teams that take ownership of and deliver value you get teams saying that they constantly are stuck waiting on others they are dependent on. And so on.</p><p>So how can you tell if you are on the “wrong” side? Here are a few hints.</p><ul><li><strong>Postponing demos</strong> because it is not done. This might seem very obvious since it is part of the 12 agile principles but I have seen it so many times. This goes both ways, developers not wanting to show until it is shiny but also stakeholders not wanting to see until everything is done.</li><li><strong>Opting for output rather than outcome. </strong>If you are postponing, or simply not have, demos with stakeholders you will probably sooner or later fall into the trap of wanting to do as many things as possible instead of making sure to make things that create value. That is natural because without a dialogue about what creates value for your stakeholders you will need to define your purpose in other ways and that will be the number of things you deliver.</li><li><strong>People dependent backlog</strong>. If you are assigning people to the issues already when they are in the backlog you are a slippery slope that will lead to people availability dictating the prioritisation instead of value. When that happens people focus on delivering their task and not delivering value and if you are focusing on your tasks of-course it feels like a waste of time listening to all others tasks. As soon as you are starting to opt for output rather than outcome you risk ending up here because it is probably a smart thing to only do things that fit you perfectly if you want to do as many things as possible.</li></ul><p>An agile organisation is like a pyramid where the values create the foundation, the framework is the top and in between there are principles and methods. If you want to have an organisation that succeeds with the chosen framework, if you choose to have one, you must first have a solid foundation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*p8G5vLAq_ZC1uWdQND5lnA.png" /></figure><h4>So what are the values I’m talking about?</h4><p>First of all there are some common interpersonal values, trust, respect, honesty, and loyalty, that in my opinion need to be there regardless to have a healthy organisation. In this context though you can’t live by the agile principles without them. I.e you can’t live by the agile principle of self organising teams without them, you can’t live by the agile principle of focusing on value without trust and honesty and so on.</p><p>Then there are a few really essential, let’s say more agile specific, values that you must believe in and live by in order to be able to be agile.</p><p><strong>Transparency</strong>, <strong>courage</strong> and <strong>innovation</strong> are all three really key, without them you will not be able to be agile all the way. Let’s take a look at what I mean and put it in the context of some of the well known agile principles, these are the <a href="https://www.agilealliance.org/agile101/12-principles-behind-the-agile-manifesto/">12 agile principles</a> behind the <a href="https://www.agilealliance.org/agile101/the-agile-manifesto/">agile manifesto</a>.</p><p><em>Our highest priority is to </em><strong><em>satisfy the customer</em></strong><em> through early and continuous delivery of valuable solutions.</em></p><p>Many organisations focus only on the bold text and forget what’s really key to being agile, namely early and continuous delivery. I don’t think they intend to do that, only that doing early and continuous deliveries takes <strong>courage</strong>, loads of it to be fully <strong>transparent</strong> and share your day to day progress and setbacks with your customers. Let’s face it, it’s super scary to actually deliver something that you don’t think is done because you might get feedback that suggest that you should shift direction.</p><p><strong><em>Simplicity</em></strong><em> — the art of maximising the work not done — is essential.</em></p><p>Making something truly simple, both processes and products, without diminishing the value of it, takes a lot of <strong>innovation</strong>. You also have to have the <strong>courage</strong> to do something disruptive.</p><p><em>At regular intervals, the team </em><strong><em>reflects</em></strong><em> on how to become more effective, then tunes </em><strong><em>and adjusts</em></strong><em> its behaviour accordingly.</em></p><p>This is probably one of the most essential agile principles and also the one where it is the most common case of following the framework but not truly living the values. Often when reflecting teams tend to come up with either low hanging fruits like “<em>we take on too much in each sprint</em>” or things that are outside the team like “<em>management constantly changes strategy</em>”. These things are very safe to talk about but it is also not the things that have the most effect on effectiveness. Talking about the things that have real impact on the effectiveness of the team requires a lot of <strong>transparency</strong> and <strong>courage</strong>. For instance if you think you are missing the best solution because your teammate constantly is killing the discussions you need to tell that in the retro, that will have far bigger impact than discussion the correct phrasing of a user story.</p><p>To reap the full benefits of agile you need to be agile not do agile and to be agile start living by the values and work your way up the pyramid. The first thing we need to do is take the values; trust, honesty, loyalty, transparency, courage and innovation and turn them into verbs because it is so much more powerful to ask “Have you invited and shared your insights with people around you today?” then it is to ask “Have you been transparent today?” or “Today I’m gonna look at the problem from a different angle?” compared to “Today I’m gonna be innovative”</p><p>This will also give the added benefit that they will be more tailored for you and your organisation.</p><p>So here is my challenge for you. Take the agile values; trust, honesty, loyalty, transparency, courage and innovation. Sit down and define what they mean for you, i.e what does transparency mean, which also includes defining what it is not, in your organisation. Is it sharing customer insights with the team or maybe it is letting people around you know what’s going on in your personal life. Then turn those things into verbs, i.e <strong><em>share your insights with your team</em></strong> or <strong><em>tell your team if something is bothering you in your personal life</em></strong>. That way you can easily talk about them and use them as questions in your team sessions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=78a883659534" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/the-value-of-values-78a883659534">The value of values</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Becoming a manager]]></title>
            <link>https://storytel.tech/becoming-a-manager-c493a342339c?source=rss----af0b7b8d131---4</link>
            <guid isPermaLink="false">https://medium.com/p/c493a342339c</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[management]]></category>
            <dc:creator><![CDATA[Jakob Wolman]]></dc:creator>
            <pubDate>Thu, 17 Jun 2021 06:54:39 GMT</pubDate>
            <atom:updated>2021-06-17T07:00:43.535Z</atom:updated>
            <content:encoded><![CDATA[<h4>Putting on the high hat and stepping up on the stage</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*0E7HNobHLdcQ1yGg5u1-Nw.png" /><figcaption>I have always visualized coaches with caps and managers with high hats</figcaption></figure><p>Transitioning from <a href="https://www.patkua.com/blog/maker-vs-multiplier/">maker to multiplier</a> is hard, especially if you are also taking on management responsibilities. About a year and a half ago I transitioned from agile coach to my first management position. My motivation at the time was to become a better coach by understanding the challenges of management. I wanted to learn new skills, and have an opportunity to lead, not just coach, a team. As I am now taking on the mentorship of a new manager, I am reflecting on some of the learnings I’ve had and how my work has changed.</p><p>My experience of and long-standing interest in leadership has prepared me well for a role as a manager. Far from all leaders are managers, and far from all managers are leaders. The intersection is powerful, but not enough to be a good manager, and I was new to many responsibilities of a manager.</p><h3>Changes in expectations</h3><p>The biggest change in my transition to management has been the expectations of me and the work I do. As an agile coach you are seldom responsible, or accountable for the team’s results. Being a coach is ungrateful, because success is always the team&#39;s success, and you get blamed for the failures. But you are not <em>really</em> accountable. As a manager you are. You can be tasked with reaching certain results, fulfilling goals, and meeting deadlines. With that also comes being part of the team’s success. When the team fails, you are not only blamed but also expected to remedy to make sure the failure is not repeated.</p><p>The expectation from the team is also very different. People expect you to act on problems and get them solved. They look to you for decisions, and in some cases for a detailed description of what to work on. As a coach, you can stand comfortably on the sidelines and answer most questions with “it depends”.</p><h3>Dynamics change</h3><p>With the change in expectations, the dynamics and your relationship with the team also change.</p><p>A coach can be the team&#39;s best friend. Their focus is to improve the team and the environment. Every failure is an opportunity for learning. It is easy to fall into the trap of blaming managers that are part of the system around the team as obstacles. Team members and other managers can easily trust you because you are not a threat.</p><p>Stepping into management these dynamics changes. You now have the power to make decisions that will affect people’s lives. You decide who to hire into the team, and in some cases who should leave the team. You set the salary affecting people’s financials. You also have the powers to tell people what to do, when, where, and how to work (but you’d be damned if you use these powers). People become more careful around you, especially team members. After all, you are their manager and we have been taught from early childhood to be careful around the boss (I’ve started cringing at the stereotype boss described in children’s books and films). Depending on the culture of your organization your peers might now treat you as a competitor for resources, power, fame, and glory. Luckily, at Storytel, we support each other to build the best product for our customers, rather than optimizing for our own territory.</p><p>Being a manager you should be more careful about your own behavior. Even though every leader models behavior, people are especially affected by their manager. If you set rules you’d better be the one following them thoroughly. You have to think about how your communication can be interpreted. Suggestions can be interpreted as orders, challenges as deadlines, and disagreements as a risk of being fired.</p><p>As a manager, you are now part of the system. You might have to defend decisions you disagree with, without blaming anyone else. Likewise, you will have to defend your team, even if you are frustrated with the situation.</p><h3>Building trust</h3><p>For both managers and coaches, vulnerability trust is the primary tool. You have to build trust between yourself and the team members to work effectively. Besides focusing on your own relationships you also have to create an environment where team members can trust each other. I have found that it takes more effort, and is much harder to build trust as a manager. Your team members know you hold extra power and there are more situations where you cannot share information openly.</p><p>I view trust as a currency. Once you have collected enough, you can invest some in a hard feedback session or a challenge. As a manager, you will find yourself in situations where you have to deliver hard feedback without having had a chance to build up enough trust currency to do it well. You will find yourself in tricky situations that handled well will build trust with the whole team, and handled badly will decrease the overall trust. Letting a team member go is a prime example of such a situation. Showing empathy for the team and quickly solving a painful situation will build trust, but letting someone go in a bad way will scare people and decrease trust.</p><h3>A seat at the table</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*8bOnzdXhN4DAjH2k8PXrWQ.png" /></figure><p>As a coach, it is easy to get frustrated about changes happening to you and your team. You often find yourself on the sidelines when important decisions are made that affect the whole system you are trying to improve. As a manager, you get a seat at the table. Admittedly you are not part of every big decision made in the organization, but you are always involved if the decision directly affects your team. Your opinion matters.</p><p>When I worked as a coach I often found myself in situations where I knew how to solve a problem, but simply did not have the decision power to make the change happen. Instead, I had to work through others. As a manager, I do have this power but have to use it carefully. This allowed me to <a href="https://medium.com/storyteltech/reteaming-often-57313a254247">implement a system of reteaming</a> and make other systematic decisions I was comfortable with. With power comes responsibility. A good manager has to work even harder to understand how and why people disagree and be very open about a decision being wrong.</p><p>The same dynamics described above apply to the larger organization you are a part of. As a coach, I have been part of several big change initiatives, but I was always dependent on someone else making the decision to implement the change. As a manager, I can either work with other managers to make the change happen or start by doing things differently and then inspire others to follow suit.</p><h3>Strategy vs tactics</h3><p>When working as a coach I often found time to think strategically. I could zoom out to see the bigger picture and apply systems thinking to my approach. It was easy for me to be an observer to a lot of the tactical decisions and actions going on in the team. As a manager, my calendar magically filled up with meetings. This is natural, as meetings are the arena where most management work happens. There are just so many of them. I also become involved in an endless flurry of tactical decisions and questions (Slack promotes this bad behavior). I have to fight much harder as a manager to make sure I have time to think strategically. I have also discovered that a lot of managers never do this, they are in constant firefighting mode. Even if it is easy to get caught up I have reserved time for strategic thinking, reflections, explorations, and learning. I constantly have to protect that time from being eaten up by tactical issues that need to be dealt with.</p><h3>Let it go</h3><p>In the words of a famous Disney princess — you have to let things go. As a coach, I used to step back to allow the team to fill the void, grow and learn. This was expected of me, as the coach is always temporary and should help the team improve. I am applying the same principles as a manager, but it is much more challenging. Mostly because the expectations of the outside world, and the team, are very different. The process of stepping back is slower, and the team is slower to fill the void. As the team grows I have found the act of letting go is essential to survive as a manager. It is simply impossible to be involved in all the details.</p><h3>Tips from the mentor</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/200/1*yl2FytVVJWtEJ_SFQKoDgA.png" /></figure><p>As I step into the role of mentoring a new leader I want to distill my learnings into more concrete tips and guides to make the beginning of the journey easier. I still have a lot to learn, but so far I have found the following to be useful to me:</p><ul><li><em>Lead the space, not the particles.</em> Or in Drucker’s words “Manage the system, not the people and not the work”. Make decisions and set direction on a systematic level, and not in the details.</li><li><em>Build your support system.</em> As a new manager, it is invaluable to have a support system around you. Be it a mentor, a group of peers, a coach, or someone else that you can share your pains with, discuss challenges with and look for guidance from.</li><li><em>Find your partners.</em> Find who in your team, and around your team are your partners in making decisions and implementing them. People that are willing to challenge you, but also ready to disagree and align making things happen.</li><li><em>Experiment and learn.</em> Don’t be afraid to try things out, but make sure there is a feedback loop. Run the experiment for a while, see if it works and keep adapting.</li><li><em>Use data.</em> Have a data-driven approach to your leadership, and use the data that is already available to you. Check what effects your decisions and experiments have on the data. Use both available outcome data (looking at customer behavior) but also on output data (lead time of bugs, WIP, and more).</li><li><em>Coaching is a powerful tool.</em> Use it wisely. I am applying coaching in all my 1:1 conversations, and use it for formal coaching sessions. I have also applied it in more informal settings with peers.</li><li><a href="https://www.huffpost.com/entry/rule-6_b_6166638"><em>Remember rule number 6.</em></a> That means don’t take yourself so seriously. This is one of my most valuable lessons from <a href="https://medium.com/@jakobwolman/the-art-of-possibility-and-self-leadership-1bcb8b554ce3">The art of possibility</a>. You are not more important than anyone else. Your job is just a bit different.</li></ul><h3>Finally</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/200/1*mpKHYKj_MlQC4j2eQrmH6g.png" /></figure><p>I am truly enjoying my transition into management, and I am proud of what I have achieved so far. I am constantly learning new things, and I still have a long way to go on this journey. The biggest reward is seeing team members grow, and the team reaching new heights. It’s addictive! At the same time, it is a very challenging journey, and I’ve found myself in deep waters on multiple occasions. It is not for everyone, but if you are curious about management and leadership I encourage you to find a role that allows you to safely try your wings.</p><h3>Further reading</h3><p>There are tons of leadership and management books, blogs, and podcasts. As a new manager, I’d recommend reading and listening to a lot, and then picking the raisins out of the cake. There is no single truth and no silver bullets. Learning broadly, experimenting, and learning as you go has been my approach. To me personally, these books have been valuable:</p><ul><li><a href="https://www.goodreads.com/book/show/85697.The_Art_of_Possibility">The Art Of Possibility</a> by Ben and Rosamund Zander. Best book on self-leadership. You have to be able to lead yourself before you can lead others.</li><li><a href="https://www.goodreads.com/book/show/38821039-the-making-of-a-manager">The Making Of A Manager</a> by <a href="https://medium.com/u/b8a4e5ae7490">Julie Zhuo</a>. A great introduction when you are starting your management journey.</li><li><a href="https://www.goodreads.com/book/show/6452796-drive">Drive — The Surprising Truth About What Motivates Us</a> by Dan Pink. A must-read to understand what motivates people that do creative work, and why there is a gap between what science knows and what business does.</li><li><a href="https://www.goodreads.com/book/show/16158601-turn-the-ship-around">Turn The Ship Around</a> by David Marquet is a great example of how to step back from decision making and make space for the team to fill</li><li><a href="https://www.goodreads.com/book/show/21343.The_Five_Dysfunctions_of_a_Team">The Five Dysfunctions Of A Team</a> by Patrick Lencioni. This leadership fable and model is great for understanding team dynamics, and understanding how to improve your team.</li><li><a href="https://www.goodreads.com/book/show/38900866-it-doesn-t-have-to-be-crazy-at-work">It Doesn’t Have To Be Crazy At Work</a> by <a href="https://medium.com/u/c030228809f2">Jason Fried</a> and <a href="https://medium.com/u/54bcbf647830">DHH</a> is a fresh take on the corporate world and how to work sustainably.</li><li><a href="https://www.goodreads.com/book/show/40086702-nine-lies-about-work">Nine Lies About Work</a> where Marcus Buckingham and Ashley Goodall debunk myths about work using survey data. To me, it was especially eye-opening that people care more about the team they work in and how they manager act than the organization they work for.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c493a342339c" width="1" height="1" alt=""><hr><p><a href="https://storytel.tech/becoming-a-manager-c493a342339c">Becoming a manager</a> was originally published in <a href="https://storytel.tech">Storytel Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>