The importance of RFCs has been emphasized by many people. As @tison said in How to Participate in the Apache project community:
A description is certainly needed for any non-trivial change to explain the motivation. For major changes, design documentation becomes even more necessary since no one has a permanent memory, and people always forget why they did something in the first place. The precipitation of design documentation plays a vital role in freeing the community from the uncertain evolution of human activities.
I myself also elaborated on my understanding of RFC in How to Refactor in Open Source Projects？
It takes more than qualified code to make a good open source project; talking only about abstract techs and code with the open source community aside would be meaningless. Therefore, we must clarify our ideas and explain our motives before submitting large-scale changes to open source projects; this way, the community will understand what contributions we want to make and how we plan to do that.
These archived documents can help supplement information, improve ideas, and build better designs during discussion. From a long-term perspective, these documents can also help latecomers understand why such a design was proposed at that time and help promote the community more efficiently. Moreover, good design documentation can often influence and inspire the design of other open source projects, thus promoting the progress of the entire industry.
The following examples can prove that open source projects that work well often have sound RFC processes, and the relationship between them is complimentary:
So here comes the question:
How to Write an RFC
In my opinion, writing RFCs is a very natural thing to do. The essential reason why some people find it hard is often the lack of sufficient preparation since it’s easy to put forward a new idea, but transforming the idea into a feasible solution takes hard work. RFCs are the embodiment of this transforming process. Usually, I would go through the following steps when writing an RFC:
- Collect background information
- Analyze feasible schemes
- Write an RFC
- Discuss among the community
Collect Background Information
The most laborious and often overlooked step is to collect background information.
After coming up with a good idea, we need to refer to the historical RFCs and relevant issues/PR to make sure whether this idea is feasible or not. We need to collect enough information to answer the following questions:
- How do the existing related modules work? What is the problem to be solved now? Is it really necessary to make further changes?
- Is there any similar work in related fields, and how’s that going? Is there any similar experience for reference in other projects?
- Has this idea been considered before, and why was it shelved？ What has changed the situation now?
This process of collecting background information allows us to get more context information and have a better understanding of relevant modules, and thus avoid proposing improvements that are practically infeasible. Besides, collecting background information can help prevent us from carrying out repeated work; after all, there is nothing new under the sun.
I made the following preparations before proposing RFC: Config Backward Compatibility：
- Conduct one-vs-one communication with users who raised relevant questions to understand their demands and needs
- Understand the core logic of how Databend implements config processing
- Find out how similar projects implement config compatibility
Analyze Feasible Schemes
After fully understanding the background information, what to do next is to analyze feasible schemes.
There are often many possible solutions for a technical problem, so rather than trying to find a definite answer, we need to analyze and investigate various schemes and select a relatively better one. Sometimes a simple demo is necessary to help verify your ideas.
Here are several mistakes I often make in this process:：
• Laziness: Knowing that there are other possible solutions and not taking the initiative to investigate this usually led to being pointed out by the community when entering the discussion stage and forced to make additional explanations. It takes much more time to go back and forth than to think things straight at the beginning.
• Path Dependence: Focusing too much on how to fix the appeared problems and failing to think out of the box with better solutions.
• Self-Defense: The moment we come up with an idea, a general design is also formed in our minds, and we are inclined to this existing design when conducting research work. Even if the existing design has serious defects, we often are unwilling to give it up and choose to add unreasonable settings for it.
• Implementation Before Design: The implementation has been completed before the investigating process, and therefore the analysis of the schemes becomes the discussion and replication of the specific implementation.
These mistakes often lead to obvious bias in the subsequent writing of RFCs, and thus the conclusions are biased. Either the RFCs will be greatly adjusted, or in more serious situations, there will be major differences and even verbal conflicts with the defenders. To avoid such problems as much as possible, we should strive to make a fair and objective evaluation of different implementation schemes. Surely everyone has his/her own technological preferences, so there is no need to be too strict about fairness, as long as the advantages and disadvantages are clearly analyzed.
Write an RFC
Now that the preparatory work has been completed, we enter the stage of writing RFCs.
Usually, the project has its own format and requirements; thus, the way to write RFCs also varies according to local conditions.
Most RFCs include the following chapters:
- Background/ Motivation
- Detailed description
- Basic principles
- Unsolved problems
- Future possibilities
Background/ Motivation: In this chapter, we need to explain the background and motivation of this change and explain why this change should be made. According to different situations, the specific description should be slightly different according to specific situations. For example, when adding new functions, we should explain why the existing function cannot meet the requirements, what usage scenarios it would support and what the expected output is. In other situations when function reconfiguration and modification are needed, we should focus on explaining what problems exist in the existing implementation and what attempts have been made before.
Detailed Description: This chapter needs to specify how the project needs to be changed and also needs to be adjusted according to the specific requirements. Take Rust as an example; there are two main parts in this chapter: guide-level explanation and reference-level explanation.
Guide-Level Explanation: This chapter explains what changes this change will bring from the perspective of users, what new concepts have been introduced, and what new syntax has been added.
Reference-Level Explanation: This chapter explains how to implement the change from a technical point of view, how to interact with other features, and which edge cases need to be considered. Please remember not to paste too many detailed implementations in this chapter; a brief explanation of the ideas and some core code is enough. This way reviewer will have a better grasp of the macro design rather than get caught up in the implementation details.
Basic Principles: This chapter should explain why the change is implemented in this particular way.
Are there any other implementation schemes? What are the advantages and disadvantages of each, and what reasons drove us to adopt scheme A instead of scheme B? What is the existing technology? How are other projects realized, and what are their respective starting points?
Unsolved Problems: This chapter needs to explain the limitations of this change. For example, problems raised in the review but cannot be solved at present can be recorded in this chapter. Future possibilities This chapter is used to record the future possibilities of this change, which has been considered but has not been implemented this time.
Such as how a newly introduced feature can be combined with other features in the future. Or, when someone puts forward ideas beyond the scope of the current RFC during reviews, these ideas can be recorded in this chapter for future reference.
Discuss Among the Community
After completing the RFC, we can propose it to the community for discussion.
Of course, we don’t want our own RFCs to be rejected after all this work, so we need to participate in community discussions and respond to the concerns of community members actively. It’s necessary to adjust our implementation plan according to the feedback we receive; common adjustments are like supplementing what is not considered, adjusting the idea of implementation, splitting RFCs when necessary, implementing part of the original plan, and so on. Keep in mind that the interests of community members tend to be consistent, and every member’s ultimate goal is to help develop the project. Negative behaviors like taking others’ objections as an attack or passively confronting community members are not conducive to the further promotion of RFCs.
The RFC I proposed for DataBend — RFC: Config Backward Compatibility was originally a larger one — Scope: Versioned Config. In the original RFC, I planned to introduce the concept of version into the DataBend config so that developers can make destructive changes with more confidence. However, the community members were concerned about the complexity of the design, and they generally believed that the design was too complex and would introduce a lot of redundant code. Moreover, they believed that DataBend had not released a stable version yet, and there was no need to introduce a versioned config then. Based on the feedback, I made my own response：
Comment: Our community members have great concern about the complexity of introducing a versioned config at this stage(no stable release, no production users). It seems better to only split outer and inner configs and leave the decision of versioned config till the future. Response: I have updated the RFC and moved content about the versioned config to the Future possibilities part. This change only introduces a reconfiguration, which splits outer and inner configs.
After this adjustment, the community gave an LGTM and merged this RFC to the specific implementation stage.
Writing RFCs is an effective way to communicate and exchange ideas with the community. By completing the steps of collecting background information, analyzing feasible schemes, writing RFCs and community discussion, combined adjustments according to the particular project, we can all write a reliable proposal.
Go submit a proposal for your favorite open source project!