dxalxmur.com

Engineering Culture at Leboncoin: A Journey Through Tools and Practices

Written on

At Leboncoin, our engineering culture has been significantly influenced by the tools we use and the practices we follow. Much like adapting to local customs when moving to a new country, understanding the rationale behind our tool choices can be perplexing when joining a new organization. Questions arise: Why select a particular framework or programming language? Why choose a specific CI/CD platform? Often, our responses focus solely on functionality, neglecting to consider the deeper implications of our choices.

Reflecting on the reasons behind the selection of certain tools is vital, especially during transformative projects. Personally, I came to this realization during a significant merger, where rapid changes prompted me to question long-standing practices. I found myself resisting these changes without fully grasping their significance. Were we adhering to outdated methods merely out of habit, or did these practices hold deeper value and influence?

Through this exploration, I learned that the decisions we make often have far-reaching consequences that shape our engineering culture. For instance, adopting a monorepo can alter team dynamics, while tools like Gerrit can redefine our approach to code reviews. In this article, I aim to share the evolution of development practices at Leboncoin and their impact on our engineering culture.

Where It All Began

Leboncoin originated over 15 years ago as a derivative of the Swedish second-hand platform, Blocket. Initially, we operated a monolithic application using C, PHP, and PostgresDB within a single SVN repository, where frontend and backend development occurred seamlessly by the same teams. This model suited our needs at the time, allowing for straightforward deployment across all components.

However, as our requirements evolved—especially with the surge of mobile applications and changing user expectations—the complexities of the monolith increased. We transitioned to a modular architecture with distinct repositories for various tech stacks, including Java for Android, Objective-C for iOS, Golang for backend microservices, and JavaScript with React for the web.

This transition raised an important question: How should we structure our repositories? Ultimately, we decided to shift to a per-tech-stack repository model. This was a natural evolution as we organized teams by function, with backend and frontend engineers working in separate teams. This structure remained unchanged for eight years.

The Path We Have Chosen

Fast forward to today, and Leboncoin is now structured into multidisciplinary teams focused on specific domains, each consisting of experts from various tech stacks. Each team is tasked with addressing challenges within their domain, utilizing separate repositories for web, Android, iOS, and backend.

Our structure draws inspiration from the Spotify model, where vertical teams concentrate on different aspects of the customer journey, supported by horizontal technical stacks. We maintain a common repository for shared practices and tools, holding guild meetings every two weeks with dedicated Stack Tech Leads to promote collaboration and standardization.

This setup fosters a sense of belonging among teams, creating a shared environment where everyone feels at home.

Everyone Has Their Own Objectives

As companies expand, integrating new engineers introduces diverse practices and challenges. In this growth phase, while companies are generating revenue, they also face competitive pressures.

During this time, the focus is on efficiency, rapid feature development, and swift progress. At Leboncoin, the CTO expressed a desire to concentrate efforts on strategic projects. Balancing this strategic focus with the engineers' need for innovation became a critical challenge.

To bridge this gap, I engaged with various teams, often chatting by the coffee machine to better understand their daily concerns. I noticed stark contrasts: teams working side by side often seemed to inhabit different worlds. The CTO's priorities sometimes diverged significantly from those of the engineers, leading to differing perspectives on issues and solutions.

This variation was both common and understandable, influenced by factors such as management styles and the nature of each team's work. Some teams, deemed technically proficient, operated independently and embraced experimentation, while others, closely interacting with Finance and Legal, adopted a more cautious approach.

The consequences of this divergence were evident. One team eagerly integrated new libraries and practices, while another, burdened by pressure, preferred established methods. This disparity risked widening the gap between teams, potentially hindering the CTO's strategic goals.

We Aren't Alone

You might be wondering where this discussion is headed. I began with monorepos and CTO project management, touching on team culture and innovation. Bear with me; I'm getting to the crux of the matter!

As we examined various approaches—particularly for our frontend stacks—we experimented with different repository and collaboration models. We tried per-module repositories, per-team setups, dedicated expert teams for cross-cutting concerns, and rotating individuals on global projects.

Despite these independent initiatives, most of our stacks gravitated towards similar models. Reflecting on our current structure, we recognized that we had minimal programming languages—almost one per stack—with each stack housed in its own repository. We were following a version of the Spotify model, with dedicated core teams tackling issues beyond the traditional product roadmap.

To gain insights, we looked at established tech giants:

  • Spotify organizes its structure around multiple repositories, allowing for a degree of freedom. It employs a Golden Path approach and has standardized to ensure consistency.
  • Amazon uses a combination of large monolithic repositories for major systems and smaller ones for well-defined services, allowing teams to deviate with justification.
  • Meta takes a similar approach to Amazon but is more flexible with technology choices.
  • Google is stricter regarding new programming languages and tools, maintaining one of the largest monorepos for critical systems.
  • Palantir utilizes multiple repositories with varying visibility and access, catering to the diverse needs of its clients.

Examining these strategies revealed that they often reflect their unique company cultures:

  • Zalando emphasizes team autonomy, employing a mix of monorepo and multirepo strategies suited to its fast-paced ecommerce environment.
  • Uber has consolidated its code into monorepos for certain platforms, promoting unified development practices.
  • Airbnb appears to balance innovation with consistency, aiming for a seamless experience for guests and hosts.

After analyzing these insights, I reevaluated our operations and what had been effective. Our guild setup encouraged shared practices, and with a common repository, collaboration on libraries became natural. We even designated individuals to maintain versioning updates.

However, some team members began to seek opportunities beyond the established framework, inspired by the open-source community. This led us to envision a scenario where libraries, services, and tools could evolve independently.

Everything Is Connected

It took time to grasp this concept fully, but I eventually understood: Every aspect of a company shapes its culture, which, in turn, influences its decisions and capabilities. Choices that may appear purely technical—such as programming language, repository structure, or tooling—can profoundly affect a company's culture. This aligns with Conway's law: the structure of your repositories, along with other technical choices, shapes communication patterns within teams, just as strategic focus guides team alignment.

If technical decisions impact culture, we must consider what culture is and how different structures influence it. The C4 model provides a framework for describing the static structure of a software system, consisting of containers (applications and data stores) that house components, which are realized through code elements (classes, interfaces, etc.).

Let’s explore various repository structures and their cultural implications.

One Repository per Component

First, let's clarify what we mean by a component, referring to technology building blocks that form an application—elements that execute, like libraries or frameworks. Imagine a scenario where each component of an application is stored in a separate repository. This model can be observed in web applications, where each UI element functions as a library, or in Spring Boot applications, which modularize various components.

This approach aligns with the Unix philosophy, advocating for well-designed interfaces and communication standards that enable composability. It’s prevalent in open-source communities due to several factors, including asynchronous collaboration and a community-driven ethos where contributions must meet established standards.

The lack of time pressure, common in open-source projects, fosters a culture of thorough code reviews and shared ownership, as contributors may not know how their work will be utilized. The project pace is dictated by its needs, creating an environment where contributors work independently while remaining connected to the broader project.

One Repository per Container

Next, consider containers, defined in the C4 model as units that operate, such as mobile apps or Docker containers. What if each container had its own repository? This structure is typical in operating systems, where different executables are installed through package managers.

Service-based architectures exemplify this model, with each service communicating via APIs. Here, containers serve specific purposes, managing their lifecycle and potentially employing diverse technologies. Collaboration is interface-driven, necessitating discussions between teams regarding behavior, akin to contract agreements.

Ownership typically resides at the team level, fostering active collaboration. While company-wide guidelines may exist, teams retain autonomy over their containers, creating a culture that encourages collaboration while respecting individual preferences.

One Repository per System

Advancing in the C4 model, we reach systems—collections of containers that interact to provide cohesive features. Imagine defining a cohesive unit at the system level, with one repository per system.

A system offers features that users or other systems can engage with. For instance, SaaS platforms or payment systems exemplify this model, where systems often collaborate to deliver value to consumers.

In this framework, systems like Kubernetes or Android could exist within their own repositories. Collaboration can be quite open, depending on the system's inclusivity, often featuring well-defined SDKs and APIs for interaction. The culture here resembles a small village—self-sufficient yet reliant on external resources.

One Repository for All Systems

Finally, consider a single repository encompassing everything. This model alters collaboration dynamics entirely, as multiple systems must work closely together, fostering a different level of trust due to shared goals.

This culture resembles a small island, where resources are limited, necessitating stronger collaboration to succeed.

Choosing the Issues We Want to Solve

Transitioning from theory to practice, our company aimed to acquire businesses to enhance user experiences across various sectors—renting flats, purchasing furniture, buying clothes, and more. Technically, this involved striving for consistency across features while enabling quick integration of new functionalities from acquired companies.

The CTO aimed for a setup that facilitated collaboration, minimized duplication, and focused teams. However, we also needed to balance this with engineers' desires to innovate and experiment.

Where should we draw the line?

The previous collaboration models guided our decision-making. We aspired for high consistency and efficiency without heavy investment in R&D, concentrating on delivering user-centric features while avoiding waste.

Ultimately, we made a significant—and somewhat controversial—decision: We would unify all services into a single repository, enforce a singular programming language, and create a cohesive setup for backend services. This approach closely aligned with the all-systems-together model, with the exception of the frontend, which presented its own challenges but was deemed a necessary compromise.

This decision, accepted by tech leads, aimed to propel the company's technical culture forward. It allowed us to:

  • Easily communicate on shared issues: With a unified technology stack, issues became comparable across teams, fostering understanding.
  • Propose common solutions: Shared tools and language increased the likelihood that solutions would meet everyone’s needs.
  • Facilitate senior engineer assistance: Senior engineers found it easier to review code and suggest improvements when operating within the same context.
  • Encourage internal mobility between teams: Engineers could transition between teams more smoothly, needing only to adapt to business contexts rather than entirely new tech stacks.
  • Simplify large-scale refactoring: Changes to logging or observability could occur atomically across all services, ensuring functionality before merging.
  • Focus tooling investments on key areas: A common environment made it easier to justify and benefit from tooling investments.

Of course, these choices came with challenges:

  • Reduced technological innovation: A singular repository often requires a high level of reusability, hindering customization for unique challenges. Introducing new technologies necessitates reworking existing tools, which can be daunting.
  • Customized tooling for large use cases: Larger repositories often require tailored experiences, which can lead to investing in custom tools that need ongoing management.
  • Adapting work methods: This setup is not typically encountered in academic settings or smaller companies, necessitating intentional efforts to adapt work styles.
  • Intimidating changes to common components: For junior engineers, modifying libraries affecting numerous services can be overwhelming. Collaboration with experienced engineers often becomes essential, as alignment among stakeholders can be challenging.

Despite these hurdles, we've benefited from maintaining the latest Go version across all services for over a decade and ensuring that even long-neglected services receive regular security updates. However, we've also faced issues, such as bugs affecting all services and complex changes requiring significant effort.

More importantly, this approach has fostered the culture we aimed to create.

In the End, We Are a Community

Ultimately, this configuration transformed our organization into a cohesive community. Senior engineers could swiftly address issues and mentor others, while shared practices promoted smoother collaboration. This unified environment nurtured a sense of belonging, with teams working collectively toward common goals despite differing daily challenges.

Senior team members facilitated large-scale changes, quickly resolving issues and assisting teams, enabling us to align practices and encourage teams under pressure to adopt new libraries or standards. With everyone speaking the same language, presentations and code reviews resonated across the organization, inspiring others to adopt or enhance solutions.

Living under the same roof meant adhering to shared rules, even as individual teams retained the freedom to design their services. This structure encouraged collaboration and sometimes prompted team members to assist others when needed. If you need to update consumers on a new API version, why not take the initiative? You’re the one who will benefit the most. While the other team would need to validate changes, the new shared practices ensured that no one felt out of their depth in assisting one another. This cultivated a sense of shared ownership—why wouldn't you lend a hand?

I did observe some resistance to this model from newcomers, which is understandable. Transitioning from a lifetime of independence to a shared living space can be daunting. Integrating into a community presents challenges, as evidenced by many news stories. Yet, I firmly believe that sharing struggles, alongside joys, fosters connections that help individuals understand one another.

Leboncoin has successfully cultivated a sense of belonging through its growth—expanding from fewer than 30 backend engineers when I joined to over 100 now, partly due to this robust technical framework. This narrative is just one facet of our journey; the mono-backend-repo structure has also been embraced across other stacks following its internal success.

Reflecting on our path, it's evident that every technical decision has broader implications. As the saying goes, "You don't eliminate complexity; you merely relocate it." By opting to consolidate our repositories, we confronted the complexities of integration and collaboration directly. In doing so, we influenced our company's culture and laid the groundwork for future innovation and autonomy.

For those facing similar crossroads, I advise beginning with an understanding of your organization's unique needs and objectives. Assess how your technical choices align with your desired company culture. Are you prioritizing tight collaboration or fostering innovation through independence? Are you focused on speed or stability?

Experiment, iterate, and be mindful of trade-offs. What works for one organization may not suit another. The key is to align your technical strategies with your cultural goals, ensuring your tools and practices support business objectives while fostering an environment where teams can flourish.

Ultimately, true success lies in establishing a sense of community and shared purpose, where everyone—regardless of role or experience—feels connected to the larger mission. This is how strong systems and teams capable of achieving remarkable outcomes together are built.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Unlocking Health: Top 5 Activities for a Fit Lifestyle in 2024

Discover the best physical activities to enhance health, reduce disease risk, and improve overall fitness as suggested by Harvard Medical School.

Fueling Your Path: Harnessing Inner Motivation for Success

Explore how internal motivation drives success through passion, healthy habits, and support systems.

The Deep Desire of Women: Being Chosen and Valued

Women deeply appreciate feeling chosen and valued, whether in dance or life, as it fulfills their need for safety and connection.

Elevate Your Self-Love: 7 Essential Questions and Tips

Discover 7 key questions and actionable tips to boost your self-love and personal growth journey.

The Alarming Truth About Ocean Currents and Climate Change

Discover the potential climate crisis tied to ocean currents and their role in carbon absorption.

Microsoft's New Challenge: Competing with Notion and Slack

Microsoft is introducing Loop to rival Notion, raising questions about innovation and competition in the tech industry.

Empowering Yourself Against Imposter Syndrome: A Personal Journey

Discover how personal feedback and goal-setting can help combat imposter syndrome and foster self-improvement.

Unlocking Productive Mornings: A Simplified Routine Guide

Discover five essential steps to streamline your mornings, enhance productivity, and embrace a stress-free start to your day.