Project: userver (App Definition and Development / Streaming & Messaging)
File: driver_guide.md

Do you want to write a new driver for the server? This work is noble, but difficult. There are multiple ways to write a driver. Create a separate `TaskProcessor` for the driver and put all the blocking interactions of the native library into it: ```cpp template <class... Args> auto UserverSide::DoStuff(Args&&... args) { return utils::Async(drivertaskprocessor, "driver-action", { return nativedostuff(std::forward<Args>(args)); }).Get(); } ``` This approach has a problem. userver discourages performing CPU-bound work outside of the `main-task-processor` and prohibits blocking system calls in the `main-task-processor`. While the threads of the `{driver}-task-processor` are blocked on I/O, there are no problems. But as soon as they start parsing data the invariant above breaks, which leads lead to degradation of the service. This approach is discouraged. If the native driver provides some kind of notification mechanism for asynchronous completions, then you can notify userver from the completion queue using asynchronous server primitives (engine::Future or engine::SingleConsumerEvent). This approach was used to write the gRPC driver in userver. Otherwise, if the native driver is written in C++ and is not too big, then it may be possible to patch it to use userver IO primitives (engine::io::Socket, for example) and cleaning out other blocking primitives in favor of userver ones (engine::Mutex, engine::ConditionVariable). This approach was used to write the Clickhouse driver in userver. In both approaches there is a downside. The native library must be integrated into CMake scripts and additional libraries should be added to userver dependencies. In some rare cases this might be non-trivial. Implement the protocol yourself (probably reusing a subset of the native library functionality). This approach was used to write the PostgreSQL driver in userver - it creates a connection in a separate `TaskProcessor` via native functions, and then subscribes to the socket via engine::io::Socket, parses the protocol in the main `TaskProcessor`. Such an approach may open the door for further optimizations. If no native library is reused, then there's no need in dealing with CMake scripts to integrate it. Your driver should be well integrated into the userver framework. Write logs, a lot of logs, write them in all places where something is happening. It's always better to have extra logs (which can then be removed) than to have errors without logs. Write metrics - about connections, about queries, about query timings - in general, about everything that happens inside the driver. Write tests - unit and functional ones. If possible, write benchmarks. For full-fledged testing, you will need to set up an instance of what you are writing a driver for in the testsuite. This may not be trivial... Docs and samples should be provided. Note: PR for new drivers are welcome! If you started the work, the driver already does something useful but you're feeling exhausted, then we may help you to deal with the remaining parts. @htmlonly <div class="bottom-nav"> @endhtmlonly @ref scripts/docs/en/userver/development/stability.md | @ref scripts/docs/en/userver/tutorial/build_userver.md @htmlonly </div> @endhtmlonly