<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Márk Sági-Kazár</title>
    <description>The latest articles on DEV Community by Márk Sági-Kazár (@sagikazarmark).</description>
    <link>https://dev.to/sagikazarmark</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F202199%2F97d5c809-5de2-4c19-a3ec-353aff8a8ba4.jpeg</url>
      <title>DEV Community: Márk Sági-Kazár</title>
      <link>https://dev.to/sagikazarmark</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sagikazarmark"/>
    <language>en</language>
    <item>
      <title>How to become an Open Source Software contributor?</title>
      <dc:creator>Márk Sági-Kazár</dc:creator>
      <pubDate>Mon, 24 Jul 2023 23:19:10 +0000</pubDate>
      <link>https://dev.to/sagikazarmark/how-to-become-an-open-source-software-contributor-7lf</link>
      <guid>https://dev.to/sagikazarmark/how-to-become-an-open-source-software-contributor-7lf</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sagikazarmark.hu/blog/how-to-become-an-open-source-software-contributor/"&gt;https://sagikazarmark.hu&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;A question I often get from people—both from beginners stepping into the world of programming and seasoned developers alike—is about how they can start contributing to open source software projects. In this post, my aim is to provide practical advice that will guide you to become not only an active participant in the open source community but also a significant contributor to it.&lt;/p&gt;

&lt;p&gt;There's a certain allure to the idea of open source, not only because of its collaborative nature that allows for diverse minds to come together, but also the ability to actively make a difference in the software tools that many of us use daily. No wonder so many people aspire to be a part of this innovative community.&lt;/p&gt;

&lt;p&gt;Unfortunately, a perceived high bar often deters people from becoming contributors. This perception is misguided, as many fail to realize the significant value they can bring to a project simply by participating or providing feedback. The common belief that meaningful contributions must consist of hundreds of lines of code could not be further from the truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  My journey in the world of open source software
&lt;/h2&gt;

&lt;p&gt;I've spent most of my career working on open source software projects. I consider myself fortunate, as not only did working on open source projects shape my career in IT, but it also evolved into a hobby over the years. I've invested countless hours in numerous side-projects, some of which have gained significant popularity over time, and I've made contributions to every project that crossed my path.&lt;/p&gt;

&lt;p&gt;While it may seem as though I'm boasting, this journey has in fact been quite a humbling experience. Beyond learning that I'm rarely the smartest person in the room, it provided perspective on what truly matters in the lifespan of an open source software project. Of course, coding and discussions play a crucial role, but as time progresses, things such as high-quality bug reports, timely feature requests, up-to-date documentation, responding to issues, and building a community become just as important as anything else.&lt;/p&gt;

&lt;p&gt;As a maintainer, I've come to appreciate all kinds of contributions. As a contributor, I've learned various ways to give back to the community.&lt;/p&gt;

&lt;p&gt;Over the years, I've helped many people become contributors and then maintainers in open source projects. I've witnessed their struggles and triumphs, and I've supported them in overcoming their fears and achieving remarkable feats. Drawing on this experience, allow me to share a few tips that could set you on the path to becoming a valuable contributor to this great community.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can check out some of the projects I'm involved in on &lt;a href="https://github.com/sagikazarmark"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is this guide for?
&lt;/h2&gt;

&lt;p&gt;This guide was crafted with a particular audience in mind, but it holds beneficial insights for a broad range of individuals. If you've ever felt a pull towards contributing to open source projects, this guide is for you. It can serve as a resource for those who wish to dip their toes into the open source waters occasionally, opening an issue or submitting a pull request from time to time. However, the primary focus is on those individuals who wish to become active contributors within a project.&lt;/p&gt;

&lt;p&gt;What sets this guide apart is its focus on interactions and activities that can bring you closer to becoming an integral part of an open source community. Rather than getting lost in the technical weeds, such as how to use Git or GitHub, I've emphasized the human element, which is often the most challenging yet rewarding aspect of the journey.&lt;/p&gt;

&lt;p&gt;While a certain degree of technical knowledge is undoubtedly beneficial, open source communities are inherently diverse and encompass a vast array of skill levels, from beginners to experts. Whether you're an experienced coder or a newcomer to the programming world, your unique perspective and input can contribute to the growth and success of the project.&lt;/p&gt;

&lt;p&gt;This guide can provide you with the roadmap you need to navigate the open source landscape, helping you build connections, share your skills, and become an active participant in a project that resonates with you. It is a guide for the enthusiastic, the curious, and the committed who are ready to embark on their open source journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the right project
&lt;/h2&gt;

&lt;p&gt;Many people hesitate to contribute to open source projects simply because they're unsure of where to begin. This uncertainty often forms a mental barrier that keeps them from becoming valuable contributors. Although taking the first step can seem daunting, the task itself is fairly straightforward: finding the right project to contribute to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with a project you already use
&lt;/h3&gt;

&lt;p&gt;One of the best ways to start contributing to open source is to begin with a project or software that you already use. You might wonder why this is advised, and the answer lies in familiarity and vested interest.&lt;/p&gt;

&lt;p&gt;By virtue of being a user, you are already familiar with the software. You know how it works, you know its strengths, and, more importantly, you are aware of its weaknesses. This familiarity allows you to identify potential areas of improvement and therefore contributions that you could make.&lt;/p&gt;

&lt;p&gt;Furthermore, when you're a regular user of a project, you have a vested interest in its progress and development. This means you'll likely feel more invested and motivated to improve it because it directly affects your own work or hobbies. This personal connection can serve as a strong motivator, particularly when you're navigating the challenges and learning curve associated with your first open source contributions.&lt;/p&gt;

&lt;p&gt;Contributing to a project you already use allows you to become an active participant in the development of the tools that help you in your daily tasks, giving you a sense of accomplishment and belonging in the open source community.&lt;/p&gt;

&lt;h3&gt;
  
  
  Find a project that falls into your area of expertise
&lt;/h3&gt;

&lt;p&gt;The tools you use daily may not necessarily align with your interests or fall within your area of expertise. That's completely okay, and it's where this next piece of advice comes in handy.&lt;/p&gt;

&lt;p&gt;While familiarity with a project is a great advantage, another crucial aspect to consider is your expertise and passion. By choosing a project related to your field of knowledge or an area you're passionate about, your contributions will not only feel more rewarding, but they'll also be of greater value to the project.&lt;/p&gt;

&lt;p&gt;For instance, if you're an expert in machine learning, there are many open source projects where your insights could be incredibly beneficial. Likewise, if you're a cybersecurity specialist, there are numerous initiatives focusing on developing secure, open source software where your expertise would be highly valuable.&lt;/p&gt;

&lt;p&gt;Perhaps you're a web developer who has mastered the art of creating accessible digital content. There are countless projects looking to improve their accessibility that could use your skills. Or maybe you're a tech writer who has a knack for creating clear, concise, and helpful documentation—skills that are always in high demand in open source projects.&lt;/p&gt;

&lt;p&gt;Even if the project you choose isn't directly related to your day job, that's perfectly fine. What matters is that you are interested and motivated to contribute. Remember, open source is not only about coding; it's about collaboration, sharing, and learning. By aligning your project with your passions and areas of expertise, you can ensure that your contributions are fulfilling and impactful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask a friend or colleague for tips
&lt;/h3&gt;

&lt;p&gt;Knowing the first step to becoming a contributor—finding the right project—is one thing, but actually choosing or discovering the right one isn't always straightforward. The vast expanse of the open source universe can be both exhilarating and overwhelming, making the selection process somewhat daunting for novices.&lt;/p&gt;

&lt;p&gt;Over the years, I've had countless people approach me asking which projects are good candidates for aspiring contributors. They seek my advice because they know I'm familiar with various open source projects and have insights into which ones are welcoming to newcomers, which align with specific areas of expertise, and which are actively seeking more contributors.&lt;/p&gt;

&lt;p&gt;In fact, leveraging your network can be an excellent way to identify potential projects. While there's a certain sense of satisfaction that comes from finding a project on your own, don't hesitate to ask for suggestions from friends, colleagues, or even online communities dedicated to open source software. This simple step could save you hours of searching and potentially point you towards projects you might not have discovered on your own.&lt;/p&gt;

&lt;p&gt;Your acquaintances might be privy to information that can guide you in the right direction, including the current needs of certain projects, their community's friendliness towards newcomers, or whether the project's goals align with your interests. It's also a great opportunity to learn about their personal experiences, challenges they've faced, and how they've navigated their own open source journeys.&lt;/p&gt;

&lt;p&gt;Remember, open source is all about collaboration and community. Don't be shy about reaching out for help—doing so might just lead you to the perfect project that will kickstart your open source journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get involved
&lt;/h2&gt;

&lt;p&gt;At this point, you should already have a few strong candidates that you want to contribute to. The next step is introducing yourself to the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Become active in the community
&lt;/h3&gt;

&lt;p&gt;In the open source universe, it's essential to recognize that the code is just one part of the project. A large, and arguably equally important, component is the community that forms around it. Most open source projects have various channels where individuals involved in the project interact with each other. Nowadays, these are often Slack or Discord channels (though some of the old school projects may still be on IRC). Additionally, they have issue trackers, forums, and various other communication channels.&lt;/p&gt;

&lt;p&gt;The participants in these channels range from core maintainers and occasional contributors to end users. Each person, regardless of their role, has a unique perspective that can provide invaluable insights.&lt;/p&gt;

&lt;p&gt;Engage with the community. Observe how people interact, participate in discussions, answer questions from those seeking help, and don't be afraid to ask your own. If there's a space for introductions, take a moment to share a bit about yourself and why you're there. This not only helps the community get to know you, but also demonstrates your interest in and commitment to the project.&lt;/p&gt;

&lt;p&gt;Most communities welcome active members with open arms. Your involvement may start as answering questions or participating in conversations, but this initial engagement can quickly evolve into more substantial contributions. Remember, open source communities are a rich source of knowledge, mentorship, and support, and they play a critical role in the project's evolution.&lt;/p&gt;

&lt;p&gt;Active participation in the community paves the way to more meaningful contributions and can significantly enrich your open source journey.&lt;/p&gt;

&lt;h3&gt;
  
  
  Join community meetings
&lt;/h3&gt;

&lt;p&gt;While active participation in community channels is a critical first step in getting involved in open source projects, attending community meetings holds its unique significance and deserves special mention.&lt;/p&gt;

&lt;p&gt;A common misconception is that community meetings are solely for maintainers or serious contributors. However, I invite people holding this belief to consider the name "community meeting". These meetings are public for a reason - to give the broader community a chance to follow the project more closely, irrespective of their contribution levels.&lt;/p&gt;

&lt;p&gt;Community meetings are an excellent opportunity to introduce yourself to the maintainers and the community via video call, adding a personal touch that text-based interaction might lack. This setting allows you to detail your use cases or elaborate on the issues you're facing, overcoming the constraints of written communication.&lt;/p&gt;

&lt;p&gt;Additionally, attending these meetings can provide a greater sense of belonging, making you feel more connected to the project and its progress. This experience can spark further motivation to participate actively, further solidifying your role within the community.&lt;/p&gt;

&lt;p&gt;In sum, community meetings are a valuable avenue for engagement in open source projects. They offer unique opportunities for personal interaction, deeper understanding, and a stronger sense of involvement. So next time you see an invitation to a community meeting, don't hesitate to attend. Your participation could make a significant difference, both to your journey and to the project's success.&lt;/p&gt;

&lt;h3&gt;
  
  
  Report bugs or request features
&lt;/h3&gt;

&lt;p&gt;A common misconception is that contributions to an open source project must always involve code. In reality, many first contributions are bug reports or feature requests. While these might seem less significant than submitting code, they play a crucial role in improving the project, and it's often where maintainers form their initial impressions about new contributors.&lt;/p&gt;

&lt;p&gt;People frequently open issues when they encounter a problem or need a specific feature, focusing on their personal needs. While this is understandable, it's essential to remember that maintainers are not your free support. They may cooperate with you to resolve a bug or implement a feature, but the ultimate goal is to benefit the entire project, not just individual needs.&lt;/p&gt;

&lt;p&gt;When opening an issue, whether you're reporting a bug or requesting a feature, always check if someone else has already opened a similar issue. This includes taking the time to look through closed issues. If an issue already exists, feel free to comment on it with more context or additional use cases, but avoid sending +1s or messages urging for a quicker resolution. It's perfectly acceptable to ask for an update on a stale issue, but only do so if the conversation seems genuinely stalled to avoid triggering any unwanted reactions.&lt;/p&gt;

&lt;p&gt;If you find a rejected feature request but believe you have a compelling argument for a slightly different use case, don't hesitate to open a new issue. Ensure to link back to the old one and clearly explain how your use case differs. Always be respectful, and be prepared that your suggestion might also be rejected.&lt;/p&gt;

&lt;p&gt;If you can't find an existing issue that addresses your particular bug or feature request, go ahead and open a new one. Provide as many details as possible. Someone on the other side will have to read your issue and figure out how to handle it. Merely stating that something isn't working or that you need something without sufficient details may not elicit the response you're hoping for.&lt;/p&gt;

&lt;p&gt;Overall, approach each interaction with respect and empathy, and remember to provide as many details as possible. The maintainers likely have to navigate through dozens of issues each week, so your consideration will go a long way in fostering a positive relationship with them. This could be the stepping stone to your journey as a valued contributor in the world of open source software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Familiarize yourself with project guidelines
&lt;/h3&gt;

&lt;p&gt;At the risk of seeming tedious, there comes a time in your open source journey where you need to become intimately familiar with the project's rules. As your involvement in the project deepens, understanding these guidelines becomes crucial to your ongoing participation.&lt;/p&gt;

&lt;p&gt;Whether it's the project's Code of Conduct (a regrettable necessity in today's world), contribution guidelines (including specific information required for bug reports and feature requests), or development protocols (how to run the project, write tests, etc.), these rules form an essential part of the project's operations.&lt;/p&gt;

&lt;p&gt;Typically, these guidelines are documented in the project's README, contribution guidelines, or dedicated documentation. Take the time to familiarize yourself with these documents; they provide the framework for successful participation in the project.&lt;/p&gt;

&lt;p&gt;While following these rules may initially seem daunting, remember to empathize with the maintainers. These rules are in place to streamline their work and maintain the project's consistency and quality, tasks that are no small feat given the open source nature of the project.&lt;/p&gt;

&lt;p&gt;Becoming conversant with these guidelines will not only ease your future contributions but also establish and reinforce the maintainers' trust and respect in you. It marks you as a committed member of the community, willing to adhere to the shared principles that underpin the project's success. So don't overlook this vital step – the benefits are manifold and rewarding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Triage issues and review Pull Requests
&lt;/h3&gt;

&lt;p&gt;Each week, I receive hundreds of emails from GitHub. The majority of these emails are about new issues or pull requests that someone has submitted to a project I maintain. Assessing these issues, figuring out what they entail, and deciding whether they are valid or not (you'd be surprised how many issues I have to close immediately) all take a significant amount of time.&lt;/p&gt;

&lt;p&gt;Let's do some quick math. Suppose it takes 10-20 minutes to triage an issue. That involves deciding whether it's a duplicate or not (which means I either recall having seen a similar issue or manually search for it, rather than the submitter doing so), determining if the feature request or bug report is valid, and deciding on the best course of action (finding a workaround, scheduling it for fixing or implementation, directing the user to the documentation, etc). This is just the triage; we're not even talking about fixing the bug or implementing the feature. By the above calculation, I can triage about 4 issues every hour, on average.&lt;/p&gt;

&lt;p&gt;Remember when I mentioned earlier that I receive hundreds of emails from GitHub each week? Let's say 40 of those are new issues. That would require 10 hours of my time just to triage them. And that's before we even get to fixing the bugs or implementing the features.&lt;/p&gt;

&lt;p&gt;Ask yourself, when was the last time you spent 10 hours a week responding to other people's problems or requests (outside of your day job)?&lt;/p&gt;

&lt;p&gt;I might have exaggerated the above slightly (although I'm not entirely sure about that), but it's clear that maintaining an open source project demands a lot of time. And maintainers often spend that time simply managing issues. Wouldn't it be better if maintainers could focus on more significant issues and features?&lt;/p&gt;

&lt;p&gt;Well, here's a surprise: &lt;strong&gt;you&lt;/strong&gt; can help make that happen.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you see a duplicate issue? Link to the original and ask the submitter to close the duplicate if possible.&lt;/li&gt;
&lt;li&gt;Do you see an incomplete bug report or feature request? Ask the submitter to provide the missing details.&lt;/li&gt;
&lt;li&gt;Do you suspect a bug report is inaccurate? Try to reproduce the problem and share your findings.&lt;/li&gt;
&lt;li&gt;Do you notice a violation of the contributing guide? Ask the submitter to correct their mistake.&lt;/li&gt;
&lt;li&gt;Do you see a new pull request without tests? Ask the submitter to write tests.&lt;/li&gt;
&lt;li&gt;Even better, go ahead and test the changes yourself to ensure they work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are countless ways you can help out with issues or pull requests. Many people think this is something only maintainers can do, but as a maintainer, there's nothing more satisfying than seeing the community collaborate on issues and pull requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step on the gas
&lt;/h2&gt;

&lt;p&gt;Congratulations: you became an Open Source Contributor. And I bet what comes next is not going to be nearly as scary as you thought before.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open your first Pull Request
&lt;/h3&gt;

&lt;p&gt;Determining the right time to open your first pull request varies from person to person and project to project. If you're an absolute beginner, I recommend going through the previously mentioned steps before opening pull requests, especially if you're aiming to be a long-term contributor.&lt;/p&gt;

&lt;p&gt;However, sometimes opening a pull request without immersing yourself fully in the community is the right move. For instance, I often find myself opening PRs to fix typos in projects on a weekly basis. Simply submitting a PR with a quick fix is occasionally the best course of action.&lt;/p&gt;

&lt;p&gt;Generally, though, you'll want to complete at least a few of the aforementioned steps before submitting your first pull request. By this time, you should have built some reputation with the project maintainers who, ideally, trust your judgement and commitment to the project.&lt;/p&gt;

&lt;p&gt;I usually advise starting with a smaller task, such as writing a documentation page for a feature, crafting a test to cover an edge case (or to prove a bug), or implementing a minor change to an existing feature. Small changes are easier to review and merge, providing you with a sense of achievement that can fuel your motivation to contribute further.&lt;/p&gt;

&lt;p&gt;By this stage, you should be familiar with the project's contribution guidelines (and the degree to which they are enforced). Try to adhere to these guidelines as closely as possible to make it easier for the maintainers to review and approve your changes.&lt;/p&gt;

&lt;p&gt;When you ask for a review and receive feedback, remain humble, patient, and respectful. Maintainers often have a unique perspective on the project that you may lack, leading to decisions you may not agree with. You're free to respectfully challenge their views, provide additional input, or request further information if you don't understand or agree with a particular decision. However, always remember that maintainers prioritize the needs of the project, and they likely have a better understanding of what those needs are.&lt;/p&gt;

&lt;p&gt;If all goes well (and why wouldn't it?), your first pull request will be straightforward to review and should be merged relatively quickly.&lt;/p&gt;

&lt;p&gt;Once again, congratulations! Although you were already a contributor, you now have your own commits in the repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep up the good work
&lt;/h3&gt;

&lt;p&gt;Congratulations on having your first pull request merged! However, your journey as a contributor to an open source project doesn't end here. It is just the beginning. The real magic lies in consistency and continuous effort.&lt;/p&gt;

&lt;p&gt;Maintaining the momentum is crucial. Keep engaging with the community, asking and answering questions, participating in discussions, and contributing to the codebase. Remember that open source contribution is not just about code; it's about making the project better. That could mean improving documentation, fixing bugs, implementing new features, or helping others to use the project more effectively.&lt;/p&gt;

&lt;p&gt;Remember to continue following the project's guidelines and respect the decisions of the project maintainers. Disagreements may arise from time to time, but it's essential to navigate these moments with grace and respect for all parties involved. Open source is a space for collaboration and shared growth, so strive to learn from each experience and interaction.&lt;/p&gt;

&lt;p&gt;Every contribution you make, no matter how small, adds up and makes a difference. Over time, your continued involvement can lead to more substantial contributions and a deeper understanding of the project. You may find yourself guiding new contributors, becoming a trusted reviewer, or maybe even a project maintainer someday.&lt;/p&gt;

&lt;p&gt;As you gain experience, you might want to challenge yourself with more complex tasks or contribute to other projects that align with your interests or professional goals. Remember, every contribution counts, and your growth is the true testament to your dedication to the open source community.&lt;/p&gt;

&lt;p&gt;So, keep up the good work! The open source community values your participation, and your ongoing efforts will only serve to strengthen the project and the community surrounding it. You're on an exciting journey, and the road ahead is filled with endless opportunities to learn, grow, and make a lasting impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I began writing this post following a conversation with a colleague who sought advice on how to get involved with open source projects. While my initial response was much more brief, I soon realized the potential to provide a more comprehensive guide that could benefit anyone interested in the open source world. And so, this detailed guide was born.&lt;/p&gt;

&lt;p&gt;Participating in open source is about more than just contributing code. It's about becoming part of a community, sharing knowledge, learning from others, and working together to create something that benefits everyone. It's a rewarding journey, filled with opportunities for personal and professional growth, and it all starts with that first step: the decision to get involved.&lt;/p&gt;

&lt;p&gt;Whether you're a seasoned developer or a newcomer to the world of programming, there's a place for you in the open source community. The journey may seem daunting at first, but with patience, persistence, and a willingness to learn, you can become a valuable contributor to any project you choose.&lt;/p&gt;

&lt;p&gt;Keep in mind that this is not a strict blueprint that you must adhere to, but a set of guidelines and recommendations based on my own experiences and observations. Every open source project is unique, with its own set of rules, community, and culture, and your journey may look different based on these factors.&lt;/p&gt;

&lt;p&gt;I hope that you found this guide helpful, and that it serves as a useful resource as you embark on your open source journey. Remember, every contribution, no matter how small, makes a difference. The world of open source is vast and varied, and it's waiting for you to dive in and make your mark.&lt;/p&gt;

&lt;p&gt;Happy contributing!&lt;/p&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>Go adds structured logging to its standard library…but should it?</title>
      <dc:creator>Márk Sági-Kazár</dc:creator>
      <pubDate>Fri, 31 Mar 2023 11:46:35 +0000</pubDate>
      <link>https://dev.to/sagikazarmark/go-adds-structured-logging-to-its-standard-librarybut-should-it-27fj</link>
      <guid>https://dev.to/sagikazarmark/go-adds-structured-logging-to-its-standard-librarybut-should-it-27fj</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sagikazarmark.hu/blog/go-adds-structured-logging-to-its-stdlib/"&gt;https://sagikazarmark.hu&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The Go programming language has recently &lt;a href="https://github.com/golang/go/issues/56345#issuecomment-1470506816"&gt;accepted&lt;/a&gt; a &lt;a href="https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.md"&gt;proposal&lt;/a&gt; to add a structured logging package to its standard library. This long-awaited addition is expected to land in a future version of the language, sparking excitement and debate among developers.&lt;/p&gt;

&lt;p&gt;Before diving into the pros and cons of this decision, let's first take a look at the history of structured logging in Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured logging in Go so far
&lt;/h2&gt;

&lt;p&gt;Structured logging has been a topic of discussion in the Go community for quite some time. As the language gained popularity and more developers started using it to build increasingly complex applications, the need for a standardized, built-in structured logging solution became clear. However, &lt;a href="https://docs.google.com/document/d/1shW9DZJXOeGbG9Mr9Us9MiaPqmlcVatD_D8lrOXRNMU/edit#heading=h.2yd85cpp0hby"&gt;previous attempts&lt;/a&gt; to address this issue within the Go standard library have not been successful.&lt;/p&gt;

&lt;p&gt;The absence of a standardized structured logging solution led to the emergence of a plethora of third-party libraries, each aiming to fill the void. While these libraries have provided developers with viable options for implementing structured logging in their applications, their coexistence has also contributed to fragmentation within the Go ecosystem.&lt;/p&gt;

&lt;p&gt;This fragmentation has led to several problems that hinder the development and maintenance of Go applications.&lt;/p&gt;

&lt;p&gt;First and foremost, the lack of a standardized approach to structured logging has resulted in difficulties in interoperability between different Go projects. For instance, when two or more projects that use different logging libraries need to be integrated, developers often face the challenge of bridging the gap between the disparate logging solutions.&lt;/p&gt;

&lt;p&gt;Moreover, this fragmentation has created a lack of consensus on best practices for logging within Go applications. With multiple libraries offering various features and approaches, developers are left to navigate a maze of options, often leading to inconsistencies in how logging is implemented across different projects. This lack of consistency can make it difficult for newcomers to the language to learn and adopt best practices.&lt;/p&gt;

&lt;p&gt;Another issue arising from the fragmentation is the increased complexity of dependency management. When using multiple third-party logging libraries, developers may encounter compatibility issues, especially as these libraries evolve and introduce breaking changes. This can result in additional work to keep dependencies up-to-date and ensure that the logging functionality remains stable.&lt;/p&gt;

&lt;p&gt;Finally, the abundance of third-party logging libraries has led to a duplication of effort among the Go community. Instead of focusing on a unified, standardized solution, developers have spent time and resources creating and maintaining various libraries that essentially attempt to solve the same problem. This can be seen as an inefficient use of the community's collective energy and expertise.&lt;/p&gt;

&lt;h2&gt;
  
  
  All hail the new &lt;code&gt;slog&lt;/code&gt; package
&lt;/h2&gt;

&lt;p&gt;Integrating structured logging into the Go standard library offers numerous advantages and has the potential to address many of the challenges faced by the Go community thus far. The existing fragmentation within the ecosystem has led to difficulties in interoperability, a lack of consensus on best practices, increased complexity in dependency management, and a duplication of effort. By adding a structured logging package to the Go standard library, the language can take a significant step towards overcoming these issues.&lt;/p&gt;

&lt;p&gt;One of the main benefits of having structured logging built into the standard library is the potential for improved interoperability between different Go projects. With a standardized logging solution, developers no longer need to bridge the gap between disparate third-party libraries when integrating projects. This streamlined approach can reduce the friction associated with project integration and promote more seamless collaboration within the Go community.&lt;/p&gt;

&lt;p&gt;Additionally, incorporating structured logging into the Go standard library can foster a consensus on best practices for logging within Go applications. With a unified solution, developers will have clearer guidance on how to implement logging effectively and consistently across projects. This can make it easier for newcomers to the language to learn and adopt best practices, ultimately enhancing the overall quality of Go applications.&lt;/p&gt;

&lt;p&gt;The inclusion of structured logging in the Go standard library also paves the way for future improvements, such as the introduction of "structured" errors. With the foundation of a standardized logging solution in place, the language can further enhance error handling by providing more structured and actionable information when logging errors. This can improve the debugging process and overall robustness of Go libraries and applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it too good to be true? Maybe...
&lt;/h2&gt;

&lt;p&gt;While incorporating structured logging into the Go standard library offers numerous benefits, it's important to consider the challenges and downsides it may pose.&lt;/p&gt;

&lt;p&gt;One reason that previous attempts to introduce structured logging in the Go standard library have failed is the difficulty of creating a solution that caters to a wide variety of use cases. Tools included in a standard library should be as universal as possible. There have already been voices raised against the current proposal, including from prominent communities like &lt;a href="https://github.com/golang/go/issues/56345#issuecomment-1471593372"&gt;Kubernetes&lt;/a&gt;. It's crucial to acknowledge that it may be impossible to create a solution that satisfies every developer's needs, which could lead some to continue relying on third-party libraries and also raise questions about whether this feature should be included in the standard library.&lt;/p&gt;

&lt;p&gt;Another concern with adding such a solution to the Go standard library is that it is bound by the Go backward compatibility promise, which means that any feature added to the library must be maintained and supported throughout future versions of the language. This constraint makes it difficult to address design flaws or make iterative improvements to the structured logging package once it has been added to the standard library. In contrast, community-driven efforts like &lt;a href="https://opentelemetry.io/"&gt;OpenTelemetry&lt;/a&gt; can undergo multiple iterations before being marked as generally available and can even introduce breaking changes in major versions if necessary.&lt;/p&gt;

&lt;p&gt;Lastly, introducing a new structured logging package in the Go standard library does not guarantee immediate adoption within the broader ecosystem. Reusable components and libraries often support multiple versions of Go for an extended period,&lt;br&gt;
which means it could take considerable time before the new package becomes widely used. This delayed adoption can hinder the realization of the benefits that a standardized structured logging solution offers. Once again, a community-driven effort does not have this problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The addition of structured logging to the Go standard library is undoubtedly a significant step towards enhancing the language and addressing existing challenges within the ecosystem. A standardized solution has the potential to improve interoperability, promote best practices, and simplify dependency management. However, it's essential to acknowledge the hurdles that must be overcome before this feature can be successfully implemented and widely adopted.&lt;/p&gt;

&lt;p&gt;As discussed, the process of designing a universally applicable solution is challenging, and immediate adoption within the broader ecosystem is not guaranteed. It's also important to consider the constraints imposed by the Go backward compatibility promise, which can limit the ability to address design flaws.&lt;/p&gt;

&lt;p&gt;Given these concerns, community-driven projects like OpenTelemetry may provide a more suitable home for a standard structured logging solution in Go. Community-driven projects can be more flexible and adaptable, allowing for multiple iterations and even breaking changes when necessary.&lt;br&gt;
However, it's also worth recognizing that the process of adding a feature to the Go standard library often begins with experimentation and exploration within the community itself.&lt;/p&gt;

</description>
      <category>go</category>
      <category>logging</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a CI pipeline for a Go CLI application with Dagger</title>
      <dc:creator>Márk Sági-Kazár</dc:creator>
      <pubDate>Thu, 07 Jul 2022 08:37:48 +0000</pubDate>
      <link>https://dev.to/sagikazarmark/building-a-ci-pipeline-for-a-go-cli-application-with-dagger-1ik5</link>
      <guid>https://dev.to/sagikazarmark/building-a-ci-pipeline-for-a-go-cli-application-with-dagger-1ik5</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sagikazarmark.hu/blog/dagger-go-cli/"&gt;https://sagikazarmark.hu&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In my &lt;a href="https://dev.to/sagikazarmark/building-a-ci-pipeline-for-a-go-library-with-dagger-2an7"&gt;previous post&lt;/a&gt; I explained the basic steps of building a CI pipeline for a Go library with &lt;a href="https://dagger.io"&gt;Dagger&lt;/a&gt;. Let's move onto the next level: Go CLI applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Check out &lt;a href="https://github.com/sagikazarmark/dagger-go-cli"&gt;this repository&lt;/a&gt; for a complete example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; As in the previous post, I'm going to focus on Go CLI specific details and will not explain Dagger basic concepts. Please check out the &lt;a href="https://docs.dagger.io/"&gt;documentation&lt;/a&gt; for an introduction into Dagger and my &lt;a href="https://dev.to/sagikazarmark/building-a-ci-pipeline-for-a-go-library-with-dagger-2an7"&gt;previous post&lt;/a&gt; for Go related examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="https://sagikazarmark.hu/blog/dagger-go-library"&gt;previous post&lt;/a&gt; I defined four common categories of CI steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running various builds and tests in a &lt;strong&gt;build matrix&lt;/strong&gt; (eg. for multiple Go versions)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static analysis and linters&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publishing analysis results&lt;/strong&gt; (eg. code coverage) to a third-party service&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publishing artifacts&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One could argue that the last two categories are actually the same, but they usually serve different audiences, so I prefer listing them separately.&lt;/p&gt;

&lt;p&gt;Most steps of the Go library use case fit into the first three categories. While they might still be present in the pipeline for a CLI tool, the most important step for this use case is building and publishing artifacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prior art: GoReleaser
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://goreleaser.com/"&gt;GoReleaser&lt;/a&gt; is the standard tool in the Go ecosystem for building and publishing Go CLI applications. It's capable of building, packaging and publishing CLI applications in several package formats to several artifact stores.&lt;/p&gt;

&lt;p&gt;GoReleaser itself is a CLI application (written in Go) which makes it a good candidate for being integrated into a Dagger pipeline.&lt;/p&gt;

&lt;p&gt;To do that, create a new CUE file for the action definition (in &lt;code&gt;ci/goreleaser/release.cue&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Release Go binaries using GoReleaser
#Release: {
    // Source code
    source: dagger.#FS

    // GoReleaser version
    version: *"1.10.1" | string

    _image: docker.#Pull &amp;amp; {
        source: "index.docker.io/goreleaser/goreleaser:v\(version)"
    }

    go.#Container &amp;amp; {
        name: "goreleaser"
        entrypoint: []
        "source": source
        input:    _image.output
        command: {
            name: "goreleaser"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly to other Go related actions, we use the &lt;code&gt;go.#Container&lt;/code&gt; definition to make use of all the caches and mounts set in it.&lt;/p&gt;

&lt;p&gt;Next, create a new action in your Dagger plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;release: goreleaser.#Release &amp;amp; {
    source: client.filesystem["."].read.contents

    env: {
        if client.env.GITHUB_TOKEN != _|_ {
            GITHUB_TOKEN: client.env.GITHUB_TOKEN
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, add and commit all pending changes, tag a new version and run &lt;code&gt;GITHUB_TOKEN=&amp;lt;TOKEN&amp;gt; dagger do release&lt;/code&gt; to publish a new release to GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom release pipeline
&lt;/h2&gt;

&lt;p&gt;Looking closer at what GoReleaser does under the hood, it's possible to do with a "pure" Dagger pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build the application for multiple targets/platforms (build matrix)&lt;/li&gt;
&lt;li&gt;Package the binaries and additional files (eg. &lt;code&gt;README.md&lt;/code&gt;) into archives/distro packages&lt;/li&gt;
&lt;li&gt;Push the resulting packages to artifact stores (eg. GitHub Releases)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build the application
&lt;/h3&gt;

&lt;p&gt;Let's start with the build step. Similarly to running tests for a library, you can use Cue templating to setup a build matrix to support multiple OSes and architectures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build: {
    "linux/amd64":   _
    "darwin/amd64":  _
    "windows/amd64": _

    [platform=string]: go.#Build &amp;amp; {
        source: client.filesystem["."].read.contents

        package: "."

        os:   strings.Split(platform, "/")[0]
        arch: strings.Split(platform, "/")[1]

        ldflags: "-s -w"

        env: {
            CGO_ENABLED: "0"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can build the application (all variants) with &lt;code&gt;dagger do build&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Packaging
&lt;/h3&gt;

&lt;p&gt;The next step is packaging binaries and other files (eg. &lt;code&gt;README.md&lt;/code&gt;) into archives. Since there is no builtin archive action in Dagger Universe, we have to build our own.&lt;/p&gt;

&lt;p&gt;First we need to create an image with the necessary tools (in &lt;code&gt;ci/archive/image.cue&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Build an archive base image
#Image: {
    alpine.#Build &amp;amp; {
        packages: {
            bash:      _
            coreutils: _
            p7zip:     _
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need a definition for an action that creates an archive from a list of files. The action has to be able to create &lt;code&gt;.tar.gz&lt;/code&gt; files for Linux and Darwin, &lt;code&gt;.zip&lt;/code&gt; files for Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create a new archive
#Create: {
    // Source files for the archive
    source: dagger.#FS

    // Archive name
    name: string

    _image: #Image

    _sourcePath: "/src"

    bash.#Run &amp;amp; {
        input: _image.output

        script: contents: """
case "\(name)" in
    *.tar.gz | *.tgz)
        tar -czvf \(name) *
        ;;

    *.zip)
        7z a \(name) *
        ;;

    *)
        echo "Unsupported archive type"
        exit 1
        ;;
esac

mkdir -p /result
mv \(name) /result
"""

        workdir: _sourcePath
        mounts: {
            "source": {
                dest:     _sourcePath
                contents: source
            }
        }

        export: directories: "/result": _
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a next step, let's add package building to our Dagger plan!&lt;/p&gt;

&lt;p&gt;Binary archives often contain additional files, like &lt;code&gt;README.md&lt;/code&gt;, so prepare those files first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package: {
    _files: core.#Copy &amp;amp; {
        input:    dagger.#Scratch
        contents: client.filesystem["."].read.contents
        include: [
            "README.md",
            "LICENSE",
        ]
    }

    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the actual archives using the &lt;code&gt;archive.#Create&lt;/code&gt; we defined earlier. The input for each archive is the binary from the &lt;code&gt;build&lt;/code&gt; action and the additional files we prepared earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package: {
    // ...

    _archives: {
        "linux/amd64":   _
        "darwin/amd64":  _
        "windows/amd64": _

        [platform=string]: archive.#Create &amp;amp; {
            _source: core.#Merge &amp;amp; {
                inputs: [
                    _files.output,
                    if platform == "darwin/amd64" {
                        build."darwin/amd64".output
                    },
                    if platform == "linux/amd64" {
                        build."linux/amd64".output
                    },
                    if platform == "windows/amd64" {
                        build."windows/amd64".output
                    },
                ]
            }

            source: _source.output

            _os:   strings.Split(platform, "/")[0]
            _arch: strings.Split(platform, "/")[1]
            _type: string | *"tar.gz"
            if _os == "windows" {
                _type: "zip"
            }

            name: "dagger-go-cli_\(_os)_\(_arch).\(_type)"
        }
    }

    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, define the output of the &lt;code&gt;package&lt;/code&gt; action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package: {
    // ...

    _packages: core.#Merge &amp;amp; {
        inputs: [
            package._archives."linux/amd64".export.directories."/result",
            package._archives."darwin/amd64".export.directories."/result",
            package._archives."windows/amd64".export.directories."/result",
        ]
    }

    output: _packages.output
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;package.output&lt;/code&gt; will contain all three archives for the three supported platforms. You can build them running &lt;code&gt;dagger do package&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In case you want to check the content of the resulting archives, you can write them to the filesystem by adding the following to the Dagger plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client: filesystem: "./_build": write: contents: actions.package.output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Publishing the release on GitHub
&lt;/h3&gt;

&lt;p&gt;The final step is creating a release on GitHub and uploading the archives. The easiest way to do that is using the &lt;a href="https://cli.github.com/"&gt;GitHub CLI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First (as usual), we need to create an image definition (in &lt;code&gt;ci/github/image.cue&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Image: {
    version: string | *"2.13.0"

    docker.#Build &amp;amp; {
        steps: [
            docker.#Pull &amp;amp; {
                source: "index.docker.io/alpine:3.15.0@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300"
            },
            docker.#Run &amp;amp; {
                command: {
                    name: "apk"
                    args: ["add", "bash", "curl", "git"]
                    flags: {
                        "-U":         true
                        "--no-cache": true
                    }
                }
            },
            bash.#Run &amp;amp; {
                script: contents: """
apk add curl tar
curl -L https://github.com/cli/cli/releases/download/v\(version)/gh_\(version)_linux_amd64.tar.gz | tar -zOxf - gh_\(version)_linux_amd64/bin/gh &amp;gt; /usr/local/bin/gh
chmod +x /usr/local/bin/gh
"""
            },
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can create the definition for the release action (in &lt;code&gt;ci/github/release.cue&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Release: {
    // Source files
    source: dagger.#FS

    // Artifact files
    artifacts: dagger.#FS

    // Create release from this specific tag
    tag: string

    _image: #Image

    _sourcePath:   "/src"
    _artifactPath: "/artifacts"

    bash.#Run &amp;amp; {
        input: *_image.output | docker.#Image
        script: contents: "gh release create --title '\(tag)' \(tag) /artifacts/*"
        workdir: _sourcePath
        mounts: {
            "source": {
                dest:     _sourcePath
                contents: source
            }
            "artifacts": {
                dest:     _artifactPath
                contents: artifacts
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can integrate the new action into your Dagger plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;release: github.#Release &amp;amp; {
    source:    client.filesystem["."].read.contents
    artifacts: package.output

    tag: client.env.GITHUB_REF_NAME

    env: {
        if client.env.GITHUB_TOKEN != _|_ {
            GITHUB_TOKEN: client.env.GITHUB_TOKEN
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run &lt;code&gt;GITHUB_REF_NAME=SOME_TAG GITHUB_TOKEN=&amp;lt;TOKEN&amp;gt; dagger do release&lt;/code&gt; locally to create a new release or run it on GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://goreleaser.com/"&gt;GoReleaser&lt;/a&gt; is the standard tool for publishing CLI applications and it can be easily integrated into a Dagger pipeline. Based on the complexity of the pipeline, it might not worth the effort compared to just running GoReleaser on GitHub Actions, but Dagger can still be useful when it comes to debugging pipelines.&lt;/p&gt;

&lt;p&gt;Alternatively, you can choose to build your pipeline without GoReleaser which gives you more control, but requires (a lot) more work that may not be worth the investment for small projects.&lt;/p&gt;

&lt;p&gt;Personally, I'm looking forward to see which pattern becomes more popular in the Dagger community: using ready-made tools (like GoReleaser, &lt;a href="https://megalinter.github.io"&gt;MegaLinter&lt;/a&gt;, etc) integrated into Dagger or writing "pure" Dagger pipelines using lower-level tools.&lt;/p&gt;

&lt;p&gt;Make sure to check out the &lt;a href="https://github.com/sagikazarmark/dagger-go-cli"&gt;complete example&lt;/a&gt; for more details.&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>tutorial</category>
      <category>docker</category>
    </item>
    <item>
      <title>Functional options on steroids</title>
      <dc:creator>Márk Sági-Kazár</dc:creator>
      <pubDate>Sun, 26 Jun 2022 16:30:58 +0000</pubDate>
      <link>https://dev.to/sagikazarmark/functional-options-on-steroids-5dlm</link>
      <guid>https://dev.to/sagikazarmark/functional-options-on-steroids-5dlm</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sagikazarmark.hu/blog/functional-options-on-steroids/"&gt;https://sagikazarmark.hu&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Functional options is a paradigm in Go for clean and extensible APIs popularized by &lt;a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis"&gt;Dave Cheney&lt;/a&gt; and &lt;a href="https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html"&gt;Rob Pike&lt;/a&gt;. This post is about the practices that appeared around the pattern since it was first introduced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update (2022-06-26):&lt;/strong&gt; Improved &lt;code&gt;Option&lt;/code&gt; type&lt;/p&gt;




&lt;p&gt;Functional options came to life as a way to create nice and clean APIs with configuration, specifically involving optional settings. There are many obvious ways to do it (constructor variants, config struct, setter methods, etc), but they fall short when a package has a dozen options and don't produce nearly as nice APIs as functional options do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap - What are functional options?
&lt;/h2&gt;

&lt;p&gt;Normally, when you construct an "object", you do that by calling a constructor and passing the necessary arguments to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Let's ignore the fact that there are no traditional constructors in Go for a moment.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Functional options allow extending the API with optional parameters, turning the above line into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// I can still do this...&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// ...but this works too&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myOption1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myOption2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Functional options are basically variadic function type arguments that accept the constructed (or an intermediary config) type as a parameter. Thanks to the variadic nature, it's perfectly valid to call a constructor with no options at all, keeping it clean even when you want to fall back to defaults.&lt;/p&gt;

&lt;p&gt;To better demonstrate the pattern, let's take a look at a realistic example (without functional options first):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// NewServer initializes a new Server listening on addr.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding a timeout option the code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

    &lt;span class="c"&gt;// default: no timeout&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Timeout configures a maximum length of idle connection in Server.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// NewServer initializes a new Server listening on addr with optional configuration.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// apply the list of options to Server&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting API is easy to use and read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// no optional paramters, use defaults&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// configure a timeout in addition to the address&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;// configure a timeout and TLS in addition to the address&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;TLS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TLSConfig&lt;/span&gt;&lt;span class="p"&gt;{}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In comparison, here is how constructor variants and a config struct version look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// constructor variants&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServerWithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServerWithTimeoutAndTLS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TLSConfig&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;


&lt;span class="c"&gt;// config struct&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TLS&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TLSConfig&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The advantage of using functional options over constructor variants is probably obvious: they are easier to maintain and read/write. Functional options also beat config struct when there are no options passed to the constructor (empty struct),&lt;br&gt;
but in the following sections I will show more examples where a config struct may fall short.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Read the full story of functional options by following the links in the post introduction.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Functional option practices
&lt;/h2&gt;

&lt;p&gt;Functional options themselves are nothing more than functions passed to a constructor. The simplicity of just using plain functions gives flexibility and a lot of potential. Because of that, it's no surprise that quite a few practices emerged around the pattern over the years. Here is a list of what I consider the most popular and useful practices.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feel free to leave a comment if you think something is missing.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Option type
&lt;/h3&gt;

&lt;p&gt;The first thing you might want to do when applying the functional options pattern is defining a type for the option function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Option configures a Server.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this may not seem like a huge improvement, it actually makes the code more readable by using a type name instead of a function definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/*...*/&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// reads: a new server accepts an address&lt;/span&gt;
&lt;span class="c"&gt;//      and a set of functions that accepts the server itself&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;

&lt;span class="c"&gt;// VS&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/*...*/&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// reads: a new server accepts an address and a set of options&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another advantage of having an option type is that &lt;a href="https://godoc.org/"&gt;Godoc&lt;/a&gt; organizes the option functions under the type:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KmQkhRim--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z3kh5mkex12w7fehjmk7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KmQkhRim--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z3kh5mkex12w7fehjmk7.png" alt="Option type index" width="752" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A better Option type
&lt;/h3&gt;

&lt;p&gt;Unfortunately, the &lt;code&gt;Option&lt;/code&gt; type above introduces a serious flaw into the API: it allows changing the configuration after initialization, thanks to the exported function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;999&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// potential race condition&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are several ways to solve this problem without losing any of the advantages of the &lt;code&gt;Option&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;The easiest one is declaring the option function as a non-exported type first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// option configures a Server...&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// ...but the outside world doesn't know that.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another solution is using a non-exported type for the receiver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;serverOptions&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Option configures a Server.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;serverOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is obviously a bit more work, but it also lets you deal with defaults more elegantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;serverOptions&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// call this function when initializing Server.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;serverOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;getTimeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt; &lt;span class="c"&gt;// default timeout is 10 seconds&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third (and most advanced) solution is making the &lt;code&gt;Option&lt;/code&gt; type an interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Option configures a Server.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read on to learn more about this solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option list type
&lt;/h3&gt;

&lt;p&gt;Usually, functional options are used for creating a single instance of something, but that's not always the case. Reusing a list of default options when creating multiple instances is not uncommon either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;defaultOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="n"&gt;server1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxConnections&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;server2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RateLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;server3&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not quite readable code though and the point of using functional options is having friendly APIs. Luckily, there is a way to simplify it. We just have to make the &lt;code&gt;[]Option&lt;/code&gt; slice an &lt;code&gt;Option&lt;/code&gt; itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Options turns a list of Option instances into an Option.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After replacing the slice with the &lt;code&gt;Options&lt;/code&gt; function the above code becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;defaultOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;server1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxConnections&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;server2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RateLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;server3&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;With&lt;/code&gt; / &lt;code&gt;Set&lt;/code&gt; option name prefix
&lt;/h3&gt;

&lt;p&gt;Options are often complex types, unlike a timeout or a maximum number of connections. For example, the server package might define a &lt;code&gt;Logger&lt;/code&gt; interface as an option (and fall back to a noop logger by default):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Logger&lt;/code&gt; as a name obviously cannot be used for the option as it's already taken by the interface.&lt;br&gt;
&lt;code&gt;LoggerOption&lt;/code&gt; could work, but it's not really a friendly name. When you look at the constructor as a sentence though, the word &lt;strong&gt;with&lt;/strong&gt; comes to mind, in our case: &lt;code&gt;WithLogger&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;WithLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// reads: create a new server that listens on :8080 with a logger&lt;/span&gt;
&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WithLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another common example of a complex type option is a list (slice) of values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;

    &lt;span class="n"&gt;whitelistIPs&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;WithWhitelistedIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whitelistIPs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whitelistIPs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WithWhitelistedIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"10.0.0.0/8"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;WithWhitelistedIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"172.16.0.0/12"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the default behavior is usually append instead of set which aligns with the fact that &lt;strong&gt;with&lt;/strong&gt; rather suggests addition to a list than overwriting it. If you need to overwrite the existing set of values, you can use the &lt;strong&gt;set&lt;/strong&gt; word in the option name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SetWhitelistedIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whitelistIPs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;WithWhitelistedIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"10.0.0.0/8"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;WithWhitelistedIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"172.16.0.0/12"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;SetWhitelistedIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"192.168.0.0/16"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// overwrites any previous values&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, an option for prepending can easily be created if necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  The preset pattern
&lt;/h3&gt;

&lt;p&gt;Specific use cases are often generic enough for supporting them in a library. In case of configuration, this could mean a set of options being grouped together and used as a preset for a use case. In our example, &lt;code&gt;Server&lt;/code&gt; might have a public and an internal use case that configures timeouts, rate limits, number of connections, etc differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// PublicPreset configures a Server for public usage.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;PublicPreset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;MaxConnections&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// InternalPreset configures a Server for internal usage.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;InternalPreset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;WithWhitelistedIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"10.0.0.0/8"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While presets can be useful in a few cases, they probably have more value in internal libraries and less value in public, generic libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Default values vs default preset
&lt;/h3&gt;

&lt;p&gt;In Go, empty values always have a default. For numbers it's generally zero, for boolean values it's &lt;code&gt;false&lt;/code&gt;, and so on. It's considered to be a good practice to rely on default values in optional configuration. For instance, zero value should mean unlimited timeout instead of "no timeout" (which is usually pointless).&lt;/p&gt;

&lt;p&gt;In some cases though the zero value is not a good default. For example, the default value of a &lt;code&gt;Logger&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt; which would lead to panics (unless you guard log calls with conditional checks).&lt;/p&gt;

&lt;p&gt;In those cases setting a value in a constructor (before applying options) is a good way to define a fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;noopLogger&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// apply the list of options to Server&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've seen examples for having a default preset (using the pattern explained in the previous section). However, I don't consider that a good practice. It's much less expressive than just simply setting default values in the constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// what are the defaults?&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DefaultPreset&lt;/span&gt;&lt;span class="p"&gt;()},&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// apply the list of options to Server&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Config struct option
&lt;/h3&gt;

&lt;p&gt;Having a &lt;code&gt;Config&lt;/code&gt; struct as a functional option is probably not so common, but it's not without precedent either. The idea is that functional options reference a config struct instead of referencing the actual object being created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;

    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is useful when you have tons of options and creating a config struct seems cleaner than listing all of them in a function call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
    &lt;span class="c"&gt;// lots of other options&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another use case for this pattern is setting defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
    &lt;span class="c"&gt;// lots of other options&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced patterns
&lt;/h2&gt;

&lt;p&gt;After writing dozens of functional options, you might start to wonder if there is a better way to do it. Not from a consumer point of view, but from the maintainer's perspective.&lt;/p&gt;

&lt;p&gt;For example, what if we could define types and use those as options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;

&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Notice how the API from the consumer's point of view remains the same)&lt;/p&gt;

&lt;p&gt;It turns out that by changing the &lt;code&gt;Option&lt;/code&gt; type we can easily do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Option configures a Server.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// apply is unexported,&lt;/span&gt;
    &lt;span class="c"&gt;// so only the current package can implement this interface.&lt;/span&gt;
    &lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redefining the option function as an interface opens the door to a number of new ways to implement functional options:&lt;/p&gt;

&lt;p&gt;A variety of builtin types can be used as options without a function wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Timeout configures a maximum length of idle connection in Server.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Option lists and config structs (seen in the previous sections) can also be redefined like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Options turns a list of Option instances into an Option.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My personal favorite though is the possibility to reuse an option in multiple constructors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ServerOption configures a Server.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ServerOption&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;applyServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ClientOption configures a Client.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ClientOption&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;applyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Option configures a Server or a Client.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ServerOption&lt;/span&gt;
    &lt;span class="n"&gt;ClientOption&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;WithLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;withLogger&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;withLogger&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;withLogger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;applyServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;withLogger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;applyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WithLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WithLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Functional options is a powerful pattern for creating clean (and extensible) APIs with dozens of options. Although it's a bit more work than maintaining a simple config struct, it provides a lot more flexibility and produces much cleaner APIs than the alternatives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis"&gt;https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html"&gt;https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sohamkamani.com/blog/golang/options-pattern/"&gt;https://www.sohamkamani.com/blog/golang/options-pattern/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.calhoun.io/using-functional-options-instead-of-method-chaining-in-go/"&gt;https://www.calhoun.io/using-functional-options-instead-of-method-chaining-in-go/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>functional</category>
    </item>
    <item>
      <title>Building a CI pipeline for a Go library with Dagger</title>
      <dc:creator>Márk Sági-Kazár</dc:creator>
      <pubDate>Tue, 21 Jun 2022 23:05:16 +0000</pubDate>
      <link>https://dev.to/sagikazarmark/building-a-ci-pipeline-for-a-go-library-with-dagger-2an7</link>
      <guid>https://dev.to/sagikazarmark/building-a-ci-pipeline-for-a-go-library-with-dagger-2an7</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sagikazarmark.hu/blog/dagger-go-library/"&gt;https://sagikazarmark.hu&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I've been playing with &lt;a href="https://dagger.io/"&gt;Dagger&lt;/a&gt; for months now using it in various projects. In this post, I'll share my experience with using Dagger to build CI pipelines for Go libraries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Check out &lt;a href="https://github.com/sagikazarmark/dagger-go-library"&gt;this repository&lt;/a&gt; for a complete example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; I'm going to focus on Go library specific details and will not explain Dagger basic concepts. Please check out the &lt;a href="https://docs.dagger.io/"&gt;documentation&lt;/a&gt; for an introduction into Dagger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go library CI
&lt;/h2&gt;

&lt;p&gt;The easiest way to evaluate Dagger for the Go library use case is to compare it to an existing solution. It's probably fair to say that GitHub Actions dominates the CI market for Open Source Software these days, so it makes sense to compare building a pipeline with Dagger to an existing GitHub Actions workflow.&lt;/p&gt;

&lt;p&gt;Here is a simple one for a Go library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;go&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.16'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.17'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.18'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Go&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-go@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.go }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go test -race -coverprofile=coverage.txt -covermode=atomic ./...&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload coverage&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov/codecov-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;coverage.txt&lt;/span&gt;

  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Go&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-go@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.18&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;golangci/golangci-lint-action@v3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In general terms it consists of the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build matrix&lt;/strong&gt; to run tests (in parallel) for different environments and settings (eg. multiple Go versions)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static analysis and linters&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publishing analysis results&lt;/strong&gt; (eg. code coverage) to a third-party service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are way more complex pipelines out there that run various static analysis tools and integrate with many more services, but most of them fall into one of the three categories above. (Actually, there is a fourth one, but that's hardly ever relevant for a Go library: &lt;strong&gt;artifact publishing&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;Let's see how we can build a pipeline with Dagger!&lt;/p&gt;

&lt;h2&gt;
  
  
  Build matrix
&lt;/h2&gt;

&lt;p&gt;Go libraries generally support at least two (or more) Go versions which means CI has to execute tests on all supported versions.&lt;/p&gt;

&lt;p&gt;Dagger allows nesting actions, so a naive implementation of a build matrix just repeats the same steps with different Go versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test: {
    "1.16": go.#Test &amp;amp; {
        source:  client.filesystem["."].read.contents
        package: "./..."

        _image: go.#Image &amp;amp; {
            version: "1.16"
        }

        input: _image.output
    }

    "1.17": // go.#Test ...
    "1.18": // go.#Test ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The different actions then can be executed either per version (in parallel CI jobs) or at the same time (eg. locally):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dagger &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;test &lt;/span&gt;1.18 &lt;span class="c"&gt;# Run tests for a single Go version&lt;/span&gt;
dagger &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;# Run tests for all Go versions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Naturally, this isn't the most optimal solution: it doesn't work well with multiple dimensions and it requires a lot of duplication even for a single dimension.&lt;/p&gt;

&lt;p&gt;An alternative solution is using templating in CUE. It reduces the amount of code and works well with multiple dimensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test: {
    "1.16": _
    "1.17": _
    "1.18": _

    [v=string]: go.#Test &amp;amp; {
        source:  client.filesystem["."].read.contents
        package: "./..."

        _image: go.#Image &amp;amp; {
            version: v
        }

        input: _image.output
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admittedly, this is not a real build matrix either as all variations have to be listed manually, but for most of the use cases it's close enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static analysis
&lt;/h2&gt;

&lt;p&gt;The Go ecosystem has an exceptionally large number of static analysis tools and chances are not all of them are going to be available in &lt;em&gt;Dagger Universe&lt;/em&gt; (Dagger's central repository of actions).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.dagger.io/1239/making-reusable-package"&gt;Writing a custom action&lt;/a&gt; allows integrating any arbitrary tool into a Dagger plan.&lt;/p&gt;

&lt;p&gt;First, make sure that &lt;code&gt;cue.mod/module.cue&lt;/code&gt; contains a module name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module: "github.com/sagikazarmark/dagger-go-library"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a new CUE file for the tool definition (eg. &lt;code&gt;ci/golangci/lint.cue&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Lint using golangci-lint
#Lint: {
    // Source code
    source: dagger.#FS

    // golangci-lint version
    version: *"1.46" | string

    // Timeout
    timeout: *"5m" | string

    _image: docker.#Pull &amp;amp; {
        source: "index.docker.io/golangci/golangci-lint:v\(version)"
    }

    go.#Container &amp;amp; {
        name:     "golangci_lint"
        "source": source
        input:    _image.output
        command: {
            name: "golangci-lint"
            flags: {
                run:         true
                "-v":        true
                "--timeout": timeout
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generally speaking, an action needs a source code input and some tool specific parameters as inputs.&lt;/p&gt;

&lt;p&gt;The action can then be added to the plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lint: {
    "go": golangci.#Lint &amp;amp; {
        source:  client.filesystem["."].read.contents
        version: "1.46"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; GolangCI has been &lt;a href="https://github.com/dagger/dagger/pull/2654"&gt;recently added&lt;/a&gt; to the alpha channel of &lt;em&gt;Dagger Universe&lt;/em&gt;, so you can use that one instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing analysis results
&lt;/h2&gt;

&lt;p&gt;It's common for libraries to publish (static) analysis results after test runs to analyze and visualize various code metrics.&lt;/p&gt;

&lt;p&gt;One of those metrics is code coverage that tells us how much of the source code was actually executed (&lt;em&gt;covered&lt;/em&gt;) by test runs. Code coverage information is generated by the &lt;code&gt;go test&lt;/code&gt; tool, so the output of the test step needs to be passed to another step.&lt;/p&gt;

&lt;p&gt;Dagger handles these kind of dependencies well, all we need to do is reference the output of one action in another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test: {
    "1.16": _
    "1.17": _
    "1.18": _

    [v=string]: {
        _test: go.#Test &amp;amp; {
            // ...
            command: flags: {
                "-covermode":    "atomic"
                "-coverprofile": "/coverage.out"
            }

            export: files: "/coverage.out": _
        }
        _coverage: codecov.#Upload &amp;amp; {
            // Merge the coverage file with the source code (for VCS information)
            _write: core.#WriteFile &amp;amp; {
                input:    client.filesystem["."].read.contents
                path:     "/coverage.out"
                contents: _test.export.files."/coverage.out"
            }

            source: _write.output
            file:   "coverage.out"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can find the definition for the &lt;code&gt;Upload&lt;/code&gt; action &lt;a href="https://github.com/sagikazarmark/dagger-go-library/blob/a8c960cb7a99cea7f00e31d989f13914cfbe39c4/ci/codecov/upload.cue"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it on GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Although we have a plan, it still needs to be executed somewhere. GitHub Actions is a perfect candidate, especially for Open Source Go libraries.&lt;/p&gt;

&lt;p&gt;Rewriting the above workflow, we can easily run Dagger actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dagger&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;go&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.16'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.17'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.18'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dagger&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dagger/dagger-for-github@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;project update&lt;/span&gt;
            &lt;span class="s"&gt;do check test go ${{ matrix.go }}&lt;/span&gt;

  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dagger&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dagger/dagger-for-github@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;project update&lt;/span&gt;
            &lt;span class="s"&gt;do check lint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is one more change we have to make to our plan: Some actions (like coverage upload) might need detailed VCS information. One option is to pass them manually to each action or let the tools collect them from the environment (eg. environment variables):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client: env: {
    CI:                string | *""
    GITHUB_ACTIONS:    string | *""
    GITHUB_ACTION:     string | *""
    GITHUB_HEAD_REF:   string | *""
    GITHUB_REF:        string | *""
    GITHUB_REPOSITORY: string | *""
    GITHUB_RUN_ID:     string | *""
    GITHUB_SERVER_URL: string | *""
    GITHUB_SHA:        string | *""
    GITHUB_WORKFLOW:   string | *""
}
actions: {
    test: {
        "1.16": _
        "1.17": _
        "1.18": _

        [v=string]: {
            _test: go.#Test &amp;amp; {
                // ...
            }
            _coverage: codecov.#Upload &amp;amp; {
                // ...

                // No need to run coverage upload unless running in CI
                dryRun: client.env.CI != "true"

                env: {
                    GITHUB_ACTIONS:    client.env.GITHUB_ACTIONS
                    GITHUB_ACTION:     client.env.GITHUB_ACTION
                    GITHUB_HEAD_REF:   client.env.GITHUB_HEAD_REF
                    GITHUB_REF:        client.env.GITHUB_REF
                    GITHUB_REPOSITORY: client.env.GITHUB_REPOSITORY
                    GITHUB_RUN_ID:     client.env.GITHUB_RUN_ID
                    GITHUB_SERVER_URL: client.env.GITHUB_SERVER_URL
                    GITHUB_SHA:        client.env.GITHUB_SHA
                    GITHUB_WORKFLOW:   client.env.GITHUB_WORKFLOW
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the &lt;a href="https://github.com/sagikazarmark/dagger-go-library"&gt;example repository&lt;/a&gt; for more details and examples (like &lt;a href="https://docs.dagger.io/1237/persistent-cache-with-dagger/#persistent-cache-in-github-actions"&gt;caching on GitHub Actions&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The first question that needs to be answered: Is it really worth it?&lt;/p&gt;

&lt;p&gt;Well, it depends.&lt;/p&gt;

&lt;p&gt;On one hand, building pipelines locally is certainly easier&lt;br&gt;
than doing so on a CI server. It's also comforting to know that if it runs locally, it'll run on the CI server.&lt;/p&gt;

&lt;p&gt;On the other hand, CI pipelines for Go libraries tend to be simple and with GitHub Actions the building blocks are as reusable as Dagger actions are. Native CI runs also tend to be a bit faster due to the lack of extra runtime initialization, but that performance hit can be minimized with caching and is less noticeable in large projects.&lt;/p&gt;

&lt;p&gt;So it really comes down to personal preference.&lt;/p&gt;

&lt;p&gt;My personal favorite feature in Dagger is its portability: I like that I can run and build it on my own machine, but portability really becomes important for complex pipelines and large projects. For a simple Go library, I'll probably stick to Makefiles and GitHub Actions.&lt;/p&gt;

&lt;p&gt;Make sure to check out the &lt;a href="https://github.com/sagikazarmark/dagger-go-library"&gt;complete example&lt;/a&gt; for more details.&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>tutorial</category>
      <category>docker</category>
    </item>
    <item>
      <title>The Perfect Development Environment 2022</title>
      <dc:creator>Márk Sági-Kazár</dc:creator>
      <pubDate>Sun, 12 Jun 2022 23:04:07 +0000</pubDate>
      <link>https://dev.to/sagikazarmark/the-perfect-development-environment-2022-enf</link>
      <guid>https://dev.to/sagikazarmark/the-perfect-development-environment-2022-enf</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sagikazarmark.hu/blog/the-perfect-development-environment-2022/"&gt;https://sagikazarmark.hu&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I'm a huge fan of Developer Experience and I continuously chase &lt;em&gt;The Perfect Development Environment&lt;/em&gt; every day. Not just for my sake, but to make the development process as painless as possible for my peers. But after pursuing the dream environment for years, I had to realize it's a moving target: technology evolves every day and development processes have to evolve as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Previously on &lt;em&gt;The Perfect Development Environment&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;I praised &lt;a href="https://github.com/sagikazarmark/makefiles"&gt;Makefiles&lt;/a&gt; above all else before. Then &lt;a href="https://sagikazarmark.hu/blog/please-build/"&gt;modern build systems&lt;/a&gt; (eg. &lt;a href="https://please.build/"&gt;Please&lt;/a&gt;) became my new religion. I'm pretty sure I believed (and preached) that PHP and NodeJS project had to use &lt;a href="https://getcomposer.org/"&gt;Composer&lt;/a&gt; and &lt;a href="https://www.npmjs.com/"&gt;NPM&lt;/a&gt; (and nothing else) as task runners at some point in my career. I even remember rewriting build scripts from &lt;a href="https://gruntjs.com/"&gt;Grunt&lt;/a&gt; to &lt;a href="https://gulpjs.com/"&gt;Gulp&lt;/a&gt;, then from Gulp to the next fancy JavaScript task runner over and over again.&lt;/p&gt;

&lt;p&gt;I've used various tools and methods in various projects over the years, but one thing was always common: whatever tools I used for development, I always aimed to use the same tools in CI to build and deploy the projects I worked on.&lt;/p&gt;

&lt;p&gt;It wasn't always easy, but it seemed to be a good practice: it reduced the maintenance burden of maintaining two sets of tools (for dev and CI) and it made building CI pipelines easier (by making them thin layers of integration around dev tools).&lt;/p&gt;

&lt;p&gt;It never occurred to me to do things the other way around, until &lt;a href="https://dagger.io"&gt;Dagger&lt;/a&gt; came along.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root of all evil: portability across platforms
&lt;/h2&gt;

&lt;p&gt;If everyone in the world used Linux on x86 I wouldn't be writing about developer experience, because there really would be only one perfect development environment for everyone to use and the world would be a much happier place.&lt;/p&gt;

&lt;p&gt;Unfortunately (or not?), this is not the case. Developers use and target various platforms during development and that makes development processes and the tooling infinitely more complex: different architectures, different package managers, different available software versions and tons of other factors contribute to making portability across environments a pain. Even on different versions of the same OS reproducing identical environments proved to be a challenge.&lt;/p&gt;

&lt;p&gt;Around 2013 Docker came to life and it quickly revolutionized development environments. It wasn't true portability of course, since the technology under Docker required Linux, but it introduced an abstraction layer that made packaging and running applications across various (Linux) platforms much easier.&lt;/p&gt;

&lt;p&gt;Nowadays, using Docker for development is trivial, even on Windows and MacOS and it essentially resolved the portability issue for a large number of use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev vs CI
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, using the same tools for development and running them in CI pipelines is not always easy, because they don't always serve the same purposes.&lt;/p&gt;

&lt;p&gt;For example: you don't necessarily want to run all test suites during development, just the ones you are working on. So in your local development environment, you need tools that can selectively run tests, but in CI you want to run them all, thus if you want to reuse the same tools in CI, you need something that supports both.&lt;/p&gt;

&lt;p&gt;But you also want to be able to run all tests locally, because that's what the CI does and that's what decides whether your change will be accepted or not at the end of the day.&lt;/p&gt;

&lt;p&gt;In other words: you want to be able to do and run all things the CI does in your local environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  New kid on the block: Dagger
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dagger.io"&gt;Dagger&lt;/a&gt; markets itself as a &lt;em&gt;"new way to create CI/CD pipelines"&lt;/em&gt; and a &lt;em&gt;"portable devkit for CI/CD pipelines"&lt;/em&gt;. The first closed beta was released earlier this year and was quickly followed by a public release in March.&lt;/p&gt;

&lt;p&gt;The idea behind Dagger is that instead of running CI pipelines on a central server (often written in a platform specific DSL), you can run them anywhere (where Docker is available). It doesn't mean you should stop running/using a CI server, but it puts building and running those pipelines in a new perspective:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if we could build, debug and run those pipelines locally?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev + CI
&lt;/h2&gt;

&lt;p&gt;So here is the novel idea (at least compared to my earlier believes): instead of integrating dev tools into CI pipelines, let's just build a pipeline that you can run both locally and on a CI server.&lt;/p&gt;

&lt;p&gt;Whenever you want to confirm that your changes adhere to the current rules enforced by the CI (aka. everything is green), you can just run the pipeline on your machine.&lt;/p&gt;

&lt;p&gt;With that taken care of, you can create a set of development tools that you only use for development. You don't have to worry about running them on the CI.&lt;/p&gt;

&lt;p&gt;To give you an example how this looks like in practice: I have a Go project where I use Dagger. I can run tests and the static analysis tools locally and on a CI service (eg. GitHub Actions). At the same time, I have a &lt;code&gt;Makefile&lt;/code&gt; (or lately, a &lt;a href="https://taskfile.dev/"&gt;Taskfile&lt;/a&gt;) for dev tasks, like formatting code (often using the same static analysis tools) or running code generators.&lt;/p&gt;

&lt;p&gt;If you want to see more examples, feel free to check out &lt;a href="https://github.com/sagikazarmark"&gt;my repositories&lt;/a&gt; on GitHub. I use Dagger combined with a task runner in more and more projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sagikazarmark/todobackend-go-kit"&gt;This&lt;/a&gt; is one of the first projects I used Dagger in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I can't say this is going to be &lt;em&gt;The Perfect Development Environment&lt;/em&gt;, because I don't believe it exists anymore. Even if it does, it's only perfect for a moment or until the next, better thing comes along.&lt;/p&gt;

&lt;p&gt;Building and running CI pipelines on their own instead of integrating dev tools into them is an exciting new perspective and I'm looking forward to trying it in large, established projects. Not having to worry about breaking builds that already passed once on my machine feels extremely comforting so far.&lt;/p&gt;

&lt;p&gt;On the other hand, Dagger is extremely young so I wouldn't start rewriting every single CI pipeline just yet.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>productivity</category>
      <category>docker</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
