Kang's BlogThe personal space used by Kang to notehttps://blog.kaiyikang.com/Think Like a Cathttps://blog.kaiyikang.com/posts/2017/think-lika-a-cat/https://blog.kaiyikang.com/posts/2017/think-lika-a-cat/Sat, 13 May 2017 00:00:00 GMT<p>I once asked a classmate if humans could think like cats. She replied, "How would a human know what a cat is thinking?" Perhaps we can never truly know what a cat is thinking, but we can still try to "feel" what it might be thinking. The outcome of this "feeling" is largely the same, but the ability to acquire it truly varies from person to person. Some might find deep resonance just by reading certain phrases, while others, even after prolonged exposure or immersion, find it difficult to gain such an understanding.</p> <p>The book <em>The Power of Now</em> is now typically found on self-help or emotional well-being shelves. When I first encountered it in college, I was quite profoundly affected. Years have passed, and certain phrases from the book, such as "think like trees and flowers" and "most human thought is just the tip of the iceberg," still occasionally come to mind.</p> <p>At that time, I lacked any specific knowledge on the subject. Reading these books simply brought me a sense of inner peace, and their phrases could calm my anxious or negative moods. However, the book didn't offer much explanation regarding the underlying principles and reasons. As an engineering student with a practical and truth-seeking mindset, I remained somewhat bothered by this lack of detail.</p> <p>It was in the book <em>Mindfulness-Based Therapy for Improving Moods</em>, however, that I discovered many theoretical underpinnings supported by experimental findings.</p> A brief introduction to LLM and RAGhttps://blog.kaiyikang.com/posts/2024/a-brief-introduction-to-llm-and-rag/https://blog.kaiyikang.com/posts/2024/a-brief-introduction-to-llm-and-rag/Fri, 26 Jan 2024 00:00:00 GMT<h2>1. Abbreviation Explanation</h2> <p>Here are the abbreviations that will be mentioned afterwards, you don't need to know the exact meaning, just what it probably expresses:</p> <ul> <li>LLM: Large Language Model</li> <li>RAG: Retrieval Augmented Generation</li> <li>Mate: An application that can query Confluence or Bitbucket</li> </ul> <h2>2. Metaphor of LLM and RAG</h2> <p>We have all taken important exams. To prepare, students must study the textbook, attend lectures, and research any questions they have. After reviewing the material, it is time to take the exam. Exams come in two types: <strong>closed-book</strong> and <strong>open-book</strong>.</p> <p>During closed book exams, students must read the questions and conditional hypotheses, search their memory for relevant knowledge, and then write their answers in the space provided at the back. In contrast, during open book exams, students are allowed to refer to their books or notes, and write down the relevant information and what they have memorised.</p> <p>The student is an LLM and learning knowledge means pre-training of the model. As professors or examiners, we ask the LLM questions and expect the answers. <strong>Closed book</strong> means that the answer does not rely on any external knowledge, but rather than on the parameters stored in the brain. On the other hand, RAG is an <strong>open-book exam</strong>, where the student LLM acquires knowledge related to the question through a vector query before answering.</p> <p>All processes related to data training or fine-tuning fall under the umbrella of LLM. This includes vector queries, vector database extraction, prompt construction, and more. RAG, on the other hand, is more engineering-related. The core of LLM is wrapped within the big concept of RAG.</p> <h2>3. LLM</h2> <p>For a clear and comprehensive explanation of LLM, I highly recommend watching the video <a href="https://www.youtube.com/watch?v=zjkBMFhNj_g&amp;t=1s">(1hr Talk) Intro to Large Language Models</a>. The video covers all the essential aspects of the topic and is the best resource I have come across.</p> <h3>3.1 Understanding of Transformer</h3> <p>The paper 'Attention is all you need' introduced the Transformer model, which sparked the GPT boom. You can find the paper at this link: <a href="https://arxiv.org/abs/1706.03762">Attention Is All You Need</a>.</p> <p>To comprehend the Transformer, I recommend perusing this straightforward explanation titled "<a href="http://jalammar.github.io/illustrated-transformer/">The Illustrated Transformer</a> " rather than paper. The blog post presents a clear and concise overview of how the Transformer operates.</p> <p>It is important to note that LLM and Transformer are not equivalent, despite the common assumption due to the success and popularity of the latter. Furthermore, while GPT is a variant of the decoder component of Transformer, it is not an exact equivalent; for simplicity, it could be thought of as <code>Transformer ≈ GPT ≈ LLM</code>.</p> <p>To enhance understanding of GPT, watch this Youtube video by Andrej Karpathy <a href="https://www.youtube.com/watch?v=kCc8FmEb1nY">Let's build GPT: from scratch, in code, spelled out</a> and check out the corresponding open source code <a href="https://github.com/karpathy/minGPT">minGPT</a> on GitHub. In the video, the author takes a code perspective as he builds a GPT model from scratch.</p> <h3>3.2 Understanding of LLama</h3> <p>OpenAI is the publisher of ChatGPT. However, despite its name, the model is not open source. Therefore, the open-source community has put in a lot of effort, particularly with the Llama model, which has been trained and open-sourced by Meta. The Llama model comes in sizes 7B, 13B, and 70B.</p> <p>Meta has made almost everything related to Llama-Series open source:</p> <ul> <li><a href="https://ai.meta.com/resources/models-and-libraries/llama-downloads/">Pre-trained model</a></li> <li><a href="https://github.com/facebookresearch/llama">Model structure</a></li> <li><a href="https://ai.meta.com/llama/get-started/">Detailed tutorials on use and fine-tuning</a></li> </ul> <p>Because Llama is open source, the community has fine-tuned and structured many models based on it. One of the most famous is the series of models from Mistral (France), according to their <a href="https://mistral.ai/news/mixtral-of-experts/">official website</a>, the performance of the <code>Mixtral 8x7B</code> is similar to that of the GPT-3.5.</p> <p>Most open source models are available on Huggingface with model parameters and free downloads. Meanwhile, Huggingface provides a <a href="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard">leaderboard</a> to test the performance of these models. However, it is important to note that public lists may not be entirely trustworthy due to <a href="https://twitter.com/bindureddy/status/1750327486460908006">the contamination issue</a> . For a more persuasive list, please refer to the <a href="https://huggingface.co/spaces/lmsys/chatbot-arena-leaderboard">🏆 LMSYS Chatbot Arena Leaderboard</a>, which is voted on by humans.</p> <h3>3.2 Usage of Model</h3> <p>Although training the model requires significant GPU resources, expensive hardware is not necessary to load and use it. The model can also be run directly from a MacBook.</p> <p>To run Llama2, for example, we can download the model files based on Huggingface: <a href="https://huggingface.co/meta-llama/Llama-2-7b-chat-hf/tree/main">Download</a>, and use the <a href="https://huggingface.co/docs/transformers/index">Transformers</a> library to load and run the model directly. However, both loading and running the model can be very slow, as confirmed by actual testing. Part of the reason is that the default parameter precision of Huggingface is set to <code>float32</code> (as shown in the <a href="https://huggingface.co/docs/transformers/main_classes/model">Description</a> ) and it cannot utilize the GPU on the MacBook.</p> <p>To speed up model loading and usage, the open-source community has developed tools such as <a href="https://github.com/ggerganov/llama.cpp">llamacpp</a> , <a href="https://github.com/ollama/ollama">Ollama</a> or <a href="https://github.com/vllm-project/vllm">vllm</a>. There tools are written in CPP and can read models in quantized gguf format (<a href="https://huggingface.co/TheBloke">more quantized models can be found here</a>). This format is fast and efficient, and also takes advantage of GPU performance. The backend core service can be invoked locally using Bash commands or APIs. The return format is in OpenAI-specific JSON format, which is designed to facilitate easy connectivity to different front-ends.</p> <p>The tools mentioned above are only suitable for loading and using specific model types. Additionally, Apple has released a tool called <a href="https://github.com/ml-explore/mlx">mlx</a> for Apple Silicon memory, which effectively utilises the unified memory within the M-series chip.</p> <h3>3.3 Fine-tuning</h3> <p>As previously mentioned, fine-tuning is only applicable to models. By providing the model with specific data for training, the model acquires the corresponding knowledge and style of expression. This approach also addresses the issue of the model's limited token acceptance.</p> <p>Although fine-tuning with small data is effective, it can still be costly in terms of GPU resources. I attempted to fine-tune the Llama2-7b model using Google Colab, which is more affordable, but due to hardware limitations, the trained model cannot be exported for use in a production environment. Please refer to the following steps for the experiment: <a href="https://mlabonne.github.io/blog/posts/Fine_Tune_Your_Own_Llama_2_Model_in_a_Colab_Notebook.html">Fine-Tune Your Own Llama 2 Model in a Colab Notebook</a>. The article <a href="https://huyenchip.com/2023/04/11/llm-engineering.html">Building LLM applications for production</a> also compares the difference between Prompt and Fine-tuning.</p> <p>Fine-tuning Mate at this stage is challenging due to cost and data security constraints.</p> <h2>4. RAG</h2> <p>RAG stands for</p> <ul> <li>Extract Information</li> <li>Augment Information</li> <li>Generate Results.</li> </ul> <p>It is like an open-book exam where we provide relevant information to the LLM so that it can give less illusory answers. RAG is an abstract conceptual framework and does not imply a concrete implementation. Therefore, we can choose any module we need, depending on the requirements. It is a very efficient way to do this, even if <a href="https://www.youtube.com/watch?v=ahnGLM-RC1Y">OpenAI has a very positive opinion about it</a>.</p> <p>The LLM can provide more accurate answers when given relevant information, similar to an open-book exam. RAG is a conceptual framework and does not require a specific implementation. Modules can be chosen based on specific requirements. OpenAI has <a href="https://www.youtube.com/watch?v=ahnGLM-RC1Y">expressed a positive opinion</a> about this efficient approach.</p> <p>A simple architecture diagram is shown below, which comes from the RAG open source framework LlamaIndex, which is also used as the application framework by Mate.</p> <p><img src="https://docs.llamaindex.ai/en/v0.9.48/_images/basic_rag.png" alt="" /></p> <p>To understand the RAG concepts, it is recommended to read the original article, which provides a detailed explanation of the diagram and the concepts. The article can be found at <a href="https://docs.llamaindex.ai/en/stable/getting_started/concepts.html">here</a>.</p> <p>The diagram below is detailed and clearer than the flowchart above. It comes from the Copilot group on Github, who also share their experience in making LLM apps in their <a href="https://github.blog/2023-10-30-the-architecture-of-todays-llm-applications/">blog</a>:</p> <p><img src="https://github.blog/wp-content/uploads/2023/10/LLMapparchitecturediagram.png?resize=4088%2C2148?w=2048" alt="" /></p> <h3>4.1 RAG's journey</h3> <p>The journey begins with a user's question or query.</p> <p>The user inputs a question/query into the GUI interface of the front-end (MS Teams for Mate). The back-end (Python code deployed in a VM) receives the query and converts it into a vector using an embedding model. For more information on embedding models, please refer to this article: <a href="https://huggingface.co/blog/getting-started-with-embeddings">https://huggingface.co/blog/getting-started-with-embeddings</a>.</p> <p>The query vector is then inputted into a pre-prepared vector database, which retrieves the most relevant chunks. Various vector databases are available, and this article on <a href="https://docs.llamaindex.ai/en/latest/module_guides/storing/vector_stores.html#">Vector Stores</a> compares their performance. Compared to a standard database, this type of database can store extremely long arrays of vectors. It also supports various algorithms for comparing similarities between vectors, such as calculating a cosine value to extract the text that is most similar to a query. This process is known as <strong>Retrieval</strong>.</p> <p>After extracting the relevant chunks from the database, it has to undergo additional processes, such as filtering and reordering. This step corresponds to the <strong>Augmented</strong> stage in RAG.</p> <p>We now have the user's query in one hand and the relevant chunks from the database in the other. Next, we will input them into the prompt template so that the LLM can detect them correctly.</p> <p>It is important to note that different LLMs use various system prompts for training, for example, the Llama2 model uses <code>&lt;s&gt;[INST] &lt;&lt;SYS&gt;&gt;</code> (<a href="https://huggingface.co/blog/llama2">reference</a>), while the dolphin model uses <code>&lt; system&gt; &lt;human&gt;</code> (<a href="https://huggingface.co/TheBloke/MPT-30B-Dolphin-v2-GGML">ref</a>). The system prompts greatly affect the results.</p> <p>Finally, the LLM processes all the prompts and returns a response to the user.</p> <h2>5. About LLMOps</h2> <p>According to <a href="https://www.databricks.com/glossary/llmops">databricks</a>, LLMOps primarily focuses on improving and deploying models to enhance the value of the core LLM for the product. For instance, a better LLM in the case of RAG enables the output to extract information from chunks in a more logical and precise way.</p> <p>LLMOps is appealing to both medium and large teams or companies due to their possession of ample private data and financial resources. They aim to enhance the effectiveness of their products through a stable and efficient process.</p> <p>In contrast, LLMOps may not be as attractive to smaller teams. The reason for this is that the open source community has provided models with sufficient performance, while more and more important work has focused on processing the input information, e.g. how to get the chunks correctly, how to generate useful meta-information, or how to use better prompts.</p> <p>For a project that is progressively growing from small to large, it is necessary to first improve performance outside of LLM. If a bottleneck in system performance is reached, such as LLM not providing correct feedback, generating hallucinations, or requiring subtle optimizations such as changes in LLM's speech tone, it is necessary to address these issues in order to build an efficient LLMOps.</p> <h2>6. Summary</h2> <p>This short article introduces the development and use of LLM, showing the structure of RAG and how it relates to LLM. The length of the article is limited, for more detailed information please refer to the videos and links in the article.</p> Kaş – A Strange Transactionhttps://blog.kaiyikang.com/posts/2024/ka%C5%9F--a-strange-transaction/https://blog.kaiyikang.com/posts/2024/ka%C5%9F--a-strange-transaction/Fri, 06 Sep 2024 00:00:00 GMT<p><img src="@assets/2024/R0002207.JPG" alt="Scooter rental" /></p> <p>Kaş is a small town with just one main street, yet cars are plentiful and nearly all remaining roadside spaces are taken up by scooters. In my mind, scooters are the perfect mode of transportation: small enough to weave between houses, powerful enough to handle steep terrain, with enough range to reach remote neighborhoods or beaches. Scooters were everywhere in this town. We weren’t planning to rent a car, but to increase our mobility and freedom, we decided to try a scooter.</p> <p>On the map, we found the first rental shop. We walked over in the evening; the shop door was wide open, a few scooters scattered outside, but no sign of the owner. We stepped inside, called out a few times—no response. So we left, planning to return after dinner.</p> <p>After dinner, when we came back, we finally saw the shop owner—a cheerful older man—eating with his child nearby. Seeing customers, he quickly came over. His English was excellent. He explained the price: 400 lira per day, no deposit. If an accident happens and it’s the other party’s fault, their insurance covers it; if it’s our fault, we simply pay for the damages.</p> <p><em>It sounded like a very thorough explanation—but my partner’s timely reminder held back the wild horse in me that was ready to bolt.</em></p> <p>The moment I heard “no deposit,” and felt the price was roughly acceptable, my face lit up with a kind of “what a bargain!” eagerness—ready to close the deal immediately. But my partner had done research beforehand and saw online mentions of 150 lira per day. So she was much more cautious. After aligning our thoughts, we told the shop owner we’d think about it and come back the next day if we decided to rent.</p> <p>In Türkiye, transactions often depend on whether you’re a local and whether you speak the local language. If yes, friendship price. If not, the merchant can freely rely on the information gap. As foreign tourists, we really do need to keep our guard up. I reflected on this—someone like me, whose feelings show on his face so easily, is the perfect victim.</p> <p>The next day, we marked three or four nearby rental shops and planned to ask around.</p> <p>The first was closest to the port, and the noisiest. The owner was busy with another customer and motioned for a young guy near the door to talk to us. He didn’t speak—just stared silently for a while—then took out his phone and, very skillfully, typed Turkish into a translation app. The translated text read: “What do you want to rent?”</p> <p>We answered in English: a scooter.</p> <p>He replied: 150 lira. A great price—<em>but</em> only if we had experience.</p> <p>Being honest, we told him we had no experience. Could we still rent?</p> <p>Several “conversations” (translation app exchanges) later, the answer remained no. So we thanked him and left.</p> <p>Walking away, one question lingered: If he could understand English, why use a translation app at all? Truly puzzling.</p> <p>The second shop was not far away. From a distance, we saw a young man with a shiny, styled haircut sitting in an air-conditioned room, chatting and playing on his phone. When he saw us, he put his phone down immediately and greeted us warmly.</p> <p>We confirmed the 150-lira price and asked directly: what if we had no experience? The answer was the same—no. He specifically emphasized that only those with <em>very</em> skilled driving abilities could rent. Then, almost instantly, he pivoted and recommended that we rent a <em>car</em> instead.</p> <p><em>At this, my partner raised an eyebrow. With a German driver’s license, we were fully allowed to ride scooters in Türkiye regardless of experience. Was this a sales tactic—steering customers toward higher-profit car rentals?</em></p> <p>In any case, we still couldn’t rent, so we left again, disappointed.</p> <p>Under the blazing sun, exhausted from the heat and repeated rejections, we decided: if the last shop also said no, we’d return to the first shop—even if it was overpriced.</p> <p>Walking up the slope, we reached the final rental shop. It had a high rating online, but only a handful of reviews, which made me skeptical. The shop was the simplest of all—dim room, no air-conditioning, old wooden tables and chairs, faded calendars and posters on the walls, and very few scooters outside worth noticing.</p> <p>Inside, a man leaning on a table greeted us. We repeated our usual explanation: German license that legally allows scooters, but no experience—could he teach us? He quoted 400 lira, confirmed that lack of experience was fine, and said he would teach us.</p> <p>After being rejected twice, hearing this felt like a gust of cool air from an AC—freedom suddenly became possible. We quickly decided to rent. Since we didn’t have enough lira, we asked if we could pay in euros. He said yes, but he didn’t have lira to give change right now. He would return the difference after we brought the scooter back, with a written note as proof.</p> <blockquote> <p>After some market research, we figured out the real situation: – To rent a scooter, you must have a license recognized locally (German B license is fine; Chinese license requires translation or an international permit). – Prices split into two tiers: 150 lira for experienced riders, 400 for inexperienced. – Scooters require no deposit—but also come with no insurance.</p> </blockquote> <p>After confirming everything, he didn’t take us to the scooter right away. Instead, he made a phone call in Turkish. We didn’t understand. He told us to wait—the scooter was coming soon.</p> <p>We stood at the door, waiting. Soon, an older man came roaring down the street on a black scooter, parked in front of the shop, and pointed at it—<em>our</em> scooter.</p> <p><img src="@assets/2024/IMG_1734.JPG" alt="This one" /></p> <p>The scooter was small and very worn, but looked tough, seasoned—like it had endured years of battles. We paid, and the shop owner brought out a carbon-copy rental form. He and the older man used the scooter seat as a desk and wrote down my license information, muttering in Turkish—judging by tone alone, it felt like the older man outranked the shop owner, pointing and instructing throughout.</p> <p>After the paperwork, they began teaching me how to start the engine, use the throttle, brake, etc. Because of the previous two rejections, I feared that if I performed poorly, they might revoke the rental. So I threw all my energy into memorizing and reproducing every step during the practice session. The result was… acceptable at best. At least I didn’t crash the scooter the moment it moved.</p> <p>After wobbling a small circle around the courtyard, I patted the seat and said, “Great! Very easy to ride!”</p> <p>Just when I thought the process was finished and we could pay, the shop owner said: I needed to go with him to another place, pay there, and ride the scooter back on my own.</p> <p>I naïvely agreed and got onto the passenger seat. The older man leapt onto the driver’s seat, pointed at my helmet to remind me to wear it, and started the engine.</p> <p>The road was steep—<em>very</em> steep. Sitting behind him, I kept tipping backward. To steady myself, I had no choice but to hold onto his shoulders. His polo shirt and white undershirt looked like a set, but didn’t feel like one. Initially, I placed my palms gently on his shoulders, but he was sweating from the heat. To avoid smothering him—and to avoid the awkward sensation—I lifted my palms and held on with just a few fingers. Not the most stable on a bumpy road, but at least I didn’t fall off.</p> <p>After more than ten sharp turns, we arrived at another shop. Only then did I realize he was actually a mechanic—the owner of this second shop.</p> <p>He didn’t speak English. Using gestures alone, he invited me to sit. In this unfamiliar environment, I behaved obediently—following whatever seemed right.</p> <p>I handed him the euros. He looked up the exchange rate in his browser, typed the calculation (400 lira × 3 days) into his calculator, confirmed the amount with me, then gave me the remaining change. The whole process was… exhausting. Aside from basic translation apps, we used every possible method: grunts, hand gestures, numbers—anything to convey meaning. But in the end, we sealed the deal.</p> <p>Before I rode away, he chose a helmet for me—the smallest, cutest one buried among a pile of giant motorcycle helmets. Not knowing how else to express gratitude, I gave him several thumbs-ups—the most sincere and enthusiastic thanks I could muster.</p> <p>After saying goodbye, I rode off, trying to retrace the route, recalling the meaning of the street signs. Wobbly at first, but after a few intersections I felt more confident. Soon, I was riding freely back toward town.</p> Kaş – First Arrival in Kaşhttps://blog.kaiyikang.com/posts/2024/ka%C5%9F--first-arrival-in-ka%C5%9F/https://blog.kaiyikang.com/posts/2024/ka%C5%9F--first-arrival-in-ka%C5%9F/Wed, 04 Sep 2024 00:00:00 GMT<p>At one in the morning, just after night had fully settled in, we set off to catch the earliest flight at five. The night was still. We thought the last bus would be empty, but as soon as we boarded, we realized it was surprisingly full. Everyone seemed absorbed in their own world; inside the bus felt even quieter than outside.</p> <p>At the transfer station, while waiting on the platform, I saw workers welding on the opposite track—instantly reminding me of all those eternally stagnant construction sites in Germany. So there <em>are</em> places where people actually work, I thought. Where there are people creating order, there are naturally people destroying it. Under the night sky, the drunk were plenty too. Not far away sat a man in light-colored overalls, clutching a bottle of alcohol, crouching at the edge of the platform. After quite a while, he finally stood up and staggered off into the distance. His posture had never changed—only that, from the crack between his buttocks, there was now an additional smear of brown grime. I didn’t want to guess, but couldn’t help guessing.</p> <p><img src="@assets/2024/IMG_1434.JPG" alt="Night platform" /></p> <p>At the airport, the place was sparsely populated, but not as deserted as I had imagined. Before checking in, we found a place to sit. The airport is open 24/7, which naturally attracts quite a few homeless people. To prevent anyone from lying across the seats, the benches had “specially” installed metal bars between them. Stainless steel—guarding against the homeless, and incidentally, against our plan to rest here. Half asleep, I glanced around. Some people were already sleeping; the men were watching gaming videos to kill time.</p> <p>Around four, the staff finally started their shift. After checking our luggage, we followed the crowd upstairs to wait for security to open. We waited and waited—over an hour with no sign of the doors opening. Someone ahead grabbed a passing staff member and asked. Only then did we learn that most people were in the wrong queue. We were waiting at D, but should’ve gone to B. D had no early flights, hence no staff. B had been open for a while. So before sunrise, a large group of us bolted toward the correct entrance.</p> <p>Half asleep, one’s attention and logic are full of gaps. At border control, I mistook CH for CN and thought my passport could use the fast lane. The lady at the gate immediately stopped me and pointed toward the distant regular windows. Only then did I realize I’d misread it. Head down, I quickly walked over to the correct line.</p> <p>At six, we boarded the plane. Since we’d booked a budget airline, choosing seats cost extra. But here’s a little tip: if you pay attention while booking, you can actually skip seat selection entirely. During check-in, the system assigns seats automatically, for free. Once onboard, we discovered the back rows were nearly empty—you could practically lie down. Budget airlines sure have their many little tricks to make you spend.</p> <p><img src="@assets/2024/IMG_1449.JPG" alt="Sunrise" /></p> <hr /> <p>After landing, the air was hot, but softened by a slight breeze, so it didn’t feel suffocating.</p> <p>Walking through the arrival corridor, the right side opened into an atrium. Through the glass, you could see a tiny children’s play area below. The toys carried that old plastic shine, their colors bright but patches of paint already peeling—very much like the vibe of third- or fourth-tier malls back in China.</p> <p>The border control booths were lined up in the middle of the hall, and passengers freely crowded toward them. Normally I picture immigration as solemn police windows, but here it felt more like ticket counters at a train station—utterly chaotic. We had printed our visas just in case they were strict. I handed over the paper, but the officer didn’t even unfold it. He stamped the passport and waved us through.</p> <p>The baggage hall kept the same aesthetic. While waiting, I wanted to use the restroom. As expected, it smelled terrible. The reason was obvious: every stall had a huge bin inside, piled with all kinds of paper. I remembered reading somewhere that older plumbing systems can’t handle flushing toilet paper, so bins became necessary. Another discovery: one of the stalls was a squat toilet—rare in Europe, common in Asia, and apparently alive here at the intersection of Central Asia. As I was musing, my eyes caught a water hose nearby—but no waste bin. That thought alone was… unsettling.</p> <p>After picking up our luggage, we walked out and found an exchange counter at the corner: euro to lira at 1:29. Looked fine—until we reached the city center, where it was 1:36. I finally understood the airport’s strategy: charging you while pretending to offer a service. No wonder the entire huge airport looked worn, while that tiny exchange booth looked freshly renovated.</p> <p>Standing fully outside, even in the shade, my back was soaked within minutes. As someone used to cold Germany, I suddenly had to adjust to the heat, calm my heat-agitated nerves, and think clearly about our next steps. According to online info, there should’ve been a light-rail station outside. But from where we were standing, the entrance led only to a vast parking lot—nothing resembling a rail station. So we followed the crowd, avoiding travel agency booths and taxis. Turkish taxis have a terrible reputation in both Chinese and English internet spaces.</p> <p>Under this double assault of “new environment” + “high temperature,” our brains downgraded to single-thread mode. We resorted to the oldest method: asking for directions.</p> <p>That one question stretched across an entire street. Many locals didn’t speak English, making communication difficult. So we had to repeat the same question to multiple people—security guards, workers resting under metal pillars, young couples. Everyone was friendly, smiling, patient. After multiple confirmations, we finally understood: we had to take a shuttle bus or taxi from Terminal 2 to Terminal 1, and only then could we take the city bus. Municipal buses required full fares; the airport provided zero transfer services.</p> <p>After accepting this fact, we had to accept another: such an important stop was just awkwardly placed by the gate with no signs, no schedule—nothing but shabby old ads. “Hidden in plain sight,” but truly in the strangest way. Only when we saw a guy already waiting did we confirm it was indeed a functioning stop. While waiting, we chatted with him—he was Russian and traveling alone. He said he’d already been waiting over an hour, and our hearts immediately sank. But before our doubts grew too warm, the bus suddenly appeared, and our spirits jumped right back up with the blast of air conditioning.</p> <p>We tapped our credit cards, waited briefly, and the bus set off. I nervously watched the navigation on my phone, afraid we’d board the wrong bus and end up in the middle of nowhere. Only when the little on-screen arrow aligned with the planned route did I finally relax.</p> <p><img src="@assets/2024/IMG_1459.JPG" alt="Bus stop" /></p> <hr /> <p>The light-rail station wasn’t hard to find—just a short walk from the terminal entrance. Taking the elevator up, the station continued the rustic vibe: shaky turnstiles and a lonely ticket machine.</p> <p>A woman was desperately tapping at the machine. When she saw us, she lit up as if rescue had arrived, asking for help in simple English. But this was a classic “the blind leading the blind.” We tried switching the machine to English, poking around, but even though the vocabulary was understandable, the logic was incomprehensible. No station names, no clear instructions—just something about “5 uses” or “11 uses.” Utterly baffling.</p> <p>We exchanged confused looks, but the ticket situation remained hopeless—until a couple walked over.</p> <p>They seemed like locals, young and kind. They checked the machine but also couldn’t explain the system. Communication wasn’t easy, so the guy simply went for the nuclear option: he used <em>his own</em> transit card and bought tickets for all of us—five in total. One ticket was about 20 lira. Not expensive, but his warmth felt priceless. I wanted to pay him, but had zero lira on hand, and he didn’t seem willing to accept money anyway. So we thanked him repeatedly.</p> <p>A short walk down the platform and the train was already waiting. Since this was the first stop and the line was simple, no fear of going the wrong way. The carriage was spacious and quiet. We sat down, the air-conditioning blasting, and my heart and mind finally settled.</p> <p><img src="@assets/2024/IMG_1460.JPG" alt="Modern light rail carriage" /></p> <hr /> <p>The intercity bus station was located northwest of town, sparse and far less lively than the city center. We followed the signs to the entrance; the sun still blazed. The station looked like a massive open pavilion with an orange-red serrated roof. Its steel frame was covered in rust and stains. Glass panels—brown or clear—formed the hall, plastered with remnants of tape and paper from countless old notices. Areas with leaks or collapsed tiles were simply marked off with red-and-white tape as makeshift repairs.</p> <p>After a flimsy security check that felt like a free bonus with prepaid phone credit, we entered the hall. Counters for various bus companies lined the area, though half were empty at this hour. We were heading to Kaş, so we scanned the signs and quickly found the right counter.</p> <p>“Kaş?” “Kaş!” And that was essentially the entire transaction.</p> <p>“Leaving in 15 minutes,” the old man at the counter told us. It felt a bit rushed—we still needed to organize our luggage and wash up—but since the timing worked, we didn’t bother comparing prices. Once ready, we followed the direction the old man pointed to and met another old man by the back door. After confirming our destination, I placed our luggage in the compartment.</p> <p>Some front seats were already taken—mostly locals, not tourists. We found our spots. The seats were a dull red, carrying a worn-out grandeur.</p> <p><img src="@assets/2024/IMG_1463.JPG" alt="Inside the bus" /></p> <p>Though he said 15 minutes, 20 passed without movement. We watched the driver and soon understood the rule. The internet wasn’t lying: Turkish intercity buses don’t follow strict schedules. The reason is simple—people. Even though they were speaking Turkish, I could practically imagine the dialogue: “Plenty of seats left! Going to Kaş! Leaving in ten! Last call!” Filling the seats is priority number one. Only when almost full do they depart. It reminded me of my childhood experiences—except now, in another country, it felt almost archaeological, and oddly “international.”</p> <p>Of course, the driver was an older gentleman—another hallmark of long-distance buses. White hair, black eyebrows, square face, wrinkles from age but eyes full of spirit. White polo shirt, black slacks, leather belt, shiny dress shoes. The fabric was not expensive, and you could see the undershirt beneath.</p> <p>His entire appearance and demeanor radiated one thing: seasoned professionalism. With someone like him behind the wheel, no matter how fast the bus goes, you feel safe. I could easily imagine the scenes—him navigating winding mountain roads, routes he’d driven for decades like strolling through his backyard; hands turning the oversized wheel like practicing tai chi; the road bumping, passengers swaying, yet the driver’s inner calm an unshakable truth.</p> <p>For such a character, I never hesitate with admiration.</p> <p>The bus <em>finally</em> departed—but celebrations were premature. There’s a second reason these buses are never on time: the driver picks up extra passengers along the way. A few kilometers in, the bus slowed and stopped. After a short exchange, someone boarded. When he saw the seats were full—yes, <em>now</em> they were full—the driver pulled out a cushion from above and placed it beside the driver’s seat at the front of the aisle. A VIP spot reminiscent of a punished student sitting next to the blackboard. Once the man settled, we continued.</p> <p>This happened so often that I could count the stops on both hands. The journey felt endlessly long and dull—just like the bus’s “air-conditioning,” which blew something that <em>felt</em> cool to the touch but never actually cooled anything. Luckily, we were exhausted and needed sleep. After dozing off, we finally arrived in Kaş.</p> <hr /> <p>After some final rounds of direction-checking and minor hassles, we reached our accommodation in Kaş by evening.</p> <p>Though less than a day had passed—from early morning departure to evening arrival—our bodies and minds felt as if several days had gone by. A surge of new environments and information had overwhelmed us; our brains constantly learning and solving unexpected problems. It was exhilarating—and absolutely unforgettable.</p> <p><img src="@assets/2024/IMG_1485.JPG" alt="Arriving in Kaş at dusk" /></p> Kaş – Riding Around the Peninsulahttps://blog.kaiyikang.com/posts/2024/ka%C5%9F--riding-around-the-peninsula/https://blog.kaiyikang.com/posts/2024/ka%C5%9F--riding-around-the-peninsula/Sat, 07 Sep 2024 00:00:00 GMT<p><img src="@assets/2024/R0002196.JPG" alt="" /></p> <p>On the west side of Kaş lies a peninsula, shaped like a piece of dough squeezed out by hand—narrow at the tip and wide at the base. Heading north from the busy town center and turning past a fork in the road, you reach its entrance. A large ring road encircles the entire peninsula. Just follow it forward—no guidance needed, few cars, excellent road conditions.</p> <p>The sunlight was brilliant. Even with sunglasses on, which cast the mountains and sea under a dark translucent filter, everything still lay completely exposed under the fierce shine. I rode the little scooter we rented yesterday, cruising along the road. Pink flowers appeared now and then by the roadside, their color popping vividly against the gray-yellow vegetation. To the left were mountains—hotels and apartments nestled near the foothills. To the right, the sea—crowded beaches, umbrellas, people. The road ahead was playful, curving, weaving, at times neatly hidden behind the hills.</p> <p><img src="@assets/2024/R0002213.JPG" alt="" /></p> <p>Pictures show scenery more clearly than words, but the feeling of speeding between mountain and sea on a scooter—<em>that</em> can’t be captured by a camera.</p> <p>Left hand resting lightly on the brake, right hand gently twisting the throttle, my eyes darted between the road ahead and the speedometer. The speed hovered between 30 and 40, rising and falling with each little flick of my wrist. The engine roared, a noise I gradually adapted to—only when I stopped to rest, when all sound suddenly emptied out, did I realize how loud it had been. Wind was another indicator: when it rushed past sharply and recklessly, it hinted that speed was climbing. When wind faded from my skin and sunlight scattered on me again, the speed slowed—until it stilled completely.</p> <p>Whenever I saw a good view, I’d find a safe spot, park, and turn off the engine. The earlier wild growl instantly became obedient. If I listened carefully, the exhaust pipe at the back still trembled faintly—unclear whether it was a mechanical issue or just physics. Facing the sea, I took a few photos. Turning around, I saw the scooter standing alone by the roadside—no cars, no people—like I was stranded in the wilderness. For a moment, a faint panic rose: what if the scooter broke down, leaving me abandoned under the scorching sun? My mind wandered—imagining myself as a lone highway wanderer, carrying full repair gear and mastering not systematic but highly practical repair skills. Even if my trusty companion malfunctioned, I could fix it myself—clank, bang, done. After several attempts at restarting the engine, the roar returned, and the journey continued.</p> <p><img src="@assets/2024/R0002142.JPG" alt="" /></p> <p>There was another peculiar sensation. My lower body sat firmly on the seat, feet planted securely—yet my head and upper body felt the rush of fast, cutting wind, as if they were sprinting forward with superhuman speed. It was strange—my upper and lower halves sensing different velocities. This odd mismatch repeatedly poked at my nerves; whenever my grip loosened even a little, it reminded me, correcting my posture.</p> <p>Even under the sea breeze, I couldn’t escape the sun’s burn. Most vegetation was low, hugging the mountain slopes. There were a few scattered trees by the road, barely taller than a person, but their shade couldn’t be called shelter. The land was mostly barren and flat. To stand there and overlook the sea and islands, you had to expose yourself fully to the sky, confronting the sun head-on. I endured for a while, taking photos here and there—but soon sweat wouldn’t stop flowing, and my skin stung sharply as if protesting. So I had to end my loop around the peninsula sooner than planned, fleeing toward the beach to seek the sea’s comfort.</p> <p><img src="@assets/2024/R0002237.JPG" alt="" /></p> Kaş – Snorkelinghttps://blog.kaiyikang.com/posts/2024/ka%C5%9F--snorkeling/https://blog.kaiyikang.com/posts/2024/ka%C5%9F--snorkeling/Sun, 08 Sep 2024 00:00:00 GMT<p><img src="@assets/2024/R0000002.JPG" alt="Rocky beach" /></p> <p>When your feet step onto the pebbles and you walk toward the lower ground, the seawater gradually rises—first touching your toes, then ankles, shins, knees, and finally your thighs and the rest of your body. The sun’s energy is so intense that your skin feels a fine stinging heat, yet the sea, having absorbed all that warmth, turns gentle. No cold shock at all. The moment the skin is submerged, it’s like having a layer of armor on—washing away the burning sensation of the sun.</p> <p>After the body slowly adapts to the water, the next step is lowering the face, then the entire head, into the sea. Those familiar with the water dive in without hesitation—one quick flip and they’re gone. People like me, on the other hand, soak our bodies first, then hover face-down at the surface, staring at the water in a mild daze. The water is already clear enough to reveal the seabed and rocks, but whether to move closer, I wasn’t sure—maybe because it required mental preparation and a bit of courage.</p> <p><img src="@assets/2024/R0000003.JPG" alt="Floating into the sea" /></p> <p>After a small adjustment of the snorkel mask, I slowly lowered my face into the water. First the nose and mouth, then the cheeks, then the ears, and finally the whole head. With a bit of attention, you realize the senses are linked—when even a tiny patch of skin enters the water, it somehow feels as if your entire face has gone under. Of course, that’s just an illusion. One shouldn’t panic because of it and jerk back above the surface; you must keep pushing downward until all your senses are sealed inside the water.</p> <p>The warmth of the sea didn’t unsettle me. But besides the change in temperature and touch, the biggest shift was in hearing. Human noise disappears instantly. Everything turns quiet, and the sound of waves loops softly around the ears. Occasionally, tiny bubbles crackle somewhere nearby. And if you’re wearing a breathing device—like a snorkel or scuba regulator—the thick, resonating breathing from inside your body becomes the loudest sound of all. Breath, and breath again. A sound long sealed inside the body, now released by water. My body retreated backstage, reduced to a laborer transporting air in and out. All the spotlight fell onto breathing.</p> <p>I still hadn’t adjusted to this new hierarchy of senses. Unsure how to respond, I simply widened my eyes at the stones and sand on the seabed, letting the sound of my breath drift around me.</p> <p>My legs pushed gently forward, my head dipped deeper until fully immersed. After taking in enough of the view below, it was time to muster a bit of resolve—pull both legs backward and let buoyancy lift my body up. Lifted, the body wasn’t straight but slightly wobbly, limbs bent into a swimming posture, ready to explore the shallow underwater world.</p> <p><img src="@assets/2024/IMG_1931.JPG" alt="Stones and feet underwater" /></p> <p>Because of the terrain and shifting perspectives, the underwater scenery felt elusive; entering from different spots revealed completely different worlds.</p> <p>Snorkeling from the beach, there wasn’t much seaweed—mostly sand and stones settled on the seabed. As you moved farther from shore, the ground deepened and large reefs began to appear. You could clearly see how the light scattered, the distance dimming into dark shades, the reefs turning almost black, hidden behind the deeper sea. Floating on the surface, staring down into that expanding darkness, I felt an illusion—the earth continued downward, while <em>I</em> was rising into the sky. The dark canyon ahead grew larger, and fear rose with it. I was small, using so much strength to swim forward, yet barely moving—my energy devoured by the deep sea’s darkness. So I decided not to go further. I turned back—climbing upward again, back to the shallows, back to the sand and the shore.</p> <p><img src="@assets/2024/IMG_1806.JPG" alt="Sea grass" /></p> <p>Compared to the beach, snorkeling at the coves visited on a Boat Trip showed a different landscape. Those spots must have been carefully chosen by captains and guides—the underwater world here was richer. Besides smooth sand, there were clusters of sparse seaweed. The reef shapes were more diverse—some natural, others carved by human hands, the remains of submerged ruins. One set of ruins enclosed a rectangular shape, like a sunken swimming pool. Its purpose unknown, its surface uneven and worn. Stepping on it required caution—one careless move could get you scratched.</p> <p>Maybe because it was farther from people, the fish were more abundant. Not as vibrant or dramatic as aquarium posters—perhaps that’s the difference between filters and reality—but delightful nonetheless. Small fish darted in and out of rock crevices. If you reached toward them, they sensed it instantly and vanished in a flash. Large schools were rare, but I did encounter one. It reminded me of magnetic or electric field lines in physics. Each fish was thin and delicate, bodies drifting with the currents and reflecting light. They were like particle probes—gathering together, flickering rhythmically, tracing the ocean’s flows with elegant precision. Up close, it was intricate and spectacular.</p> <hr /> <p>Returning from sea to land, I immediately missed that earlier feeling.</p> <p>That world was quiet—only waves and my own breath—like I had drawn closer to myself, and to nature. My limbs and torso were free to twist and spin; buoyancy defeating gravity. Standing on land, the body felt grounded again, but strangely heavy, as if the freedom of the soul had slipped away. Besides the waves and wind, there was once again human noise—never something that brings me joy.</p> <p>The continent stretches on, but I must walk away from it, return to my life—riding my little motorbike, eating my flatbread. I’ll miss all of it.</p> <p><img src="@assets/2024/R0000001.JPG" alt="Seaside" /></p> October 2024 Readinghttps://blog.kaiyikang.com/posts/2024/october-2024-reading/https://blog.kaiyikang.com/posts/2024/october-2024-reading/Sun, 27 Oct 2024 00:00:00 GMT<p>Recently, the term <em>mindset</em> has been appearing very frequently.</p> <p>For example, Joshua Comeau often tries to understand CSS from a programming-logic perspective. In his technical talks, the content is not only excellent but also reveals his inner journey, emphasizing how important it is for him to bridge the <em>mindset gap</em> while learning.</p> <p>I couldn’t find a proper Chinese equivalent. <em>Mind</em> refers to the head or spirit, while <em>set</em> is a collection. It’s not merely a “way of thinking” or “mental attitude.” Perhaps because I understand programming, the word feels more like <em>modules</em> to me. <em>Gap</em> is vivid and fitting for the concept: people fiddle with different modules and connect them in the right order to understand something.</p> <p>I really like this idea. It aligns perfectly with the idea from <em>Soft Skills</em>: “build your own tutorial.” Through learning and practice, you open up the chain of thought that helps you understand something—making it intuitive and easy to grasp. This is something I want to continually practice.</p> <p>At the same time, opening up these logical chains should follow the principle of simplicity. Start with statements most aligned with intuition—like simple verb–object phrases—then go deeper, as if examining something with a magnifying glass. Layer by layer, you catch the missing links, and once those gaps are bridged, you go further until the functionality is finally realized. In this recursive process, we come to understand <em>why</em> many popular solutions today are so complex: they exist precisely to cover the things our intuitive reasoning overlooks. They are the things you can think up by knocking your head, but that you cannot actually implement in practice. If time and energy are limited, focusing only on the inputs and outputs of a module—without digging into the internals—may already be enough to accomplish what you want.</p> <p>In an age overwhelmed by exploding knowledge, the ability to understand something quickly and integrate it into your existing system is essential. Reinventing the wheel is necessary when you want to truly dig into a topic, but if time is limited, using what’s already there is also a perfectly fine choice.</p> <hr /> <p>In recent months, I’ve been reading several of Cal Newport’s books: the revised edition of <em>Digital Minimalism</em>, the Chinese translation of <em>Deep Work</em>, and the original <em>Slow Productivity</em>.</p> <p>Before getting into the content of these books, I feel like I’ve begun to see the pattern of bestselling productivity books: they begin with a story—something goes wrong—followed by a period of reflection, then a promise or solution, and finally the cycle repeats. Newport is slightly better than the typical template; besides the usual structure, he also talks about the philosophies he personally believes in, offering readers a reassurance that the methods he promotes truly work.</p> <p>The idea of autonomy runs throughout his work. <em>Digital Minimalism</em> does not reject social media or new technologies, but instead urges us to consciously recognize what we want <em>before</em> we use them. The iPhone can indeed run many apps, but that’s not an excuse to indulge in them freely. When designing the original iPhone, Steve Jobs even resisted adding an app store. Yet here we are, using whatever others shove into our hands, rarely thinking about what we actually want.</p> <p><em>Deep Work</em> emphasizes the importance of attention in the modern era. Companies not only want our money—they want to strip away our attention so we invest as much of our time as possible in them. Regaining focus and devoting it to areas we genuinely want to develop is especially crucial today.</p> <p>Finally, <em>Slow Productivity</em>, his newest work from 2024, essentially combines the previous two. His principles are: in a fast-paced work culture, we should work at a natural pace, do fewer things, and remain obsessed with quality. The book has been like a cognitive alarm clock for me, repeatedly nudging me toward a more proactive approach to doing things well.</p> <p>The chapter on obsession with quality left a deep impression on me, especially his emphasis on aesthetics. Discussions about aesthetics often focus on personal refinement—taste, cultivation, spiritual elevation. But he ties aesthetics directly to work quality. This resonates with me: after appreciating elegant, minimalistic architecture or code, I can no longer tolerate producing poor-quality code myself. And when I see inferior code written by others, I become deeply repulsed. This assumes, of course, that humans have an inherent drive to move toward something better. Once someone develops taste and aesthetic sensitivity, it becomes extremely difficult for them to return to a previous state. Thus, good taste influences not only the person but also everything they create.</p> <hr /> <p>Another book I read was the Taiwanese edition of <em>Die With Zero</em>.</p> <p>The author loves risk and is wealthy, so I can’t fully agree with everything in the book. But some of his ideas are indeed thought-provoking, especially those related to personal development.</p> <p>He stresses that a person’s experiences are as valuable as money, and—just like money—they generate returns over time. The difference is that money can be earned at any age, but experiences change with age and cannot be repeated. He assumes some experiences are age-specific, and once that age is gone, the flavor is gone too. How to match the right experiences to the right age to maximize their “returns”—that’s the question he wants to discuss.</p> <p>Most people save frugally their whole lives and leave behind a large sum when they die. That leftover wealth represents the portion of life they sacrificed—time lost enjoying life because it was spent working. Human time is mostly fixed: time spent here cannot be spent there—the essence of opportunity cost.</p> <p>Grasping what you truly want, and experiencing it while you still have the energy, is the way to maximize life’s value. All of this comes from the book; real life is far more complicated, which is why I can’t accept it wholesale. But the author’s idea of “actively doing what you want to do” aligns with what Newport says as well.</p> <p>One of the most striking ideas in the book is that leaving money after death is “irresponsible.” Only those who don’t know how to enjoy life or manage money leave the decision to death itself. The opposite type of person would allocate and distribute all their wealth before dying—no matter the amount—knowing clearly how much they need, facing death with clarity and logic.</p> <p>I think this makes some sense, but it’s still too absolute. Maybe one day, when I actually have that kind of money and assets, I’ll better understand what he means.</p> <h2>Short Summary</h2> <p>No matter how many tools we have, no matter how advanced AI algorithms become, everything still comes down to being able to focus on the work itself. With an active and intentional mindset, reflect on your daily behaviors, then intentionally immerse yourself in the work. Start simple and gradually move toward the complex.</p> <p>Be kinder to yourself. Don’t be stingy. Enjoy the present. Do things well.</p> <hr /> <h2>Delirium</h2> <p>Writing is like dreaming. You don’t know where it begins or where it ends. Lines and paragraphs flow onto the screen, roll into the eyes, and roll out again. When you return to reality, the writing continues drifting around—unceasing from birth to death.</p> <p>Dreams and life are similar. Life has no beginning. From the observer’s point of view, a person’s arrival in the world follows a traceable path: sex, pregnancy, birth in a delivery room. The observer is like the reader, experiencing the arrival of new life. The reader walks into a bookstore, buys a book, unwraps it, and begins reading the first line. But from the perspective of life itself, everything is baffling. How one was born, how one spent the first years wrapped in blankets—these are lost dreams that no one remembers. Then one muddles through school and eventually into work.</p> <p>Writing ends, reading ends, life ends. All leave us with a sense of regret. Memories blur with time, seeping into our nerves. When certain nerves activate during life, we recall what once happened.</p> <p>Reality belongs to others; only the dream belongs to oneself. This is not a contradiction. It does not mean “you and I are similar, therefore both must be real” or “both must be dreams.” They are not definitions but descriptions. The statements about reality and dreaming do not come from themselves, but from you and me—from my experience and my observation of you—together forming this seemingly paradoxical structure. Seen from the other direction, everything fits together harmoniously.</p> Kafka Event in Integration Testshttps://blog.kaiyikang.com/posts/2024/kafka-event-in-integration-tests/https://blog.kaiyikang.com/posts/2024/kafka-event-in-integration-tests/Thu, 17 Oct 2024 00:00:00 GMT<h2>What is the requirement?</h2> <p>When introducing a new feature to the system, it is imperative to develop and implement a comprehensive suite of tests, including Unit Tests, Integration Tests, and Acceptance Tests.</p> <p>This post will primarily focus on the Integration Test (IT) phase.  Integration Tests are designed to verify the functionality of the service on a local machine environment. It is crucial to ensure that all external systems and dependencies are properly configured and operational before commencing the testing process. Integration Testing serves as the penultimate stage of validation before proceeding to Acceptance Testing.</p> <p>The current requirement is as follows: Upon successful completion of the primary process, the service is tasked with triggering a Kafka event. This event serves as a notification mechanism, informing other services of the successful execution. This signalling mechanism must be thoroughly validated through rigorous integration testing procedures.</p> <h2>Start Integration Test</h2> <p>There are few steps before run the code test:</p> <ol> <li>Clean up the docker containers and images.</li> <li><code>mvn clean install</code>: clean files, compile and package the current source code.</li> <li><code>mvn clean verify [with special options for validation]</code>: verify step is one of the step in install command, but the validation can be realised with different options.</li> <li><code>-P</code> parameters : activate specified Maven profiles. Like open daemon service to block the main services exits after involking.</li> <li><code>-D</code> parameters: sets system properties.</li> <li><code>-rf service</code>: Resume the build from the specified module.</li> </ol> <p>Files ending names with "ITCase" or "IT" are designated as integration tests. This is a standard naming convention. Maven is configured to automatically identify and execute these files as integration tests based on the presence of "IT" in their names.</p> <p>Note: The code snippet is provided for illustrative purposes only. It is not a functional example and cannot be used in a real-world context.</p> <h2>Basic Code Structure</h2> <p>The basic structure for ITCase is comprised of four elements:</p> <ul> <li>Define constants, class/instance variables</li> <li>Define <code>@BeforeAll</code>: it marks a method to be run once before ALL test methods in the class.</li> <li>Define <code>@BeforeEach</code>: Marks a method to be run before EACH test method in the class.</li> <li>Define test: Implementing custom annotations to tag use case IDs enhances documentation traceability and improves test case management.</li> </ul> <pre><code>public class ServiceITCase { // define variables; private static final STATIC_VALUE = VALUE; private InstanceDefinedInAtBeforeAll instance; @BeforeAll static void setUp() throws Exception { // ... } @BeforeEach static void setUpBeforeEach() throws Exception { // … } @Test @Verifies("TraceId_useCase") void TestMethod() { // Given // ... // When // ... // Then // ... } } </code></pre> <h2>Configure Kafka Event</h2> <p>Properties is a class that represents a persistent set of properties and is a part of the <code>java.util</code> package. <code>getProperty()</code> and <code>setProperty()</code> are common methods.</p> <p>This code example configures properties for Kafka consumer.</p> <pre><code>private static Properties setKafkaConsumerWith(final String groupId) { final Properties properties = new Properties(); properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, getKafkaBootServer()); properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, groupId); properties.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest"); properties.setProperty(ConsumerConfig.CLIENT_ID_CONFIG, ServiceITCase.class.getSimpleName() + System.currentTimeMillis()); properties.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); properties.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, CustomEventDeserializer.class.getName()); return properties; } </code></pre> <p>The rough model of Kafka is as follows: The <code>producer</code> sends messages to <code>Kafka Brokers</code> with <code>topics</code>, which then send them to the <code>consumer</code>. Here is a brief description of each line:</p> <ol> <li><code>BOOTSTRAP_SERVERS_CONFIG</code>: the Kafka broker addresses. It provides the initial points of contact for a related client (producer, consumer, or admin) to connect to the Kafka cluster.</li> <li><code>GROUP_ID_CONFIG</code>: defines the consumer group id. Consumers with the same group Id work together as a single logical consumer.</li> <li><code>AUTO_OFFSET_RESET_CONFIG</code>: optional, but recommended. Define where to start reading messages if no <em>offsets</em> is found.</li> <li><code>CLIENT_ID_CONFIG</code>: Define a unique identifier for the client. In this case, it is this class.</li> <li><code>KEY_DESERIALIZER_CLASS_CONFIG</code>: specifies the deserialiser for message keys.</li> <li><code>VALUE_DESERIALIZER_CLASS_CONFIG</code>: specifies the deserialiser for message value.</li> </ol> <h3>What is <em>Topic</em>, <em>Partition</em> and <em>Offset</em>?</h3> <p>Before we proceed further with our code exploration, it is beneficial to familiarize ourselves with some key technical terminology. If you're already well-versed in these terms, feel free to advance to the subsequent chapter.</p> <p><strong>Topic</strong>: Kafka topics organize related events. For example, we may have a topic called 'logs', which contains logs from an application.</p> <p><strong>Partitions</strong>: Topics are broken down into a number of partitions. A single topic may have more than one partition.</p> <p><strong>Offsets</strong> represent the position of a message within a Kafka Partition. It is an integer value, and each message in a given partition has a unique offset.</p> <p>Brief Summary: Kafka structures data into <strong>topics</strong>, which are segmented into partitions. Each partition contains messages with unique, sequential identifiers known as offsets. These offsets enable consumers to monitor their progress and resume reading from specific points within the message stream, facilitating efficient data processing.</p> <h2>Main Test</h2> <p>Let's move on to the central part of the test.</p> <pre><code> @Test @Verifies("TraceId_useCase") void TestMethod() { // Given prepareSomething = prepare(); final Properties properties = setKafkaConsumerWith("groupId"); try (final Consumer&lt;String, CustomEvent&gt; kafkaConsumer = new KafkaConsumer&lt;&gt;(properties)) { kafkaConsumer.subscribe(Collections.singleton("custom_topic")); kafkaConsumer.poll(java.time.Duration.ofSeconds(1)); // When // This method will send the event var result = doSomethingAndGetResult(); // Then assertNotNull(result); assertThat(result.contains("value")).isTrue(); await().atMost(3, TimeUnit.SECONDS) .untilAsserted(() -&gt; assertTestMethod(kafkaConsumer)); } } </code></pre> <p>The Consumer interface uses two type of parameters to specify the type of the key and value in Kafka messages.</p> <pre><code>final Consumer&lt;String, CustomEvent&gt; kafkaConsumer = new KafkaConsumer&lt;&gt;(properties) </code></pre> <p>The next two lines of code are crucial for setting up and operating a consumer:</p> <pre><code>kafkaConsumer.subscribe(Collections.singleton("custom_topic")); kafkaConsumer.poll(java.time.Duration.ofSeconds(1)); </code></pre> <p><code>subscribe()</code> registers the consumer to specific Kafka topic, specifying which data streams to monitor for incoming messages.</p> <p><code>poll()</code> fetches messages from the subscribed topic. The duration parameter (1 second in this case) defines the maximum wait time for new messages before the method returns.</p> <p>The first <code>poll()</code> call serves to establish a connection with the Kafka cluster and retrieve initial offsets. This initial call also allows for early detection of potential connection errors.</p> <p>Complementing the standard <code>assertThat()</code>, we can leverage <code>await()</code> from <code>org.awaitility.Awaitility</code>. This code snippet demonstrates an asynchronous testing methodology:</p> <pre><code>await().atMost(3, TimeUnit.SECONDS).untilAsserted(() -&gt; assertTestMethod(kafkaConsumer)); </code></pre> <p>This line of code:</p> <ol> <li>waits for a condition to be met</li> <li>has a timeout of 3 seconds</li> <li>repeatedly checks the assertion that will be introduced in the subsequent chapter</li> <li><strong>Passes</strong> if the assertion becomes true within the timeout</li> <li><strong>Fails</strong> if the timeout is reached before the assertion is true</li> </ol> <h2>Assertion</h2> <p>The Assertion of record does not require any specialized procedure. Step 2 is an optional phase designed to verify the accuracy of the record obtained from the broker.</p> <pre><code> private void assertTestMethod(final Consumer&lt;String, CustomEvent&gt; kafkaConsumer) { // 1. Poll records from broker final ConsumerRecords&lt;String, CustomEvent&gt; consumerRecords = kafkaConsumer.poll(java.time.Duration.ofSeconds(1)); // 2. (Optional) Verify the records final Optional&lt;ConsumerRecord&lt;String, CustomEvent&gt;&gt; serviceRecord = StreamSupport .stream(consumerRecords.spliterator(), false) .filter(consumerRecord -&gt; { final CustomEvent event = consumerRecord.value(); return event.getSomething().equals(testCase.getSomething().toString()); }).findAny(); assertThat(serviceRecord).isPresent(); // 3. Assert Record final ConsumerRecord&lt;String, CustomEvent&gt; record = serviceRecord.get(); assertThat(record.key()).isEqualTo(testCase.getKey().toString()); // 4. (Optional) Debug for (Header header : record.headers()) { System.out.println("Header Key: " + header.key() + ", Value: " + new String(header.value())); } } </code></pre> <p>Regarding step 4, it's important to note the presence of headers in the record. Headers are a crucial feature in Kafka, enabling the attachment of metadata to messages without altering the message payload itself. These headers consist of key-value pairs, where the key is a String and the value is a byte array. To illustrate, here's how one can add headers to a ProducerRecord:</p> <pre><code>ProducerRecord&lt;String, String&gt; record = new ProducerRecord&lt;&gt;( "topicName", null, // partition "key", "value", Headers headers = new RecordHeaders(); headers.add("header1", "value1".getBytes()); headers.add("header2", "value2".getBytes()); ); </code></pre> <h2>Extended Topic: Perceptual Decoupling</h2> <p>Before concluding, I'd like to share my thoughts on method design. These are just observations and experiences, not definitive guidelines.</p> <p>In the "Basic Code Structure" chapter, I mentioned tests are divided into three parts: <code>@BeforeAll</code>, <code>@BeforeEach</code>, and <code>@Test</code>. They not only execute in sequence but also have a hierarchical structure logically. <code>@BeforeAll</code> can be understood as global, with all tests and helper methods able to access variables defined there. <code>@BeforeEach</code> is for each test, where the test environment and state can be reset.</p> <p>Once variables are defined, we can use them in <code>@Test</code>. At this point, when creating different methods based on this test, a divergence appears: whether to use these defined variables in sub-methods or pass them as arguments. This difference highlights the concept of decoupling.</p> <p>As the project has been ongoing for a long time, many test cases exist. For new tests, I can combine different methods through copy and paste to complete testing for new features. I've found that the former method using arguments is clear and explicit, while with the other, I need to frequently check the <code>@BeforeAll</code> and <code>@BeforeEach</code> annotated functions.</p> <p>Here is an example.</p> <p>&lt;!-- 在结束之前,我想聊一聊对如何设计方法的感想。当然,这仅仅是感想和经验,而非明确的指导。</p> <p>在代码结构一章中,我提到测试分成三个部分:<code>@BeforeAll</code>, <code>@BeforeEach</code>,<code>@Test</code>。它们不仅是随时间顺序执行,同时在逻辑上也具有上下级的结构。<code>@BeforeAll</code> 可以理解成是全局的,所有的测试和辅助方法都能访问在该方法中定义的变量。<code>@BeforeEach</code> 则是针对每次测试,测试环境和状态都可以在该函数中被重置。</p> <p>当变量在不同的部分被定义后,我们可以在 <code>@Test</code> 中使用。此时,如果我们在基于该测试创建不同的方法时,分歧就出现了,即是否要在子方法中使用上述定义的变量,或通过 argument 的方式使用变量。这其中不同,凸显出了解藕的概念。</p> <p>因为项目持续了很久,所以存在很多测试用例。针对新测试,我可以通过复制和粘贴的方式,组合不同方法,从而完成新 feature 的测试。我发现,前者使用 argument 方法清晰且明确,而后者我需要频繁检查 <code>@BeforeAll</code> 和 <code>@BeforeEach</code> 修饰的函数。</p> <p>举个例子: --&gt;</p> <pre><code>private static HttpClient client; @BeforeEach void setUp(){ client = createHttpClient(USER_NAME,PASSWORD); } @Test void test() { useClient1(); useClient2(client); } void useClient1() { client.toTarget("www.abc.com").request().post() } void useClient2(HttpClient server) { server.toTarget("www.abc.com").request().post() } </code></pre> <p>When I copy <code>useClient2()</code>, I clearly know it requires an <code>HttpClient</code>. I also know that <code>HttpClient</code> is defined in the <code>@BeforeEach</code> block above.</p> <p>When I copy <code>useClient1()</code>, the <code>client</code> is hidden and not explicitly defined. So I need to spend more effort to find the corresponding keyword.</p> <p>These two approaches represent different levels of coupling with the test. Of course, I'm not saying <code>useClient2()</code> is better than <code>useClient1()</code>, as we can see <code>useClient2()</code> contains more code, making the overall code more verbose.</p> <p>How to choose between these two styles? My current answer is that it depends on the project style. If the project is mature with many similar code blocks, the decoupled approach might be better. But if this test is unique and likely to appear only here, the first approach is better.</p> <p>&lt;!-- 当我复制 <code>useClient2()</code> 时,我明确知道它需要 <code>HttpClient</code>. 同时我也明确知道,<code>HttpClient</code> 在更上一层的 <code>@BeforeEach</code> 中被定义。</p> <p>而当我复制 <code>useClient1()</code> 时,<code>client</code> 被隐藏了,并非显示定义。所以我需要花费更多的精力去寻找对应的关键词。</p> <p>两种方式对应着和测试不同程度的耦合。当然,我这里并不是说 <code>useClient2()</code> 比 <code>useClient1()</code> 好,毕竟我们可以看到,<code>useClient2()</code> 包含更多的代码,会让整体代码显得冗长。</p> <p>如何选择这两种风格?当前我的答案是按照项目风格而定的。如果项目比较成熟,有很多相似功能的代码块,那么解藕的方式会更好,但如果这个测试非常独特并且可能就只会在这里出现,那么第一种方式更好。 --&gt;</p> Race Conditionhttps://blog.kaiyikang.com/posts/2024/what-is-race-condition/https://blog.kaiyikang.com/posts/2024/what-is-race-condition/Sat, 20 Apr 2024 00:00:00 GMT<p>In the meeting, my colleague presented an overview of the spike in question, outlining the necessary steps for handling user consent.</p> <p>It is the case that different system platforms send requests to calculate user privacy patterns to our service at the same time. The different platforms may be customers or third parties.</p> <p>However, due to the asynchronous communication involved, a <strong>race condition</strong> would occur when the new requests are received.</p> <h2>What is Race condition ?</h2> <p>A <strong>race condition</strong> or race hazard is the condition of an electronics, software, or other <a href="https://en.wikipedia.org/wiki/System">system</a> where the system's substantive <strong>behavior is dependent on the sequence or timing of other uncontrollable events</strong>, leading to unexpected or inconsistent results. It becomes a <a href="https://en.wikipedia.org/wiki/Software_bug">bug</a> when one or more of the possible behaviors is undesirable.</p> <p>Source: <a href="https://en.wikipedia.org/wiki/Race_condition">Race condition - Wikipedia</a></p> <h2>Understanding from Code perspective</h2> <p>A race condition arises when two or more threads attempt <strong>to modify shared data simultaneously</strong>. Given that the thread scheduling algorithm can switch between threads at any point, it is <strong>impossible to predict the order in which threads will access shared data</strong>. Consequently, the outcome of the data modification is contingent upon the thread scheduling algorithm, whereby both threads are essentially engaged in a race to access or alter the data.</p> <pre><code>if (x == 5) // The "Check" { y = x * 2; // The "Act" // If another thread changed x in between "if (x == 5)" and "y = x * 2" above, // y will not be equal to 10. } </code></pre> <p>In order <strong>to prevent race conditions</strong> from occurring, you would typically <strong>put a lock around the shared data</strong> to ensure only one thread can access the data at a time. This would mean something like this:</p> <pre><code>lockX(); // Obtain lock for x if (x == 5) { y = x * 2; // Now, nothing can change x until the lock is released. // Therefore y = 10 } unlockX(); // release lock for x </code></pre> <p>Source: <a href="https://stackoverflow.com/questions/34510/what-is-a-race-condition">multithreading - What is a race condition? - Stack Overflow</a></p> Database Security Configuration and Connectivity for Java Applicationshttps://blog.kaiyikang.com/posts/2024/database-security-configuration-and-connectivity-for-java-applications/https://blog.kaiyikang.com/posts/2024/database-security-configuration-and-connectivity-for-java-applications/Wed, 20 Nov 2024 00:00:00 GMT<p>I have an enterprise Java application and want to securely manage database connection configuration.</p> <p>To address this requirement, this article will briefly describe how to securely manage database credentials with HashiCorp Vault and how to configure JPA for database connectivity in a Kubernetes environment.</p> <h2>Key management</h2> <h3>Key storage</h3> <p>In a Kubernetes environment, we use HashiCorp Vault as the key management tool. The configuration is stored in the `values.yaml' file, which uses the Helm configuration values:</p> <pre><code># values.yaml secretList: - name: database-credentials vaultPath: /secret/database/prod </code></pre> <h3>Key acquisition process</h3> <ol> <li>store sensitive information (e.g. database username, password) in the Vault</li> <li>retrieve credentials information from the Vault via <code>vaultPath</code></li> <li>use <code>name</code> as a reference identifier (e.g. <code>database-credentials.username</code>)</li> </ol> <h2>Configuration File Management</h2> <p>When the application is deployed, Kubernetes injects the values from <code>values.yaml</code> into the application configuration file, which mainly consists of:</p> <ul> <li><code>context.xml</code> (Tomcat data source configuration)</li> <li><code>persistence.xml</code> (JPA configuration)</li> <li><code>application.properties/yml</code> (Spring configuration)</li> </ul> <p>In the configuration file, we use the placeholder format: <code>^CFG:DATABASE_PASSWORD^</code>.</p> <h3>Tomcat data source configuration</h3> <p><code>context.xml</code> is Tomcat's context configuration file for creating and managing data sources and other container-level resources.</p> <p>Example:</p> <pre><code>&lt;Context&gt; &lt;Resource name="jdbc/MainDatabase" type="javax.sql.DataSource" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb" username="^CFG:DATABASE_USERNAME^" password="^CFG:DATABASE_PASSWORD^" maxTotal="20" maxIdle="10" maxWaitMillis="10000"/&gt; &lt;/Context&gt; </code></pre> <p>Detailed reference: <a href="https://tomcat.apache.org/tomcat-9.0-doc/jndi-resources-howto.html">Apache Tomcat 9 (9.0.97) - JNDI Resources How-To</a></p> <h3>JPA configuration</h3> <p>JPA (Java Persistence API) is a specification/interface, the most common JPA implementation is Hibernate.</p> <p><code>persistance.xml</code> is the configuration file for jpa:</p> <pre><code>&lt;persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.2"&gt; &lt;persistence-unit name="mainPU" transaction-type="RESOURCE_LOCAL"&gt; &lt;non-jta-data-source&gt;java:comp/env/jdbc/MainDatabase&lt;/non-jta-data-source&gt; &lt;properties&gt; &lt;property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/&gt; &lt;property name="hibernate.show_sql" value="false"/&gt; &lt;/properties&gt; &lt;/persistence-unit&gt; &lt;/persistence&gt; </code></pre> <p>where <code>java:comp/env/</code> is the standard prefix for JNDI namespaces, as explained here: <a href="https://stackoverflow.com/questions/11631839/what-is-javacomp-env">jakarta ee - what is java:comp/env? - Stack Overflow</a></p> <p>You can find the corresponding Resource by using it.</p> <h2>Application startup process</h2> <p>Now let's string together the above configuration files.</p> <ol> <li><strong>Kubernetes initialization</strong></li> </ol> <ul> <li>Reading <code>values.yaml</code></li> <li>Get keys from Vault</li> <li>Replace the placeholders in the configuration file</li> </ul> <ol> <li>**Tomcat startup</li> </ol> <ul> <li>Load the <code>context.xml</code></li> <li>Initialize the data source connection pool</li> </ul> <ol> <li>**Application deployment</li> </ol> <ul> <li>Reads <code>persistence.xml</code>.</li> <li>Hibernate initialization</li> <li>Create database connection</li> </ul> <h2>Code example</h2> <p>By importing the JPA specification and its implementation, Hibernate, as well as the Spring framework, we can properly manipulate the data in the database.</p> <pre><code>@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String username; // getters and setters } @Service @Transactional public class UserService { // Specify the persistence-unit with the name attribute. @PersistenceContext(unitName = "mainPU") private EntityManager entityManager; public User findById(Long id) { return entityManager.find(User.class, id); } public void save(User user) { entityManager.persist(user); } } </code></pre> <h3>Summary</h3> <p>We have simply implemented a complete database security configuration scheme:</p> <ol> <li>key management with HashiCorp Vault</li> <li>configuration injection via Kubernetes</li> <li>Combining Tomcat data sources and JPA for database connectivity.</li> </ol> <p>This solution ensures security and maintains good maintainability, which is suitable for the development and deployment of enterprise-level Java applications.</p> <p>&lt;!--</p> <p>我有一个企业级 Java 应用,希望安全地管理数据库连接配置。</p> <p>针对该需求,本文将简单的介绍如何在 Kubernetes 环境下,通过 HashiCorp Vault 实现数据库凭证的安全管理,以及如何配置 JPA 实现数据库连接。</p> <h2>密钥管理方案</h2> <h3>密钥存储</h3> <p>在 Kubernetes 环境中,我们使用 HashiCorp Vault 作为密钥管理工具。配置信息存储在 <code>values.yaml</code> 文件中:</p> <pre><code># values.yaml secretList: - name: database-credentials vaultPath: /secret/database/prod </code></pre> <h3>密钥获取流程</h3> <ol> <li>将敏感信息(如数据库用户名、密码)存储在 Vault 中</li> <li>通过 <code>vaultPath</code> 从 Vault 获取凭证信息</li> <li>使用 <code>name</code> 作为引用标识符(如 <code>database-credentials.username</code>)</li> </ol> <h2>配置文件管理</h2> <p>在应用部署时,Kubernetes 会将 <code>values.yaml</code> 中的值注入到应用配置文件中,主要包括:</p> <ul> <li><code>context.xml</code>(Tomcat 数据源配置)</li> <li><code>persistence.xml</code>(JPA 配置)</li> <li><code>application.properties/yml</code>(Spring 配置)</li> </ul> <p>配置文件中,我们使用占位符格式:<code>^CFG:DATABASE_PASSWORD^</code>。</p> <h3>Tomcat 数据源配置</h3> <p><code>context.xml</code> 是 Tomcat 的上下文配置文件,用于创建和管理数据源以及其他容器级别的资源。</p> <p>示例:</p> <pre><code>&lt;Context&gt; &lt;Resource name="jdbc/MainDatabase" type="javax.sql.DataSource" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb" username="^CFG:DATABASE_USERNAME^" password="^CFG:DATABASE_PASSWORD^" maxTotal="20" maxIdle="10" maxWaitMillis="10000"/&gt; &lt;/Context&gt; </code></pre> <p>详细参考:</p> <p>https://tomcat.apache.org/tomcat-9.0-doc/jndi-resources-howto.html</p> <h3>JPA 配置</h3> <p>JPA (Java Persistence API) 是一个规范/接口,最常用的 JPA 实现是 Hibernate。</p> <p><code>persistance.xml</code> 是 jpa 的配置文件:</p> <pre><code>&lt;persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.2"&gt; &lt;persistence-unit name="mainPU" transaction-type="RESOURCE_LOCAL"&gt; &lt;non-jta-data-source&gt;java:comp/env/jdbc/MainDatabase&lt;/non-jta-data-source&gt; &lt;properties&gt; &lt;property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/&gt; &lt;property name="hibernate.show_sql" value="false"/&gt; &lt;/properties&gt; &lt;/persistence-unit&gt; &lt;/persistence&gt; </code></pre> <p>其中 <code>java:comp/env/</code> 是 JNDI 命名空间的标准前缀,这里有解释:https://stackoverflow.com/questions/11631839/what-is-javacomp-env</p> <p>通过它可以找到对应的 Resource。</p> <h2>应用启动流程</h2> <p>现在我们将上述的配置文件串联起来。</p> <ol> <li> <p><strong>Kubernetes 初始化</strong></p> <ul> <li>读取 <code>values.yaml</code></li> <li>从 Vault 获取密钥</li> <li>替换配置文件中的占位符</li> </ul> </li> <li> <p><strong>Tomcat 启动</strong></p> <ul> <li>加载 <code>context.xml</code></li> <li>初始化数据源连接池</li> </ul> </li> <li> <p><strong>应用程序部署</strong></p> <ul> <li>读取 <code>persistence.xml</code></li> <li>Hibernate 初始化</li> <li>建立数据库连接</li> </ul> </li> </ol> <h2>代码示例</h2> <p>通过导入 JPA 规范和其实现 Hibernate,以及 Spring 框架,我们可以正确操作数据库中的数据。</p> <pre><code>@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String username; // getters and setters } @Service @Transactional public class UserService { // 通过name属性指定persistence-unit @PersistenceContext(unitName = "mainPU") private EntityManager entityManager; public User findById(Long id) { return entityManager.find(User.class, id); } public void save(User user) { entityManager.persist(user); } } </code></pre> <h3>总结</h3> <p>我们简单实现了一个完整的数据库安全配置方案:</p> <ol> <li>使用 HashiCorp Vault 进行密钥管理</li> <li>通过 Kubernetes 进行配置注入</li> <li>结合 Tomcat 数据源和 JPA 实现数据库连接</li> </ol> <p>这种方案既保证了安全性,又保持了良好的可维护性,适合企业级 Java 应用的开发部署。</p> <p>--&gt;</p> Charles Bridgehttps://blog.kaiyikang.com/posts/2018/charles-bridge/https://blog.kaiyikang.com/posts/2018/charles-bridge/Fri, 07 Sep 2018 00:00:00 GMT<p>Spanning the Vltava River, Charles Bridge has a history of about 650 years.</p> <p><img src="@assets/2018/p53772590.jpg" alt="" /></p> <p>The bridge is approximately 500 meters long, bookended by towers with steep, pointed roofs. For just 100 CZK, you can climb a tower to take in the panoramic scenery of Prague.</p> <p>The bridge is lined with 30 statues in total. Originally, 26 Baroque statues were erected in the late 17th and early 18th centuries, and four more were added later.</p> <p>Certain parts of these statues have been polished to a shine by the touch of countless tourists. It suggests that behind each statue lies a legend or story that blurs the line between reality and fantasy.</p> <p><img src="@assets/2018/p53772596.jpg" alt="" /></p> <p>I tried to find the right words to accurately describe the feeling of walking on Charles Bridge.</p> <p>Feeling a bit lost for words, I looked to writers and poets for inspiration.</p> <p>For instance, Kafka once said, "My life and my inspiration all come from the great Charles Bridge."</p> <p>That is a wonderful quote, but it didn't quite resonate with my own feelings.</p> <p>So I kept thinking, and finally, a very ordinary word came to mind: <strong>"Fitting."</strong></p> <p>When I walk on the bridge, every brick under my feet seems to fit perfectly to form the path.</p> <p>Every breath of wind touches my skin at just the right angle and with just the right strength.</p> <p>The sun and clouds seem to command the light, making it converge in exactly the right spots.</p> <p>The castle on the hill in the distance, and the river flowing nearby—everything is exactly as it should be.</p> <p>Adding to the atmosphere, street performers play violins, cellos, and hand drums, bringing a delightful layer of sound to the experience.</p> <p>The bridge feels as if it has come alive.</p> <p>Everything is so fitting and harmonious. I felt unwilling to leave; I just wanted to linger on the bridge endlessly.</p> <p><img src="@assets/2018/p53772610.jpg" alt="" /></p> <p>Some places require you to wait quietly for their beauty to reveal itself. If the weather is bad—raining or foggy—you might see nothing at all.</p> <p>But Prague at night is different. No matter the weather, it presents its most perfect form to you. This kind of generosity and tolerance is unmatched by other places.</p> <p>I find it hard to describe this feeling with my own words, so I will quote the great German writer Goethe: "In the crown of cities, like a precious stone, Prague is the most precious of all."</p> Imperfection is the New Perfectionhttps://blog.kaiyikang.com/posts/2018/imperfection-is-also-perfection/https://blog.kaiyikang.com/posts/2018/imperfection-is-also-perfection/Sat, 08 Sep 2018 00:00:00 GMT<p>Recently, I encountered two types of people online: those who seemed perfect but had flaws, and those who appeared absolutely flawless. After comparing the two and reflecting on my interactions, I came to a realization: <strong>imperfection is, in fact, a form of perfection.</strong></p> <p>From a strictly linguistic perspective, this statement is a contradiction. However, if we interpret "perfection" as a state of "harmony," it becomes much easier to understand.</p> <p>Mathematically, we might view 0 and 1 as perfect numbers. Yet, when we consider the Golden Ratio—<strong>0.618</strong>—we might feel a sense of awe. Compared to the absolutes of zero and one, this number feels far more harmonious and natural.</p> <p>Perfection can be described as "going to extremes," whereas "imperfection" implies a sense of compromise.</p> <p>This is particularly evident in the world of art. Many artists pursue extremes. <strong>Piet Mondrian's</strong> grid paintings consist solely of straight lines and blocks of pure color. <strong>Yayoi Kusama</strong> covers pure yellow backgrounds with dense, obsessive black dots. <strong>Jackson Pollock</strong> wildly splashed paint onto canvas, with his work <em>No. 5, 1948</em> fetching $165 million at auction.</p> <p>I am not suggesting that the pursuit of the ultimate or the perfect is bad. But consider the Renaissance. Take the famous <em>Mona Lisa</em>, for example. Its carefully crafted Golden Ratio feels so harmonious that it has remained a classic for centuries.</p> <p>When I searched online for interpretations of imperfection, I stumbled upon <strong>Wabi-sabi (わび・さび)</strong>. It is one of my favorite aesthetic styles.</p> <p>This Japanese aesthetic is also a spiritual pursuit. It embodies "the wisdom of imperfection, and the ability and attitude to perceive beauty in imperfect things."</p> <p>We have already realized that this world can never be truly "complete"; therefore, the world cannot be perfect.</p> <p>Appreciating perfect things is simple. However, appreciating things that are <em>not</em> perfect requires a different kind of wisdom. This explains why so many people complain about life's setbacks and lament their bad luck.</p> <p>In engineering, perfection does not mean running a machine at full load to achieve maximum output. Instead, perfection is about finding harmony and stability. Engineers must make trade-offs between performance, durability, and ease of use. Leaning too heavily in any one direction can lead to system collapse—a result that neither designers nor users wish to see.</p> <p>Of course, not pursuing perfection doesn't mean having no aspirations. In fact, pursuing "imperfection" is often harder.</p> <p>In terms of self-perception, we naturally gravitate toward perfect people or things and often hold negative views toward imperfection. Deep down, our subconscious seems to sway our will without us noticing. By the time we realize it, we have already become paranoid, feeling lost because we cannot achieve that elusive perfection.</p> <p>As mentioned above, viewing imperfection as perfection, and making harmony the ultimate goal, requires wisdom.</p> <p>Let’s go back to the two types of people I mentioned at the start. Chatting with the "imperfect" person brought me great joy. But interacting with the "seemingly perfect" person made me feel like I was walking on eggshells; I became overly cautious. Over time, my mindset turned negative—feelings of suspicion, jealousy, and disdain arose spontaneously.</p> <p>But isn't this negative feeling also a case of "seeking perfection and failing"?</p> <p>May we all travel further on the path of wisdom.</p> <p>Thank you for reading.</p> Bauhaus Experiencing (Part 1)https://blog.kaiyikang.com/posts/2023/experiencing-bauhaus-part-1/https://blog.kaiyikang.com/posts/2023/experiencing-bauhaus-part-1/Sat, 15 Apr 2023 00:00:00 GMT<p><img src="@assets/2023/p93481129.jpg" alt="Header Image: Bauhaus Building" /></p> <h2>Understanding and Enjoyment</h2> <p>Let's talk about Bauhaus.</p> <p>If you search for the German word "Bauhaus," the results will likely point you to the nearest hardware store. The word is a very dry combination of "Bau" (building) and "Haus" (house). Is it a good word? From a purely artistic perspective, perhaps not. We associate it more with construction markets than with a comprehensive design philosophy. However, if we lower our perspective and look from a more general angle, we get a sense of something practical, functional, and closer to everyday life.</p> <p>I think it is a good word. It has successfully and thoroughly fulfilled its mission. It has infiltrated our modern lives. It has turned into an invisible conceptual air. It is absorbed and understood. It surrounds us silently.</p> <p><img src="@assets/2023/p93481001.jpg" alt="From inside to outside, it surrounds us" /></p> <p>We should have a natural and intuitive understanding of Bauhaus. After all, we already have experience with it. Because it is so natural, it feels like watching a movie with spoilers. Long before I set off, I could already roughly outline the shape of this journey.</p> <p>Of course, I won't be discouraged by this. Based on personal experience, I suddenly realized that the triggering of emotions and feelings sometimes requires a rational foundation. A simple example is fingerstyle guitar. Through crisp rhythms and gentle or flashy tunes, people can feel the deep emotions conveyed by the player on stage. But actually, if you ask these players what they are thinking at that moment, the answer will definitely not be the joy of love or the sadness of heartbreak. Instead, they are thinking about the dynamics of the measure. They are thinking about specific transitions and preparations. It is a series of very serious technical details. We might feel disappointed knowing the truth. Yet, cold knowledge is indeed the prerequisite for a series of emotions.</p> <p>Returning to travel, if one sets out with an overly innocent mindset, there is a certain probability of just wandering around in the realm of pure sight and sound. It does not emphasize direct shock to the five senses. That might have shocked people a hundred years ago, but modern people are almost numb to it. Instead, it needs knowledge as a catalyst to slowly stimulate the feelings.</p> <p>Therefore, I had to read more articles and watch related documentaries before the trip. The goal was to understand its position in the historical timeline. This allows me to truly grasp how profoundly it has influenced our lives. Only in this way, when we are physically there, will those accustomed designs and structures jump out and shine. They activate the nerves in the brain. This allows us to obtain profound and unparalleled pleasure in the spiritual realm.</p> <h2>A Dry Introduction</h2> <p>Bauhaus was born in Weimar. It was originally a design school. It advocated the combination of art and craftsmanship. This was a very novel concept at the time. Later, due to political issues and other problems, it moved to Dessau. Finally, it was forced to close under pressure from the Nazis. It existed from 1919 to 1933. Although its life was short, its influence was extremely strong precisely because it was suppressed by negative forces. It eventually spread widely throughout the world.</p> <p>Departing from Munich, you arrive at Lutherstadt Wittenberg station. Then transfer to the RB train to reach Dessau Central Station. It is not on the main high-speed rail line. Also, the area is sparsely populated. Therefore, driving there would be a better choice.</p> <p><img src="@assets/2023/p93481018.jpg" alt="Mini train station, few shops, few people" /></p> <p>The main train station is very pocket-sized. Its underground passage splits into two directions. One leads to the city center. The other leads to the Bauhaus building.</p> <p>On the way to the destination, you will pass the Hochschule Anhalt (Anhalt University of Applied Sciences) campus. This school is an applied technology school. Although it focuses on engineering, it still includes design majors. This part is responsible for Bauhaus. It can be considered a continuation of the academic tradition from back then.</p> <p>The Bauhaus building does not refer to just this one building. It includes the school building and the nearby "Masters' Houses" (model houses rented by professors).</p> <p>Walking towards the city center, you can find the boxy Bauhaus Museum. To commemorate the 100th anniversary of the founding of Bauhaus, it opened in 2019.</p> <h2>Bauhausgebäude Dessau</h2> <p><img src="@assets/2023/p93481143.jpg" alt="The most classic facade, under maintenance" /></p> <p>I don't know how to call this school building in Chinese. If I translate it directly as "Bauhaus Building in Dessau," it sounds plain. It has no special flavor. But on second thought, perhaps having no flavor is also "a kind of flavor."</p> <p>The facade is constructed of black, white, and gray. The overlapping square geometries sleep quietly there. An asphalt road divides it in two. A suspended corridor connects the middle. The main door is bright red. It is out of place with the cold outer walls. It sits coldly on one side of the road. On the upper right corner of the main door eaves, the word "Bauhaus" tells us that this is the Bauhaus building.</p> <p><img src="@assets/2023/p93480966.jpg" alt="Red door, right-aligned text" /></p> <p>It evoked many of my impressions of architecture.</p> <p>For example, the fact that it is bisected by a road reminded me of the building where I did my master's degree at the Technical University of Munich. Every time we went to class or the cafeteria, we had to cross a road. Although there wasn't much traffic, the intermittent stops were very annoying. The double-push main entrance, with stairs going up in the middle and down on the sides, is the school building from my high school memories. The large sheets of transparent glass are the scenery I see when I look up while doing homework in a coffee shop. Even now, I still like similar elements.</p> <p>My current experience feels so natural. Yet the source of this experience comes from over a hundred years ago. Whenever I think of this, I feel a sense of awe. It embodies the foundation and abstraction of my architectural experience. Such an embodiment is also a concrete manifestation of how idea genes can be effectively inherited and disseminated.</p> <p><img src="@assets/2023/p93481036.jpg" alt="Transparent glass and frames, elements I like" /></p> <p>If I were to use one word to describe this experience, I think it would be "familiarity." Unlike being immersed in life, it has more power and aggression. It actively moves forward to relate to our modern lives.</p> <p>Its powerful force is not shown through religious-style grandeur. Instead, it surrounds life like a stubborn mist. Its manifestation lies not in our noticing it, but in our ignoring it. It is like looking up and thinking it is the sky. But we never realize it is actually just a wall.</p> <p>This force does not make us wither or feel fear. On the contrary, it makes us develop a considerable dependence. I like to use details of life as examples. It is similar to the order of drying your body after a shower. Usually, this order is fixed. You trust this order very much. You believe this method will dry your body perfectly. It is never really noticed. But we trust and apply it. The order of this building brings me a sense of familiarity and stability. I don't need to check a manual. I don't need to probe carefully. From the moment I push open that red door, my experience of the building itself is already known by heart.</p> <p><img src="@assets/2023/p93481045.jpg" alt="Main entrance viewed from the corridor" /></p> <p>Similar phenomena are actually more common in philosophy and thought. We think we have original thoughts. Actually, most of them were mentioned hundreds or thousands of years ago. Through communication with parents, teachers, and friends, and through independent reading and learning, we gain insight into the genetic memory of thought. This power is the air we rely on for survival.</p> <p>As mentioned at the beginning of the article, the implantation of spirit and experience is not easily detected. It can only be discovered through prior rational learning and thinking. Once detected, this power inevitably brings special enlightenment and spiritual tremors.</p> <h2>Sleeping with a Century</h2> <p><img src="@assets/2023/p93481179.jpg" alt="A cost-effective choice" /></p> <p>This building contains basic modules that prioritize function. The masters constructed the entire school building through orderly combinations. The advantage of this organization is clear. Even after a hundred years, although the function of the school building has disappeared, activating different modules can create buildings rich in other functions.</p> <p>The original shop was changed into a souvenir shop and front desk. The cafeteria was converted into a bar and restaurant. As for the original studios, they naturally became exhibition areas for works. The student dormitories were converted into a hotel. A historical building that combines art tourism with accommodation and dining has thus regained its brilliance in modern times through recombination.</p> <p><img src="@assets/2023/p93481042.jpg" alt="Outside the window of the front desk" /></p> <p>Check-in time is 2:00 PM.</p> <p>At the front desk, I received a small booklet. The introduction inside proudly stated, "Staying here is a very special experience, incomparable to an overnight stay in a regular hotel." Walking out from the main entrance to the side wing of the building, you can see a small entrance.</p> <p>According to the official description, this student dormitory was very luxurious at the time. One floor has about 6 single rooms. There is a shared kitchen, a bathroom, and a toilet. Because there was a cafeteria, the kitchen located at the far end is not large. To adapt to the water pipe system of the entire building, the shower part in the bathroom was raised. This formed an interesting "room within a room." The separation of wet and dry areas is done very reasonably. It is worth referencing for future home decoration. The toilet part is simpler. Through the exposed water pipes, you can intuitively see that the sewage from each floor converges through this vertical pipe and flows out.</p> <p>The single room is not small. It is very square. It has a sink, a classic red cabinet and desk, two chairs, a large bed, and two lamps. Facing outward is a huge rectangular window. On the left is a door leading to the outer balcony. The balcony is very small. It also has a square shape. To facilitate drainage, the shape of the floor extending out curls downward. It makes one feel unsteady. Of course, for people a hundred years ago, this mini balcony was a platform for explosive inspiration. Through black and white photos from that time, we can see young students crowding together for parties, creation, and photography. They did not care about the danger at all.</p> <p><img src="@assets/2023/p93481046.jpg" alt="Chairs, bright red table" /></p> <p>I searched my memories. What kind of accommodation atmosphere is similar to this? Piecing things together, it feels like a guesthouse from around the year 2000. Although the space is much more spacious, some of the furnishings inside are angular. They have a unique style and design sense. Yet I cannot say exactly why.</p> <p>Of course, the above comparison is just a forced match. When it comes to specific details, there is a very large gap. For example, that red double-door wardrobe. The doors are very thick. Inside there are storage cabinets. After opening 180 degrees, it can be combined with the middle wardrobe to form a new combination cabinet. It is very clever. This kind of furniture design and attention to detail is definitely not found in ordinary hotels. In terms of overall feeling, saying it is like a guesthouse is actually trying to express its practical-first approach. For instance, the floor is flat and smooth. Walking on it does not make the noise of old wooden boards. The transitions at the wall edges and corners are simple and regular. You never get tired of looking at them.</p> <p>Still, thinking about these designs, although they might show slight traces of the era now, makes one sigh sincerely. Considering this is a design from a hundred years ago, one wonders what kind of thinking and inspiration created such advanced creations.</p> <p><img src="@assets/2023/p93481057.jpg" alt="Looking up from the stairwell" /></p> <p>The space of the dormitory building is like a "lite" version of the Masters' Houses. The capacity is smaller. But the design thoughts behind it have been retained. The railings in the corridor are bright red. The ceiling uses other different colors depending on the floor. Looking from a unique angle, it looks like a work created by a student of Mondrian. The slightly chaotic structure is full of youthful vitality.</p> <p>Roaming through it, I imagine how students back then lived, studied, and created here.</p> <p>In their spare time, everyone would crowd into a classmate's small room. Those lucky enough to find a seat would sit or lie down. Those with average luck would sit on the floor or just stand. Everyone would gather in a circle. They would listen to a classmate with ideas emotionally recount their wonderful experiences of battling wits with professors. Or they would explain some brand new concept and idea. When the sun angle was right, warm sunlight would shine into the room. The classmate near the window would open the door to the balcony. Everyone would pour onto the narrow balcony. From up high, they would greedily enjoy the passion and vitality of spring.</p> <p>Perhaps the room I am staying in is where a certain master once lived.</p> <p><img src="@assets/2023/p93481041.jpg" alt="Balcony and blue sky" /></p> Bauhaus Experiencing (Part 2)https://blog.kaiyikang.com/posts/2023/experiencing-bauhaus-part-2/https://blog.kaiyikang.com/posts/2023/experiencing-bauhaus-part-2/Sun, 16 Apr 2023 00:00:00 GMT<p><img src="@assets/2023/p93482271.jpg" alt="Header Image: Doorway of the Masters' Houses" /></p> <h2>Meisterhäuser (Masters' Houses)</h2> <p><img src="@assets/2023/p93482283.jpg" alt="Exterior of the building" /></p> <p>The name sounds cool. But in reality, they are just villa buildings where professors lived. They were not only used for living. They were also used as studios and for organizing teaching activities. They served diverse functions.</p> <p>Four independent villas are located north of the main school building. They stand in a tall, open forest. Admiring them from the outside feels like appreciating sculptures in nature.</p> <p>The tour experience of the villas is quite novel. Facing the villa buildings from left to right, the first building is set up as a ticket office. The second and fourth buildings are open for visits. The third building is still used as an artist's residence and studio. Because it is a private residence, the entrance is narrow. It only leaves enough width for one person to enter or exit. The main door is closed by default. If you want to buy a ticket or visit, you need to knock or ring the doorbell. Staff inside will welcome you before you can enter the building.</p> <p>The main colors of the architecture are black, white, and gray. The shapes are not regular squares. Instead, they are an interweaving and combination of three-dimensional rectangles. Depending on the combination, the appearance of the building presents different states. But because they share "basic components," the overall style and function are consistent. This seemingly contradictory state makes the originally dull group of buildings more profound.</p> <p><img src="@assets/2023/p93482311.jpg" alt="Colors and floor-to-ceiling windows" /></p> <p>However, the enthusiasm of these architects did not end there.</p> <p>When you approach the building and observe it from all sides, pure red, yellow, and blue colors will flicker in your vision. Pure colors are like flying fish. They shuttle actively and rise and fall on the vast sea surface. They often appear in inconspicuous places. For example, inside window frames or on the ceilings of the building's exterior. I view them as the restrained and controlled passion of these masters. The trivial embellishments of the old era have not yet detached from people's minds. It remains in the hidden corners of modern design in a profound way. It is both a sudden flash of thought and a silent request. It invites you into this building and into this new era with a solemn and regular posture. Just as every wall is the gray-white of cement, you may be treated with silence. But those lively colors that suddenly flash tell you something. It is definitely not cold or rejecting. The master's thinking and inspiration permeate every detail. You will experience what independence, freedom, and humor are from it.</p> <p><img src="@assets/2023/p93482282.jpg" alt="Wassily Chair" /></p> <p>When organizing photos, I found that Bauhaus design has a kind of continuity. The form may be scattered, but the spirit is not. Although the forms of the works are ever-changing, it is like a sphere drawn from a core point. When you experience it by circling around, you not only see the different surfaces of the sphere. You can also see the unified and harmonious core from behind. This unique continuity and cohesion seem rare in other artistic concepts.</p> <p>As I passed through doors and tried to capture the scenes inside the buildings, the shadows of Kandinsky and Mondrian appeared in my camera time and again. I was surprised by the sincerity and enthusiasm revealed therein. They lived in the building. They did not just engage in dry artistic conceptual fantasies. Instead, they thought and breathed together with the space. They merged the skin of the space into their hearts. Then they unreservedly returned the fruits of their thinking to the space and the design works under their pens. This achieved a positive feedback loop. A channel of consensus formed between them and the design objects, between the inside and the outside. Design inspiration and enthusiasm flowed through it. Because of this honesty, we are able to perceive it. We become the ones who receive the grace.</p> <p><img src="@assets/2023/p93482335.jpg" alt="Doorway 2" /></p> <p><img src="@assets/2023/p93482345.jpg" alt="No filter" /></p> <h2>Bauhaus Museum</h2> <p><img src="@assets/2023/p93482295.jpg" alt="Lower space of the museum" /></p> <p>The museum is located in the city center. It is only a ten-minute walk from the train station. The main street is under repair. There are few cars on the road. Also because of the holiday, there are very few people on the street. The quiet air is filled with loneliness and desolation. In the small park in the city center, elderly people are strolling. Younger students are gathered together playing ball. Finding noise amidst the quiet is not commonly seen in Munich.</p> <p>The museum building is a very standard rectangular block. To prevent it from being a boring hard lump, the dark transparent outer wall adds a lot of interest. If you look closely, you can find visitors resting inside.</p> <p>Pushing the door open, the entire floor comes into view. The ticket office, coffee shop, and the entrance to the upstairs exhibits are all placed on one side of the building. The other spaces are true to their name. They are really "space." Shelves selling souvenirs, tables and chairs for people to rest, and large building blocks for children to play with are scattered messily in the space.</p> <p>The wide area and extremely high ceiling make these scattered modules look even more mini. Suddenly, space as an abstract word becomes intuitive and emotional. I stand here and look over there. I involuntarily start to imagine what should fill this area. Maybe some art exhibits? Or let actors put on a show? The designer is quite mischievous. They specifically planned a vast but limited space to lure out people's desire to fill it. Conflicting with imagination, reality is empty. The huge glass curtain wall reflects the bits and pieces in the space. The extreme contrast outlines the contours of the space. I look at the glass and see my blurry self. I deeply feel the embrace of the polygon.</p> <p><img src="@assets/2023/p93482286.jpg" alt="Huge transparent material" /></p> <p>Upstairs is the exhibition hall. It includes numerous works related to Bauhaus. Rather than works, it is better to say lifestyles. It ranges from small daily furniture to large buildings. It is as specific as the placement of pots and pans, and as abstract as the concepts of circles, squares, and triangles. The largest exhibition hall unfolds from the perspective of characters. It not only lists the professors' works but also expansively includes student works from the courses. You can notice how important the teacher's instruction is to the student's style.</p> <p><img src="@assets/2023/p93482287.jpg" alt="Teacher's work" /></p> <p><img src="@assets/2023/p93482288.jpg" alt="Student's work" /></p> <p>Every piece of Bauhaus work seems to move towards the direction of "being what it is."</p> <p>Take an old-fashioned chair as an example. From the chair legs to the connection with the seat, and then to the chair back, you can find traces of decoration. Whether it is natural carving, mythical dragons and phoenixes, or simple curved lines, they are all shouting to people. We are beauty. We are art. As for the chair itself, it retreats to the background and becomes a carrier of beauty.</p> <p>The owner places it in the room. They introduce the finely crafted craftsmanship on the chair to every guest. After the introduction, they ask the other person to sit on it. They drink tea and chat as usual. The exquisite skills that were just marveled at are all ignored under the buttocks.</p> <p>Bauhaus intentionally strips away these forking paths in the garden. It attempts to expose the most basic attributes of the chair. Through contemplation, the creators directly connect the former "being" with the latter "being" (the concept of the chair itself). Thereby, they lead people lost in the garden directly to the exit of the maze.</p> <p>I feel awe and moved by this long-lost straightforwardness and the courage to expose.</p> <p><img src="@assets/2023/p93482297.jpg" alt="It is a return" /></p> <p>Roaming in the "home market" named Bauhaus, what I feel is order and contradiction.</p> <p>Order refers to the balanced considerations made by artists for the sense of design and craftsmanship. I do not feel its messiness. Even if different works by different authors are put together, they appear very harmonious. For example, combining a set of furniture using only basic square shapes. This product based on the same rules and concepts is the most powerful embodiment of the sense of order.</p> <p>Contradiction is not that complicated to understand either. Sincere and clear thinking is like red flame-like enthusiasm. This is the artists attempting to dig works out of general human intuition through the most authentic and straightforward way. We ordinary people can use them immediately even without an instruction manual placed on the side. On the other end of the scale is calm and serious insight. It is like blue ice crystal-like abstraction and simplicity. Pure red, yellow, and blue primary colors, and straight and smooth geometric curves come from physical nature. Yet they have never walked out of the School of Athens. They have never approached people's most practical lives.</p> <p><img src="@assets/2023/p93482293.jpg" alt="Linear order" /></p> <p><img src="@assets/2023/p93482332.jpg" alt="Closeness and alienation" /></p> <p>The ice-blue abstract essence is transported to this current era through the most passionate red expression. It has become a guide for people in modern life. Such a contradiction has both a human side and a non-human side. The complexity of multiple layers makes it a rich fertile ground. Any kind of fruit can thrive in it with just a little modification and adaptation. I think this is the secret of why Bauhaus still plays an important role in our vision even after a hundred years.</p> <p><img src="@assets/2023/p93482300.jpg" alt="The secret ahead" /></p> Lisbon, A Damp and Mottled Rainbowhttps://blog.kaiyikang.com/posts/2023/lisbon-a-damp-and-mottled-rainbow/https://blog.kaiyikang.com/posts/2023/lisbon-a-damp-and-mottled-rainbow/Sun, 22 Jan 2023 00:00:00 GMT<p><img src="@assets/2023/p92475825.jpg" alt="" /></p> <h2>Overview</h2> <p>Portugal sits at the westernmost edge of the Eurasian continent. My geographical intuition is poor, whenever I choose travel destinations on a map, I subconsciously overlook this country. It wasn't until I had been in Europe for many years that I finally thought to make a special trip here.</p> <p>Upon getting off the plane, the damp, salty, lukewarm air rushed in, soaking my nasal cavity. Thick, sturdy clouds floated above the wide airport, exerting a strong visual pressure. I am used to traveling within inland Europe, so I rarely see such a massive contrast. The greater the contrast, and the more bizarre the experience, the richer the flavor of the journey becomes.</p> <p><img src="@assets/2023/p92475747.jpg" alt="" /></p> <p><em>At Lisbon Airport</em></p> <p>Because of its proximity to the sea, the entire city is often shrouded in sea fog in the early morning. Even as the sky gradually brightens, the undulating streets remain mysterious and trance-like. The streetlights are dim and yellow, lowering their eyes and exuding the weariness of dawn, as if humming something in a low voice, guiding lost souls into a labyrinth with no known end.</p> <p>As the sea fog disperses, the sun reveals its true form. Even in the harsh winter, its gentle rays shine upon the earth, and my body actually warms up. The temperature in Lisbon is not low—hovering between 10 and 16 degrees Celsius in winter—though the nights are cooler, requiring just one extra layer of clothing to cope.</p> <p><img src="@assets/2023/p92475749.jpg" alt="" /></p> <p><em>Lisbon in the early morning</em></p> <p>Barcelona, another coastal city, gave me more sun and sea, full of golden energy. Although Lisbon is also close to the sea, what it incubates more is the ocean's coarseness and recklessness.</p> <p>Looking down at Lisbon from the air, its streets are twisting and winding. The meandering paths are like capillaries, climbing rhythmically all over the city following the rise and fall of the terrain. The buildings on both sides of the roads are thin and tall. Although their textures and appearances vary widely, they are packed tightly at the joints without the slightest gap. The twisting roads force the buildings to arrange themselves in a snake-like formation. Due to the winding nature of the roads, nearby walls often obscure the scenery behind them. Buildings and walls play hide-and-seek, being amongst them constantly stimulates my nerves for exploration. But this is merely horizontal concealment—what pushes this thrill to the extreme is the vertical dimension.</p> <p><img src="@assets/2023/p92475762.jpg" alt="" /></p> <p>Overlooking the city</p> <p>Unlike most core European cities, Lisbon is built around hills, and many buildings in the old town have significant elevation differences. You might be walking along and suddenly encounter an upward slope. The road extends into the sky, invading your entire field of vision. I climb the slopes along the roads, trading my breathing rate for elevation gain. I would think I had reached the end of the road, only to find I had merely arrived at a slightly flatter mid-mountain point.</p> <p>Standing on this precious flat ground, I try to find a suitable gap in the cluster of buildings. Through a long, narrow void, I see more residential buildings diving downwards along the mountains, flooding toward the distant coast, and finally taking flight, rising into the grey-blue sky.</p> <p><img src="@assets/2023/p92475779.jpg" alt="" /></p> <p><em>The dive of a massive downward slope</em></p> <p>With its three-dimensional and vivid posture, Lisbon interprets what true urban 3D exploration is.</p> <p>However, excessive vitality also causes quite a few troubles for travel. The hotel I booked was located halfway up a hill. Although there were bus stops nearby, they were either at the top or the bottom of the slope. Every time I returned exhausted, I still had to expend extra physical energy to deal with the additional elevation. But as the number of trips increased, I learned to take shortcuts. Instead of relying on navigation, I kept an eye on the stops, specifically choosing to depart from the station at the bottom of the hill and return via the station at the top. This back-and-forth meant I only ever had to walk downhill.</p> <p><img src="@assets/2023/p92475793.jpg" alt="" /></p> <p><em>City Garden</em></p> <p>Constrained by the narrowness of the city streets, public transportation is designed very uniquely. Common buses have very long carriages to carry more passengers, but because they cannot adapt to the narrow paths, they are only seen on broad main avenues. Once inside the narrow lanes, only special short, chubby buses can shuttle through. They are much shorter than regular buses, with a charmingly naive form. Trams are similar, but compared to the former, the latter has a much longer history. Whether it's the opening and closing of doors, the interior decoration, or the manual changing of the tracks, they lean more towards a natural mechanical style. This has made them a unique calling card for Lisbon.</p> <p><img src="@assets/2023/p92475802.jpg" alt="" /></p> <p><em>The old Route 12E, still maintaining its form from years past</em></p> <p><img src="@assets/2023/p92475803.jpg" alt="" /></p> <p><em>Interior decoration</em></p> <p>Lisbon's architecture oscillates between coarseness and refinement. Due to the sea breeze, black-green stains often appear on the surfaces of stone buildings. They prove that time is constantly extending, like a heraldic crest of personality, giving each building a unique character.</p> <p><img src="@assets/2023/p92475810.jpg" alt="" /></p> <p><em>Mottled architecture</em></p> <p>Tiles (Azulejos) are another unique landscape of Lisbon. Sometimes they cover an entire building in large sheets, looking magnificent, other times they cover only a small part of a wall, broken and decaying. Although the tiles are rigid squares, the patterns on them are ever-changing. Using mathematical fractal techniques, the patterns can stand alone on a single tile or link with surrounding tiles to form a brand new, massive design. With such magical variations, people never get bored.</p> <p><img src="@assets/2023/p92475812.jpg" alt="" /></p> <p><em>Large expanses of tiles</em></p> <p><img src="@assets/2023/p92475819.jpg" alt="" /></p> <p><em>Tile Museum</em></p> <p>Similar aesthetics, aside from being influenced by foreign cultures, are also inspired by local plants. Because of Lisbon's humid and warm environment, local vegetation is exceptionally lush. Even in December winter, one can see patches of subtropical flora. Huge leaves and towering branches grow in the city's micro-parks. Large and small plant textures have also been adopted into the elements of tiles and architecture. Compared to the Art Nouveau seen in Paris or Barcelona, the fusion of cultural and natural elements here is rougher and more primitive. The transition of details is far less smooth than the former. Although clumsy, it is incredibly cute, radiating a primitive passion.</p> <p><img src="@assets/2023/p92475824.jpg" alt="" /></p> <p><em>Plants for sale, very lush</em></p> <p>Lisbon is a radiator of the oceanic spirit. It not only contains the roughness and wildness of the sea, but the people living here are also summoned by it, sparking exploration and passion. This ensures that even amidst the waves, the city holds infinite exquisite treasures.</p> <p><img src="@assets/2023/p92475832.jpg" alt="" /></p> <p><em>City sculpture fountain</em></p> <p><img src="@assets/2023/p92475847.jpg" alt="" /></p> <p><em>Pier pillars</em></p> <h2>Transport &amp; Plans</h2> <p>After arriving at Lisbon Airport, follow the signs to the metro station, where there are several ticket machines. For the first time, you need to buy an extra rechargeable card for 0.50 Euros. I bought a 24-hour day pass for 6.50 Euros. You need to swipe the card before entering the gate. The first swipe activates the day pass, which lasts for 24 hours. The recharge machines are generally in the metro stations. If you happen to run out of money on the card and need to take a bus, you can pay on board (but it is more troublesome, so I didn't try it).</p> <p>The metro, buses, and trams are the most commonly used means of transportation. The metro is divided into four lines: the Blue Seagull, the Yellow Sunflower, the Green Caravel, and the Red Orient. The airport is at the terminal station of the Red Line and just a few stops on the metro will get you to the city center quickly.</p> <p><img src="@assets/2023/p92475822.jpg" alt="" /></p> <p><em>The four metro lines</em></p> <p>The hotel I chose was located halfway up a hill in the west of the city, relatively close to Rato, the terminal station of the Yellow Line.</p> <p>I stayed in Lisbon for a total of three days. On the first day, I went to Sintra, a town near Lisbon, which has many castle landscapes. From the center of Sintra, you can also reach Cabo da Roca, the westernmost point of continental Europe. On the second day, I visited famous attractions in Lisbon city center: the National Tile Museum, the Cathedral, and the Carmo Convent, as well as the old town, riding the Santa Justa Lift. On the third day, I visited the Jerónimos Monastery.</p> <h2>Pena Palace in Sintra</h2> <p>Looking at the map, Portugal appears as a long strip. The distance from east to west is not very far. Departing from Rossio train station in Lisbon, a one-hour suburban train ride takes you to the station in Sintra.</p> <p>It is worth noting that tickets for this train, as well as bus tickets upon arriving in the suburbs, need to be purchased separately. My plan was: go to the manual ticket counter at Rossio station and buy a "Train and Bus Ticket." I recall the price being around 16 Euros. This ticket includes the round-trip train fare from Lisbon to Sintra and the local bus fare in Sintra. Of course, you can choose to buy them separately, but I chose the lazier method.</p> <p><img src="@assets/2023/p92475848.jpg" alt="" /></p> <p><em>Ticket office in the early morning</em></p> <p>At around seven or eight in the morning, I arrived at the manual ticket counter. There was hardly anyone there. After briefly communicating with the ticket seller, he gave me a yellow card ticket. With this card, I could ride any bus in Sintra.</p> <p>The suburban railway line is a bit dilapidated—not just the carriages, but the scenery along the way as well. Without the glossy exterior of Lisbon city surrounding it, the whole presents a sense of desolation. Aside from tourists, the locals in the car were of various skin tones and ethnicities, dressed very plainly. It truly restored the most vivid, life-like posture of Portugal.</p> <p><img src="@assets/2023/p92475876.jpg" alt="" /></p> <p><em>Suburban railway</em></p> <p>Sintra's central train station is not big. "Central" is actually an excessive boast. Small bungalows stuffed with mini service offices that can only squeeze in a few employees. Conversely, the colorful tiles inside the station building are very eye-catching, even somewhat incongruous.</p> <p><img src="@assets/2023/p92475884.jpg" alt="" /></p> <p><em>Walls inside the train station</em></p> <p><img src="@assets/2023/p92475881.jpg" alt="" /></p> <p><em>Sintra route map</em></p> <p>The bus station is right next to the train station. Buses are the main tool for visiting various attractions. Although you can choose to drive, the rugged and narrow mountain roads are a huge challenge to driving skills. It's better to leave the bumps to the experienced bus drivers, consider it a kind of fun in the journey. The bus route is circular, so the pick-up and drop-off locations are the same—no need to worry about not finding the station.</p> <p><img src="@assets/2023/p92475862.jpg" alt="" /></p> <p><em>Lush vegetation on the mountain</em></p> <p>Pena Palace is located on the highest mountain in the suburbs. When the weather is good, climbing up to Pena Palace allows you to overlook all of Lisbon.</p> <p><img src="@assets/2023/p92475854.jpg" alt="" /></p> <p><em>City shrouded in clouds and mist</em></p> <p>You can choose to buy tickets online or purchase them on-site via automatic ticket machines. I went quite early, so there were few people near the ticket machines, but by noon, the line was very long. When buying a ticket, you need to choose a time to enter the castle. If you go too early, even if you can enter the park, you still need to queue in front of the castle and can only enter at the scheduled time. After entering the castle, there are two or three more ticket checks, so remember to keep your ticket at all times.</p> <p><img src="@assets/2023/p92475861.jpg" alt="" /></p> <p><em>The ticket check at noon, full of people</em></p> <p>I find it hard to evaluate the style of this palace architecture. Three-dimensional geometric shapes of different colors and forms are spliced together, yet the sculptures and decorations on the door frames and walls are very delicate, as if trying their best to cover up the dissonance brought by the splicing.</p> <p><img src="@assets/2023/p92475874.jpg" alt="" /></p> <p><em>Exterior wall of the palace</em></p> <p><img src="@assets/2023/p92475880.jpg" alt="" /></p> <p><em>Mix and match of different colors</em></p> <p><img src="@assets/2023/p92475882.jpg" alt="" /></p> <p><em>Red tower</em></p> <p>Romanticism can be divided into many types: there are the perfectly ideal ones, and the trivial, intuitive ones. I'm not sure if it has to do with the German architect, but it vividly displays what "it looks like in the eyes of outsiders" from a very intuitive perspective. This interpretation and elaboration of local culture by foreign culture is also a very important theme in the cultural field. This castle is like Dürer's Rhinoceros. Aesthetics have been placed in the second position, and what matters is magnificent imagination and creativity.</p> <p>Due to itinerary planning, I hurried down the mountain after visiting the castle. As for the remaining gardens and other interesting castles, I will visit them next time I have free time.</p> <h2>Churches and Monasteries</h2> <p>I am not keen on religious culture across Europe. For the frequently appearing religious buildings, I just skim through them, merely watching the spectacle.</p> <p><img src="@assets/2023/p92475885.jpg" alt="" /></p> <p><em>Sculptures on the building</em></p> <p>In Lisbon, besides the Cathedral, there are two very famous monasteries. One is on the edge of the city center, called the Carmo Convent. Because of an early earthquake, it lost its roof, leaving only walls and flying buttresses. The other is in the west of the city along the coast, called the Jerónimos Monastery.</p> <p><img src="@assets/2023/p92475904.jpg" alt="" /></p> <p><em>Ruins of Carmo Convent</em></p> <p><img src="@assets/2023/p92475906.jpg" alt="" /></p> <p><em>Sculptures of Carmo Convent</em></p> <p>Roughness and wildness are the main themes. Even the exquisiteness that can bring a sense of religious sanctity is carved very intuitively on the façade of the building. The main body is like a thick, naive giant blue whale. Entering its body, you can see bone-like architectural structures piercing the ceiling. It seems placed there unadorned simply to comply with the laws of physics. Of course, it is not without ingenuity, but there is more "thought" than "craft." That primitive power in human thought is exposed with nowhere to hide, embodied in the ornamentation of the architecture.</p> <p><img src="@assets/2023/p92475886.jpg" alt="" /></p> <p><em>Interior of Carmo Convent</em></p> <p>The heart of the ocean constantly influences these buildings. The human imagination infected by it is the source of inspiration for realizing these structures, and to resist the powerful force of nature, every brick and stone in the building inevitably becomes heavy and coarse. Over a long period, it is like a silent war—construction on one side, destruction on the other. From the mottled wall bricks and jagged cracks, we see the bits and pieces of this massive tug-of-war.</p> <p><img src="@assets/2023/p92475892.jpg" alt="" /></p> <p><em>Tower of Jerónimos Monastery</em></p> <p><img src="@assets/2023/p92475908.jpg" alt="" /></p> <p><em>Interior of Jerónimos Monastery</em></p> <p>Today, these monasteries have become history, abstract knowledge, and symbols. They are either empty or converted into museums to house historical artifacts. Commercial modifications have led to the loss of many details of how people lived at the time. The accumulation of too many tourists makes these buildings—solidified by money, blood, tears, and faith—lose much of their flavor. Only by tasting carefully can one smack a hint of flavor from them.</p> <h2>Nightfall</h2> <p>Lisbon's sun sets below the sea level, and night falls low. People show no lingering attachment to the sun's exit. Instead, they look forward to it, eagerly awaiting the arrival of the night.</p> <p><img src="@assets/2023/p92475943.jpg" alt="" /></p> <p><em>The city under nightfall</em></p> <p>There are two kinds of lights hanging overhead: one is the common street lamp, standing neatly on both sides of the main roads. The other is the bright yellow bulb hanging on the high walls by the small paths, making eyes dazzled. They have built-in sensors, when people walk underneath, the light suddenly flashes, as if a god is floating behind you, eagerly rushing to help.</p> <p><img src="@assets/2023/p92475944.jpg" alt="" /></p> <p><em>Lamppost and tram</em></p> <p>The colorful darkness continues to thrum. The main roads and small paths offer two different landscapes and must be spoken of separately.</p> <p>On the main roads, the lined-up shops have closed their doors, replaced by bars and restaurants. The style is usually high-end, places frequently visited by wealthy adults. Constrained by the narrowness of the buildings, restaurants are often rectangular, embedded in the architecture. On one side is the entrance and exit, on the other is a kitchen enclosed by transparent glass. Looking in from the outside, it is very deep, like the ocean, activating the exploratory desire of the taste buds.</p> <p><img src="@assets/2023/p92475956.jpg" alt="" /></p> <p><em>Long and narrow restaurant</em></p> <p>The meandering alleys embody the true charm of Lisbon at night.</p> <p>Unlike the suits and leather shoes on the main avenues, the dance in the small paths is young and dynamic. From the moment you enter, you are immediately summoned. The streets are extremely narrow. The residential buildings on the left and right are very towering, ultimately leaving only a slit through which to look up at the sky. Stones the size of a child's palm pave roads that burst forth with fine, light drumbeats, thump-thump-da-da, going up and down with the undulation of the hills. The restless rhythm does not stop. Instead, it resonates with the tiles on the walls on both sides. Lights mix with moonlight, and the tiles constantly change color, jumping and shuttling between the buildings—sometimes growing larger, sometimes smaller, sometimes dense, sometimes sparse. While the day overflows with color, the night becomes weird, unpredictable, and elusive.</p> <p><img src="@assets/2023/p92475967.jpg" alt="" /></p> <p><em>Alleys of Lisbon</em></p> <p><img src="@assets/2023/p92475958.jpg" alt="" /></p> <p><em>Sitting outside drinking</em></p> <p>Noisy rhythms fill the quiet paths of the late night, constantly inciting people to explore deeper. Bars and restaurants are like jelly beans of mixed flavors. The bag wasn't held steady, a hand shook, and they scattered irregularly everywhere. The rigid rules of adults lose their effectiveness here. The fog of cigarettes, the sound of laughter, showgirls hidden behind heavy doors, exotic customs... The long, drawn-out alleys are like a magic pocket, bursting with all kinds of novel and fun elements, teasing the burning curiosity of young people.</p> <p><img src="@assets/2023/p92475965.jpg" alt="" /></p> <p><em>Standing outside drinking</em></p> <p><img src="@assets/2023/p92475961.jpg" alt="" /></p> <p><em>Panda Canteen</em></p> <p>Turning the corner of the street, the mini garden is a whole other world. Scattered paths converge at the central fountain, and the remaining space is filled with greenery. Huge leaves and dense bushes act like a hallucinogen, making it impossible for me to distinguish between winter and spring. The open view combined with the dim lighting makes even the tallest plants seem to regress into moss in the corner, becoming closed-off and introverted. In this moment, I suddenly recall childhood scenes in southern China. Although the white walls were tall, covering the sky, they had their unsightly side. The base of the wall near the ground became black and greasy due to dampness. Moss grew wherever there was a crack, savage enough. After looking at it for a long time, I got used to it, and finally, it settled into memory, surprisingly giving birth to a long-lost sense of peace and comfort.</p> <p><img src="@assets/2023/p92475960.jpg" alt="" /></p> <p><em>Garden in the early hours</em></p> <p>The small kiosk next to the mini garden scatters a white fluorescence like heaven, selling not cigarettes or newspapers, but what I call the source of spirit and inspiration—alcohol and snacks. Although there are not many types of wine, it is completely enough to console different souls. The guy inside wears a smile, leisurely satisfying everyone's needs. That night, he became a savior.</p> <p><img src="@assets/2023/p92475962.jpg" alt="" /></p> <p><em>Soul Replenishment Master</em></p> <p><img src="@assets/2023/p92475970.jpg" alt="" /></p> <p><em>And the Soul Replenishment Station</em></p> <p>Some alone, some in groups of two or three, order a glass of wine and sit around a corner of the garden. Accompanied by the colorful darkness, they talk freely, and life thus becomes incredibly mellow and rich.</p> Where the Land Ends and the Sea Beginshttps://blog.kaiyikang.com/posts/2023/where-the-land-ends-and-the-sea-begins/https://blog.kaiyikang.com/posts/2023/where-the-land-ends-and-the-sea-begins/Mon, 23 Jan 2023 00:00:00 GMT<p><img src="@assets/2023/p92602999.jpg" alt="" /></p> <p>Before I lose my mind and descend entirely into an abyss of emotion, what I want to tell you is this: from Sintra train station near Lisbon, take bus 403, and you will arrive in about an hour.</p> <p>I feel like a drifter. My coat is dirty; the cuffs and hem are frayed and worn. My shirt clings to my skin, clammy and uncomfortable. The bus bumps along the mountain road, carrying fewer than ten people. We sit sparsely, strangers scattered by the windows. Occasionally, a few oblivious locals board the bus, disrupting the awkward equilibrium we had so painstakingly established. I am drowsy, trying to save my phone battery, sleeping against the plastic back of the seat in front of me. I don’t know how much time passes before we arrive. A subtle, microscopic excitement seeps out, spreading to my arms, neck, cheeks, and scalp.</p> <p>This is an exploration of limits that belongs to the mind, not the physical body. Its so-called limit lies in the fact that it is the westernmost point of continental Europe. My limit lies in the fact that I was summoned by it, and my brain perceives it as an extremity. Thus, I bestow upon this act a modest name: A Safe Limit.</p> <p><img src="@assets/2023/p92603017.jpg" alt="" /></p> <p>Accompanying my unfocused vision, I choose to swallow this scene down with the album <em>From the Sea to the Land Beyond</em> by the British band Sea Power. The whimpering noise and classical sounds, along with the faint crashing of waves and cries of seagulls, are a match made in heaven.</p> <p>This is the terminus for wandering thoughts, a place that can satisfy everything we desire. Looking back, there are hills covered in green vegetation and small yellow flowers. Looking forward, there is the endless ocean and sky. To the side, a small red lighthouse serves as the perfect embellishment. Upon arriving here, all emotions are tethered—not because the space is too narrow, but because it is too vast, so vast that no end can be sought, no landing found, and thus, they are bound.</p> <p>I want to recite poetry, but I do not know how. I want to take photos, but the camera cannot contain the distance. I want to think, but the ceaseless blowing of the sea wind causes my brain to stop its endless roaming and singing. The only things that can interrupt me are the tourists passing by from time to time. Some speak languages I understand, mostly discussing mundane and tedious daily lives. No matter how strong the sea wind is, it cannot blow away that putrid and suffocating scent. I walk in small steps along the safety fence, staying away from them, pacing continuously toward the other shore of my own spirit.</p> <p><img src="@assets/2023/p92603036.jpg" alt="" /></p> <p>The rocks have been eroded into sharp shapes by the sea wind, yet the small yellow flowers are incredibly soft and exquisite. They stand in uniform rows, looking in a consistent direction—perhaps inward toward the endless continent, perhaps outward toward the endless ocean, or simply toward that lovely lighthouse, so much so that they feel they present a lovely appearance themselves.</p> <p><img src="@assets/2023/p92603037.jpg" alt="" /></p> <p>The sea wind continues to blow, stirring up sea fog. The faint mist diffuses, connecting the sky, the distant horizon, the nearby waves, the coast, and the cliffs—everything is linked together. It is likely difficult to get good visuals with photography here. As I mentioned before, when the camera lens extends toward the horizon, all it captures is complete defocus. Like the blind, everyone who arrives at this moment finds their eyes following a natural magnetic pull, instantly failing. Just as when facing the vast ocean and sky, we finally become equal and fragile.</p> <p><img src="@assets/2023/p92603043.jpg" alt="" /></p> <p>What is the price of losing one's vision? I think it is imagination. I feel that in this moment, I have lost my imagination, unable to use any conceived elements to piece together a rational world. Every brick, every pane of glass, is sucked into the endless horizon. Colors—what colors remain? There is nothing before my eyes but blue. I feel angry, yet have nowhere to vent it. The anger flows away rapidly, leaving me silently. Then joy burrows out, but it doesn't last long, vanishing just as quickly.</p> <p>I have been stood up by the world. All sensory emotions have fallen through, leaving me an empty shell, a structure. Nature shaped this structure, and all the results generated by this structure have drifted back to nature. Thus, we have become one. Perhaps I shouldn't be so confident as to call me and it "we". The polite way to put it is "them." I have become "them," and I will no longer be "me."</p> <p>Gradually, I realize that I cannot stay lost, or I will melt forever into the endless ocean and fantasy. So, I attempt to regain my reason and begin to turn away from this bewildered, magnificent place.</p> <p>I return to the nearby bus stop. While waiting for the bus, I write this passage in my notes: Looking left and right—on one side is the sentimental coast, on the other is the bus of life. Ultimately, no matter how vast it is there, we eventually need to live.</p> <p><img src="@assets/2023/p92603044.jpg" alt="" /></p> A Simple Example for Optimizing Architecturehttps://blog.kaiyikang.com/posts/2025/a-simple-example-for-optimizing-architecture/https://blog.kaiyikang.com/posts/2025/a-simple-example-for-optimizing-architecture/Thu, 06 Nov 2025 00:00:00 GMT<h2>Current State</h2> <p>We have a class named <code>StatusService</code> which contains a <code>save()</code> method for persisting <code>StatusModel</code> data to a repository.</p> <p>Two modules utilize this persistence logic:</p> <ol> <li>The <code>StatusService</code> itself, which is invoked by a <code>StatusResource</code> (a REST endpoint).</li> <li>A <code>ResetStatus</code> class, which is triggered by a <code>ResetStatusEventListener</code>.</li> </ol> <p>The <code>ResetStatus</code> class directly calls a method in <code>StatusService</code> to persist the status data. The name <code>StatusService</code> suggests it acts as a facade or manager for all status-related operations (querying, saving, deleting, etc.), but this design creates hidden pitfalls for testing.</p> <p>The current logical relationship is illustrated below:</p> <p><img src="@assets/2025/a-simple-example-for-optimising-architecture-an-1.jpg" alt="" /> Here are the relevant code snippets:</p> <pre><code>// StatusService.java public void save(final StatusModel statusModel) { final EntityID id = statusModel.id(); // Business validation logic guardEntityExists(id); final List&lt;ExternalInfo&gt; externalInfos = lookupExternalInfo(id, getContext()); guardInfoIsValid(statusModel, externalInfos); guardSomeBusinessRule(statusModel, externalInfos); // Data mapping logic final StatusModel mappedStatusModel = mapDataToMatchExternalInfo(statusModel, externalInfos); // Persistence and side-effects final boolean stateHasChanged = statusRepository.save(mappedStatusModel); if (stateHasChanged) { log.debug("Invalidating cache for ID={}", id); cacheService.invalidateCacheForIds(List.of(id.toString())); } } </code></pre> <p>It is used by two different modules. The first is:</p> <pre><code>// StatusResource.java @PUT @Path("entities/{id}/status") public Response updateStatus( @PathParam("id") final EntityID id, @Valid final StatusUpdateRequest request) { statusService.save(mapToDomainModel(id, request.details())); return Response.ok().build(); } </code></pre> <p>And the second module is:</p> <pre><code>// ResetStatus.java public void reset(@NonNull final EntityID id) { log.info("Invoking factory reset of status for ID={}", id); final StatusModel currentStatus = statusRepository.findById(id); final StatusModel resetStatus = currentStatus.withStatusReset(); statusService.save(resetStatus); } // ResetStatusEventListener.java public void handleEvent(final SystemResetEvent event) { resetStatus.reset(event.getId()); } </code></pre> <h2>The Problem Caused by This Structure</h2> <p>During testing, an integration test that calls the <code>reset</code> functionality failed. The error indicated that the entity ID could not be found.</p> <p>Upon investigation, I discovered that <code>StatusService.update()</code> (originally named <code>save()</code>) contains a <code>guardEntityExists()</code> check. The test case did not set up a corresponding entity, causing the guard to fail. The <code>StatusService</code> was originally designed to serve the <code>StatusResource</code>, and its internal logic was tailored for that specific flow. In other words, the implementation of <code>StatusService.update()</code> was not suitable for the <code>Reset</code> scenario.</p> <p>This error clearly reveals two distinct <strong>Use Cases</strong>:</p> <ol> <li>One for handling requests from the <code>Resource</code> (an Inbound Adapter).</li> <li>Another for handling the <code>Reset</code> request from the <code>Listener</code> (another Inbound Adapter).</li> </ol> <p>The commonality is that both need to eventually call the repository's <code>save()</code> method. The key difference is that the <code>Reset</code> use case does not require the <code>guardEntityExists()</code> check.</p> <h2>Analysis</h2> <p>In architectural styles like <strong>Hexagonal Architecture (Ports and Adapters)</strong>, we organize our code into layers like Inbound/Outbound Adapters, Use Cases, and the Domain. It's crucial to correctly classify our existing classes and define how they should interact.</p> <p>We can clearly classify the following components:</p> <ul> <li><strong>Inbound Adapter</strong>: <code>ResetStatusEventListener</code></li> <li><strong>Inbound Adapter</strong>: <code>StatusResource</code></li> <li><strong>Outbound Adapter</strong>: <code>StatusRepository</code> (specifically, its implementation)</li> </ul> <p>This leaves us to classify <code>ResetStatus</code> and <code>StatusService</code>.</p> <p>The name <code>ResetStatus</code> strongly implies it is a <strong>Use Case</strong>. Looking at <code>StatusService</code>, we see it contains a mix of logic. It has "anemic" pass-through methods that simply delegate to the repository, such as:</p> <pre><code>// StatusService public StatusModel findById(final EntityID id) { return statusRepository.findById(id); } </code></pre> <p>At the same time, it includes what should be <strong>Domain</strong> logic, like <code>lookupExternalInfo()</code> or the aforementioned <code>guardEntityExists()</code>. To simplify for a moment, let's ignore the domain-like methods and treat the core orchestration logic as another <strong>Use Case</strong>. We now have two:</p> <ul> <li><strong>Use Case</strong>: <code>StatusService</code></li> <li><strong>Use Case</strong>: <code>ResetStatus</code></li> </ul> <p>Use Cases should be isolated from one another, but the current structure violates this principle, which is why it "feels" wrong. With this new classification, the path forward becomes clearer.</p> <h2>Refactoring the Structure</h2> <p>In the new structure, <code>ResetStatus</code> should interact directly with its required Outbound Port (<code>StatusRepository</code>) to save the reset status. It should not be coupled to another use case like <code>StatusService</code>.</p> <p>Additionally, the <code>save()</code> method in <code>StatusService</code> needs to be renamed. The reason is that this method is not "pure". Besides saving data, it also performs guards, mapping, and cache invalidation. It's clearly not just "saving" something but <strong>orchestrating a process</strong>. I would rename it to <code>update()</code> or something similar to reflect its role in orchestrating a complete workflow.</p> <p>Our new structure now looks like this:</p> <p><img src="@assets/2025/a-simple-example-for-optimising-architecture-an-2.jpg" alt="" /></p> <p>Although the overall structure is clearer, the name <code>StatusService</code> is still confusing. I researched definitions and best practices for Use Cases, and a strong convention emerged: <strong>A Use Case class should have only one public method.</strong></p> <p>The reasons are as follows:</p> <ul> <li><strong>Single Responsibility Principle (SRP)</strong>: A class should have only one reason to change. If a class represents a single use case, it will only change when that specific business process changes. If a class contains multiple use cases, a change in one can inadvertently affect others, increasing the risk of bugs.</li> <li><strong>Clear Business Boundaries</strong>: When you open the application package, you see a list of clearly named Use Case classes (<code>ActivateFeatureUseCase</code>, <code>UpdateStatusUseCase</code>, etc.). This acts as a menu of the system's capabilities, making it immediately understandable.</li> <li><strong>Minimized Dependencies</strong>: Different use cases have different dependencies. If they are combined into one class, that class will accumulate all dependencies, even if a specific method doesn't need them. This leads to dependency clutter and unnecessary coupling.</li> <li><strong>Clear Transactional &amp; Security Boundaries</strong>: In frameworks like Spring, annotations like <code>@Transactional</code> are typically applied to public methods. When a class has only one public method, the transactional and security scope is unambiguous: the entire use case succeeds or fails as a single unit. This avoids the complexity of managing state across multiple methods within a single class.</li> </ul> <p>Following this principle, <code>StatusService</code> should be renamed to <code>UpdateStatusUseCase</code>, and its public method could be named <code>execute()</code> or <code>apply()</code>.</p> <p>The renamed structure is:</p> <p><img src="@assets/2025/a-simple-example-for-optimising-architecture-an-3.jpg" alt="" /></p> <p>We've focused on Use Cases, but let's briefly touch on the <strong>Domain</strong>. The Domain layer should contain the actual business rules and logic, not orchestrate workflows or call external services. In our example, methods from <code>UpdateStatusUseCase</code> (the former <code>StatusService</code>) like <code>guardEntityExists()</code>, <code>lookupExternalInfo()</code>, and the <code>StatusModel</code> itself belong in the Domain. Use Cases rely on the Domain for its business logic. For instance, <code>ResetStatus</code> needs knowledge of the <code>StatusModel</code> to correctly reset the status.</p> <p>After this final organization, the ideal structure is:</p> <p><img src="@assets/2025/a-simple-example-for-optimising-architecture-an-4.jpg" alt="" /></p> <h2>Further Thoughts</h2> <p>I've made two observations during this process.</p> <p>First, discussions about architecture often feel like time-consuming black holes. Second, discussing architecture is a bit like discussing politics or philosophy; everyone, regardless of experience, seems to have an opinion. While the views of experienced architects are naturally more profound and important than those of someone less experienced (like myself), they can easily get lost in a sea of opinions.</p> <p>It is a high-entropy topic.</p> <p>Of course, no matter how the architecture changes, its ultimate purpose is to solve real problems. In this case, the problem was getting a specific integration test to pass. By starting with a concrete issue, we were able to reflect on the problematic code, refactor it, and ultimately solve the problem.</p> <p>If I had been asked to design a "perfectly structured" solution from scratch, I would have felt lost. There is no absolute standard for what is "better" in a vacuum. At a given moment, placing a module A in position 1 versus position 2 might make no functional difference and cause no immediate issues; the choice is often a matter of taste or feel. It's hard to call these "real problems." The real problems often only surface after the system is in use, which is also why it's easy to fall into the trap of over-engineering during the initial design phase.</p> <p>The true power of architectural design, and the foresight of an experienced designer, can often only be validated over time.</p> <p>In summary, this simple example allowed me to tangibly understand the high-level definitions of different architectural components and how the design of their boundaries directly impacts the functioning of a real system.</p> Choosing a Clear Namehttps://blog.kaiyikang.com/posts/2025/choosing-a-clear-name/https://blog.kaiyikang.com/posts/2025/choosing-a-clear-name/Fri, 28 Mar 2025 00:00:00 GMT<p>'Whitelist configuration' is a technical term, but the associated context and the resulting consequences are what truly matter, and this information should be documented.</p> <p>Specifically, when the 'whitelist' is modified, the system reads it during initialization. When the system reaches the 'ServiceInfoCalculation' phase, it triggers the 'ServiceStatusChangedDetector' to check for differences between the new and old 'ServiceInfo'. These changes are then sent out and broadcast through various channels. Crucially, only changes related to services listed in the 'whitelist' can actually be sent.</p> <p>In other words, the 'whitelist' is merely one component in the message sending process, indicating that this particular technique is employed. Other techniques, such as using a 'hardcode array', could also serve the purpose. For documentation purposes, the primary focus should be on the use case—the impact and consequences of applying the technology.</p> <p>Describing requirements solely centered around the term 'whitelist' can confuse readers or users, making it an unsuitable approach. Ultimately, describing the overall process should be the main focus.</p> Dahab - Arriving at the Mountains at Dawnhttps://blog.kaiyikang.com/posts/2025/dahab-1-arriving-at-the-mountains-at-dawn/https://blog.kaiyikang.com/posts/2025/dahab-1-arriving-at-the-mountains-at-dawn/Tue, 08 Apr 2025 00:00:00 GMT<p>Landing at 4 AM after an overnight flight, we were completely exhausted. The airport was small with visibly aging facilities. At the welcome entrance, Egyptian totems were symbolically arranged alongside scattered fake palm trees against a low-resolution printed backdrop—giving the whole setup a distinctly plastic, artificial feel. We glanced at it with curious interest before hurriedly following the crowd through immigration, taking our luggage, exchanging some currency, and exiting the airport within minutes.</p> <p>Initially, we had been quite concerned about arriving in a completely unfamiliar place in the early morning hours with our energy depleted. Wouldn't this create problems if we hadn't made proper arrangements? To prevent any issues, I had specifically asked our contact detailed questions like "What are the procedures? What information will we need?" The response was surprisingly casual—nothing was needed, everything was arranged, just show up. Though still apprehensive, I reluctantly accepted this answer.</p> <p>In fact, we overthink everything. Outside the airport gates, despite the early hour, the square was brightly lit with people lining both sides. Scanning quickly from left to right, I immediately spotted our driver. He was holding a thin piece of paper with "Dahab Kang" written on it. Though the writing was delicate and fragile, it was perfectly legible.</p> <p>This exemplifies their approach to handling matters—primitive yet direct. I often realize how deeply modern urban life has influenced my behavior. For any task, I feel compelled to consider and follow external procedures and restrictions, without truly understanding their purpose. This vagueness creates irrational anxiety about the consequences of procedural errors. You probably understand that in city life, missing a specific document means running back and forth, wasting considerable time and energy. By comparison, their direct problem-solving approach, which permeates nearly every aspect of local life, stands out starkly. I suddenly felt embarrassed, realizing how many unnecessary concerns had been occupying my time.</p> <p>We greeted the driver cordially. Without much conversation, he led us through the crowd toward the parking lot. The path was uneven and potholed, with exposed gravel along the curbs. The streetlights flickered inconsistently, fragmenting the illumination.</p> <p>The driver stopped at a small car, indicating it was our ride. Inside, the rustic decor reminded me of 1990s Beijing taxis. A red soft cloth decorated with yellow speckles covered the dashboard beneath the windshield. Many parts of the car were covered with transparent plastic sheets, which I couldn't tell whether they were original wrappings never removed or deliberately added to protect against dust. Sitting on the left side of the back seat, I was struck by the door handle wrapped in white tape, which became my identifying marker when we rode the same car later. The rear windshield was covered with black plastic mesh, likely serving the same purpose as the red cloth up front—reducing interior temperatures during desert sun exposure.</p> <p>The car started with a sputtering engine that clearly wasn't in prime condition. As we slowly drove away, looking through the windows revealed almost no well-maintained vehicles in the parking lot. By "well-maintained," I don't mean luxury brands, but simply cars that appeared intact externally. Like the roads and facilities, most vehicles here showed significant wear. I couldn't tell what storms these cars had weathered—not a single patch of paint remained intact, metal frames were warped, bumpers had pieces missing here and there, and having all headlights working was considered quite an achievement.</p> <p>Most cars here had been stripped back to their original form—simply tools with four wheels for carrying people. The word "tool" describes them perfectly; these vehicles no longer carried status symbols, and luxury features seemed meaningless in the desert environment. Instead, those four wheels and a shaded interior were precisely what locals needed most.</p> <p><img src="@assets/2025/IMG_3219.JPG" alt="" /><em>A relatively intact taxi</em></p> <p>Consequently, sturdy and durable Nissan vehicles dominated the streets. Small ISUZU pickup trucks served as versatile transportation between desert and towns, perfect for carrying both people and cargo, visible everywhere locally. Slightly larger minivans, often retrofitted with longitudinal benches in the back to accommodate eight passengers, were ideal for small road trips. We occasionally rode in better-maintained vehicles with functioning air conditioning and comfortable seats, but after becoming accustomed to these "broken cars," the nicer ones felt almost uncomfortable. Walking around, whenever we spotted high-end brands, we would joke that such delicate vehicles were completely unsuited for this harsh environment.</p> <p>These cars always remind me of the saying "rough words but sound reasoning." The "words" represent surface appearances, while the "reasoning" embodies the underlying concept and philosophy. Local residents continuously interpret and implement this principle—though their vehicles appear rough externally, the essential functions remain completely adequate. Any superfluous elements eventually get polished away by the sea and desert until they disappear completely.</p> <p>Around the airport, scattered streetlights provided some illumination, but heading north, darkness enveloped everything. The road was nearly empty of other vehicles, and the lane markings on the asphalt had lost all practical purpose. The road layout featured bold, sweeping designs with long straight sections and gentle curves. Sparse traffic signs reflected lonely light under our headlights. The Arabic text lacked English translations, leaving their messages incomprehensible to us.</p> <p><img src="@assets/2025/IMG_3223.JPG" alt="" /> <em>It's completely dark outside</em></p> <p>Our headlights, complemented by moonlight, outlined the silhouettes of mountains on both sides. Their forms appeared pitch black, and only by looking upward along with the sky could we barely discern the mountaintops. As we sped past, these shapes silently dissolved into the night, vanishing completely.</p> <p>Physically and mentally drained, we drifted in and out of sleep with the vehicle's movements. Our shy driver spoke little, his voice soft—perhaps intentionally avoiding disturbing our rest—and played no music or radio. The night grew increasingly serene, and we three seemed to exist in a special dimension, rapidly floating forward.</p> <p><img src="@assets/2025/IMG_3628.JPG" alt="" /> <em>Dawn broke, driving in the desert</em></p> <p>After an indeterminate period of drowsiness, the sky gradually brightened. Opening my eyes, I realized we were surrounded by mountains. Desert stretched everywhere without vegetation, and distant mountains stood starkly bare and abrupt. While this represents typical desert scenery, for those accustomed to skyscrapers and verdant mountains, it felt remarkably fresh. From the back seat, we exchanged smiles, our eyes reflecting the faint dawn light, silently communicating our curiosity and excitement at these unusual mountain vistas.</p> <p><img src="@assets/2025/IMG_3224.JPG" alt="" /> <em>Desert outside the car window</em></p> <p>As we exited the mountainous region, we approached the checkpoint at the town entrance. Though not large, these checkpoints occupied all key entry and exit points. We had passed one when leaving the airport, though darkness had obscured it. In daylight, everything appeared clearer. Iron barriers blocked the road at the checkpoint, with small huts housing police and soldiers. Armored vehicles and armed personnel created an intimidating atmosphere, immediately changing the mood.</p> <p>Such checkpoints dot various areas across the peninsula. Traveling between towns by car inevitably means encountering them. We needed to keep our passports readily accessible for verification. The inspection standards varied dramatically—sometimes the driver could pass with just a few words (whose meaning and special significance remained mysterious to me), while other times they would stop the car for several minutes before allowing passage. It's difficult to imagine how non-Arabic speakers could navigate between towns independently. Self-driving seemed impossible; local assistance was essential. The towns existed as distinct, separated entities, and leaving one always created a sense of disorientation. The contrast between town vibrancy and external stillness was striking, fragmenting our continuous journey into discrete segments.</p> <p>After clearing the checkpoint, our long-silent driver softly announced, "Welcome to Dahab." Through the car windows, we noticed promotional slogans on nearby walls marking the town boundary. Constrained by the armed military presence, we didn't dare photograph this memorable moment, instead turning our heads to mentally capture the significant landmark for a few extra seconds.</p> <p>As we continued driving, the vast sea gradually came into view in the distance along the descending avenue. My heart rate quickened slightly as familiar scenery began transforming—the sea slowly inserting itself between sky and stone. This marked the true beginning of our journey; after enduring a long night of wakefulness and travel, we had finally reached the small town of Dahab.</p> Dahab - Early Morning in the Townhttps://blog.kaiyikang.com/posts/2025/dahab-2-early-morning-in-the-town/https://blog.kaiyikang.com/posts/2025/dahab-2-early-morning-in-the-town/Wed, 09 Apr 2025 00:00:00 GMT<p>We took a taxi into the small town, leaving the asphalt road behind and turning onto a dusty path.</p> <p>The path was covered in dust, flanked by low, densely packed buildings. Calling them buildings might be generous—they were more like square enclosures made from broken walls and door panels. The entrances were cluttered with stones and plastic, which from any distance looked like piles of trash or a construction site about to begin. The road itself was very basic—just sandy soil scattered with various debris, most commonly dog and sheep droppings, along with some plastic waste. The droppings had been baked hard by the sun and heat, turning almost stone-like, odorless, and flies didn’t bother to come.</p> <p><img src="@assets/2025/L1090619.jpg" alt="" /> <em>Dirt road</em></p> <p>I tried to reconcile these ever-present dirt roads and buildings with my own experiences, and my conclusion was that this place resembled a Chinese "XiaoQu" (residential compound) or a German 30km/h residential zone—but in an Egyptian style. By blending these impressions, I gradually came to accept this new form of neighborhood. The housing here is extremely basic—utterly simple and devoid of design—but highly practical and perfectly suited to the local climate and culture. People who have lived here for generations are masters of "form follows function," but here, it’s all function with no form.</p> <p>Walking these dirt roads always feels like a small adventure. During the day, flowers and kittens suddenly appear on the walls, and the air carries the faint scent of sheep droppings. At night, under dim light and darkness, I carefully dodge the droppings and trash scattered on the sandy ground. Once you get used to it, it’s not so bad—it has its own unique charm.</p> <p>Of course, not all buildings here are like that. The hostel we stayed at was quite different. It was a small villa, square and neat, with small windows. The exterior walls and courtyard were clean and tidy, with flowers and greenery growing taller than our heads, spreading widely and visible from afar. There are quite a few similar small buildings in town, but most have been converted into Airbnbs or hostels. These are mostly clustered in the town center. Though the facilities aren’t the newest, they’re fully functional and show many modern touches.</p> <p>Larger resorts are located on the outskirts of town. We didn’t like those places much. Every time we passed by and peeked inside, they felt deserted. Railings and facilities were badly corroded by the sea breeze. Despite having many rooms, few people seemed to live there—it was cold and empty. In short, if you ever come here, I highly recommend staying in a hostel or Airbnb rather than a resort hotel.</p> <p><img src="@assets/2025/L1090630.jpg" alt="" /> <em>Some courtyards have more refined doorways</em></p> <p>After getting out of the car, the reserved but attentive driver helped us carry our luggage. Since we arrived too early, we had to knock repeatedly and call to wake people up. After a few minutes, the person in charge, Mr. Z, opened the door, looking sleepy but greeting us warmly. After thanking the driver, we left our luggage in the hallway. Mr. Z briefly introduced the hostel’s facilities and rules.</p> <p>Mr. Z is a tall, dark-skinned Egyptian youth, usually dressed in sportswear, responsible for all hostel affairs. Every morning when passing through the living room, we’d see him lying on the sofa, either napping or listening to music. But reliable sources say this isn’t laziness -- he works out early every day and only rests on the sofa after finishing his routine. What we saw was him relaxing after a workout.</p> <p>What we appreciated most was his encyclopedic knowledge. From hostel management to organizing activities, even small questions like where to print documents, he knew the answers and responded quickly. In this place with limited internet access, map apps barely work and are often outdated. For the latest firsthand info, you have to ask him. We felt lucky to choose this hostel —- a solid, friendly, and reliable local who made our trip smoother.</p> <p>In the hostel’s living room, right by the main door, there’s a water dispenser. I especially liked the free ice water and drank several cups every time I returned. The living room had several sofas and a TV. Further inside was the kitchen and a long table. The red tea in the kitchen was free, but other instant drinks required self-payment.</p> <p><img src="@assets/2025/L1090611.jpg" alt="" /></p> <p><em>Morning at the hostel</em></p> <p>Egyptian red tea is a powdered tea called "El Arosa Tea," sold cheaply in large bags. To make it, you scoop about half a spoon of powder, no filter needed, pour hot water over it, and wait for the water to turn a bright sunset red before drinking. Compared to red tea, coffee—which is common in Europe—doesn’t seem to be popular here. At least, the coffee I’ve had these days was mediocre and lacked aroma, while the red tea was very satisfying.</p> <p>The hostel’s backyard is very pleasant, with comfortable sofas, mats, and long tables and benches. Nearby greenery adds a natural touch. The surrounding walls block sunlight, so even on hot days, the shade feels cool.</p> <p><img src="@assets/2025/L1090618.jpg" alt="" /></p> <p><em>Courtyard wall</em></p> <p>People staying here often lounge in the courtyard, eating, drinking tea, and chatting. I do the same. When I see someone, I try to strike up a conversation, though I’m not very good at it—especially when they’re focused on screens or engaged in lively discussions. I’ve never figured out how to start or end casual chats smoothly. They feel like dreams—no clear beginning or end when recalled.</p> <p>I’m like a fish, drifting without strong opinions, chiming in when there’s a topic, and slipping away when I can’t think of new ideas or express complex thoughts. Socializing is more challenging than enjoyable—thinking of topics, joining discussions, expressing opinions while trying not to offend. Doing this in a non-native language makes it even more exhausting.</p> <p>Paradoxically, socializing is a skill—the more you do it, the better you get; if you don’t, you get worse. So the environment pushing me to change is crucial. I feel that living in Germany sometimes makes it hard to sustain this positive evolution. When I speak broken German, I often get silence or indifferent responses. I know there’s no malice, but without feedback, I never know if I’m understood or just talking to myself. In contrast, people from sunny places give me more positive, encouraging responses. Even if our languages don’t fully connect, like the sun’s energy spreading to all, I can sense their emotions and empathy.</p> <p>Conversation is like water flow. You can stir it when calm or join an existing current, upstream or downstream. The key is to observe the flow and find the right moment to join or leave. I’m glad this trip gave me chances to interact with different people, boosting my confidence and letting me truly feel the flow of human connection.</p> <p>Since we arrived early and check-in was still hours away, to avoid disturbing others, we moved from the living room to the backyard, found a soft mat, and lay down. Then a light sound caught our attention—a skinny calico cat jumped down from the high backyard wall and nimbly came over. As a cat lover, I couldn’t resist petting it all over, enjoying the moment.</p> <p>Later, Mr. Z told us its name was Orca. Like other Egyptian kittens, its favorite pastime is scavenging. After a few days’ observation, we noticed it often waits by the glass door in the backyard. When someone comes in or out with plates or cups, it immediately approaches, rubbing against their legs. Once they sit, it rubs its head against chairs and table corners, meowing coquettishly. I thought it was showing affection to me, but later realized it’s just a snob, only interested in people with food. Its greed shows when it gets food—its meows become louder and more aggressive, and it scratches with sharp claws, truly living up to its name "Orca." Knowing this, we became more cautious, giving it space and resisting its cute temptations.</p> <p><img src="@assets/2025/L1090633.jpg" alt="" /> <em>Orca walking</em></p> <p>Dahab has many cats and dogs roaming the streets. Dogs are large and noisy, often barking and fighting. I’ve witnessed many dog fights. Cats, in contrast, are slimmer and more agile, rarely fighting or gathering unless food is involved. They’re also very clean. Once I saw a kitten poop on the beach, cover it with sand, elegantly rub its butt, and walk away. Its grace makes me wary; I’m always cautious walking barefoot on the beach, afraid of stepping on fresh "landmines."</p> <p>Compared to the lazy Turkish cats, these cats are wilder and will do anything for food. Some bold cats even jump onto tables and plates. At those times, we have to be firm and shoo them away; if we’re soft, they’ll take over. Of course, some cats are gentler, quietly watching us eat and leaving when no food is left. We reward these well-behaved cats with chicken or beef to commend their good manners.</p> <p>By the way, local cats have their own dialect. Unlike ordinary meows, "Psst" means "come here." Locals taught me this. If you want Egyptian cats to approach, just say "Psst" a few times.</p> <p>Orca, lazy and free-spirited, after being petted, climbed along the wall and left. We lay on the soft mat, hats over our eyes, half asleep. The flies here have evolved into experts over generations. They occasionally land on skin, and normal, rhythmic swatting doesn’t work. You have to wave vigorously and unpredictably to make them fly off. Annoyed and unable to sleep, we decided to take a walk and enjoy the town’s early morning atmosphere.</p> <p>The distance from our place to the sea is short, and the coastal road is straight, but buildings are tightly packed, making it hard to find a path through. After many twists and turns, we finally reached the shore. Early morning, the sun had just risen, the wind was light, the sea calm, and the temperature comfortable.</p> <p><img src="@assets/2025/L1090667.jpg" alt="" /> <em>A few corners of the coffee shop street</em></p> <p>The street was quiet, with only a few joggers and some people in wetsuits chatting in small groups. Coffee shops and dive shops lined the street, most still closed, with only a few cleaning behind glass windows. The coffee shops were mostly seaside, facing the beach, with carpets and mats laid out and small tea tables set up as seating. Near the shallow water, there were regular tables, chairs, and beach loungers for swimming or sunbathing.</p> <p>Being new, we didn’t know how to pay card or cash and it was too early to find anyone to ask. We hesitated along the coast, then, exhausted, saw two people sleeping on a beach carpet and decided to join them, lying down by the sea.</p> <p>We greeted the owner, ordered two iced coffees, and lay down under the umbrella. After a while, the owner brought two plastic cups. We took them and weakly thanked him. The coffee tasted awful, but sleep was more important. Holding our bags, we fell asleep to the sound of waves.</p> <p>The beach mats were dusty and worn, none intact. The fabric at holes was frayed, with gray cotton fibers exposed and floating. The carpets hadn’t been cleaned for a long time, stained with various colors, obscuring the original red patterns. I guessed that fresh water is scarce here, so washing carpets is costly and inefficient. Or maybe the strong sunlight sterilizes them naturally, so people don’t worry about bacteria. At first, I thought only a few shops were like this, but after visiting more, I found most restaurants and coffee shops were the same. Still, no one sees it as a problem. People come in, buy food and drinks, and sit or lie down as usual.</p> <p>I’m used to city life, where people expect everything to be clean. If something is stained, it must be cleaned immediately before use. Advertisements all promote cleanliness and whitening. "No stains" is a city norm, common sense for using things, and no one questions why things must be clean or why only clean things can be used. Compared to Egyptian carpets, whether dirty or torn isn’t a concern. The key is whether it exists. If it does, you can sit or lie down and rest. If not, you get up and find another place.</p> <p><img src="@assets/2025/L1090640.jpg" alt="" /> <em>Sheep on the road</em></p> <p>Back in the city, I rode past a high-end furniture store. The huge glass window displayed various luxurious sofas and chairs, made from different materials and shapes. Seeing so many options, I suddenly thought of the Egyptian carpets and mats—dirty, worn, and only offering sitting or lying positions. Comparing the two, I felt a strange shame. The carpets blend naturally with their environment, while the furniture caters to the artificial ecology humans build in cities, with concrete and man-made activities. Different environments and occasions require different sofas or chairs. If they don’t fit, people frown and leave.</p> <p>I don’t know how long I closed my eyes before waking again. The surroundings were still calm. A few wild big dogs gathered nearby. I dared not move, letting them sniff around, creating <em>a curious spectacle of five dogs circling "Kang" (me)</em>. After a few minutes, finding no food, they ran off in a pack.</p> <p>Two men sleeping ahead woke up. One chatted with the owner in Arabic, apparently a local. Soon, he skillfully brought a hookah and placed it beside their leftover beer. Just waking up, mixing smoke and alcohol—I couldn’t help but admire him inwardly.</p> <p>He looked around, saw no one else, walked over to us, and started a conversation. We introduced ourselves briefly and chatted about various things, though I don’t recall details—probably first impressions of the town.</p> <p>I remember when discussing currency exchange, he said he worked in anti-money laundering, just off night shift, and came straight to the beach to relax. Using imperfect English, he warned us not to exchange money on the black market because the rates are terrible. The safest way is to go to the bank’s teller window.</p> <p>Seeing the time, we thanked him and prepared to leave. Before parting, he warmly shook our hands and added, "There will be techno music nearby tonight, you’re welcome to come."</p> <p>Since childhood, I was taught to be wary of strangers, especially those who approach warmly, always warned "they have ulterior motives." So when smiling strangers approach on the street, regardless of age or gender, I instinctively keep my guard up. Life in Germany has reinforced this: strangers are either salespeople or beggars. Over time, I almost forgot that genuine warmth is a simple, kind trait that can bring people closer.</p> <p>I don’t blame those who fake friendliness for gain. I just feel a little sad—that pure humanity has been twisted into a sharp tool, and its true value and gentleness are rarely appreciated.</p> <p>During our stay, similar chats happened almost daily. Whether with friends or strangers, I truly felt the kindness and warmth from people’s hearts. From an individual perspective, we may all be ordinary, but these unexpected conversations make every moment shine uniquely, making everyone here feel special, glowing with extraordinary light.</p> <p><img src="@assets/2025/L1090665.jpg" alt="" /> <em>Unknown local youths, but I took their photo</em></p> <p>Before the sun rose too high, we returned to the hostel. Though it wasn’t check-in time, Z told us the room was ready, so we could move in early. I couldn’t help but give him a thumbs-up and sincerely thanked him; the timing was just right.</p> <p>Inside, we tossed our luggage aside, ignoring dust and sweat, and collapsed onto the freshly made bed. Opening and closing my eyes, I realized it was only noon, but my body and mind felt like they’d crossed centuries. That wave of exhaustion reminded us—this journey had only just begun.</p> <hr /> <p>Here's the contact info for <a href="https://www.instagram.com/tranquilohosteldahab?utm_source=ig_web_button_share_sheet&amp;igsh=ZDNlZDc0MzIxNw==">hostel tranquilo</a>, I think you'll like it there :D</p> Dahab - Not Yet Freedivinghttps://blog.kaiyikang.com/posts/2025/dahab-4-not-yet-freediving/https://blog.kaiyikang.com/posts/2025/dahab-4-not-yet-freediving/Mon, 14 Apr 2025 00:00:00 GMT<p>Freediving is not a leisure activity; it is a sport that requires a great deal of skill. These skills include both the obvious, "visible skills" and the less obvious, "invisible skills." The latter are revealed and amplified by the ocean's unique environment, making them even a decisive factor.</p> <p>If you don't meet certain technical requirements, you simply can't get started. Right now, I find myself pacing back and forth at this threshold, hesitant and unsure if I will ever be able to step through that door.</p> <p>Next, I will share my experience in detail.</p> <p><img src="@assets/2025/IMG_3439.JPG" alt="" /></p> <p><em>This is <a href="https://www.linyue-zou.com">Her</a></em></p> <p>Dahab isn't exactly a tourist hotspot. People who come here either have a clear purpose or have been persuaded to come—there's almost no middle ground. The rule here is that almost every activity has a certain threshold. In other words, if you want to take part in something interesting, you either need exceptional talent, special courage and energy, or well-honed skills. There are almost no activities that can be experienced effortlessly, like visiting a scenic spot or museum, which only requires walking.</p> <p>After spending a few days here, I deeply felt that Dahab is not flattering at all. It generously showcases the Blue Hole, cliffs, and lagoons. If you want to play, you can come; whether you have the ability or not is your own business, not its. It won't lower the bar or make things easier just so you can have fun. This proactive attitude is unique among tourist destinations—you either throw yourself into the cliffs and ocean or lie in the shade complaining. So my advice is to use your imagination fully before coming and be clear about which sports you want to try, to avoid boredom.</p> <p><img src="@assets/2025/DJI_20250418125714.jpg" alt="" /></p> <p><em>Fish</em></p> <p>I signed up for a freediving experience course, which is level one in the AIDA system. Simply put, diving falls into two main categories: one is what we usually call "diving," which involves going underwater with oxygen tanks; the other is going underwater without any equipment, relying solely on your body—that's the meaning of "free" in freediving. The former is better known through the PADI system, while the latter includes the AIDA or Molchanovs systems. These names aren't that important—you can find plenty of information with a quick search. Although I joined the system and the course has levels, I know very well that this doesn't represent my personal ability; it's just proof that I've been there. I have no special expectations and do not wish to overly promote the so-called certificate.</p> <p>Because it was windy and the water surface was rough that day, we split the course into two days: half a day of theory and half a day of practice. The coach lives on the town's north road, about a ten-minute walk from where I was staying. Without a house number, I just roughly followed the Weixin location. When I arrived, I looked around but couldn't tell what was ruins and what was a residence; all I saw was a cluster of yellow-flowered trees casting shadows and smelled a strong scent of sheep dung.</p> <p>The coach saw I had arrived and came down to open the door for me. I went up the stairs and entered the house with him. The interior was simple, like old houses in China, very functional. There was a carpet on the floor, and next to it were various diving gear and teaching aids. Although I couldn't name them, they looked very professional. Since it was a beginner course, the content was straightforward and mostly introductory. The focus was on safety first, then technique. After the course, I chatted with the coach for a while before leaving.</p> <p>The course mentioned that freediving is a "mental game." This phrase stuck with me. Generally, a game is a broad concept that applies to most activities. It tests various abilities like endurance, skill, flexibility, memory, and so on. I tend to associate different abilities with certain activities—for example, memory is like a bespectacled scholar, flexibility like a tall, lean athlete, the latter being a manifestation of the former. As for what a person with strong mental ability looks like, or how mental strength manifests, I can't say for sure. That's why I'm curious to "play this game" myself and experience the strength of the mind firsthand.</p> <p>Another thing that impressed me was the "buddy system", meaning freediving is always done with at least two people to ensure safety. So freediving demands not only individual mental strength but also connection between people. These two combined are like the elements of creation—one inward, one outward—and only when inside and outside unite is it complete. Two people, life, and the ocean: many elements naturally and harmoniously merge in freediving.</p> <p>On the way back, I kept reflecting on these fascinating ideas. Of course, theory is theory, and in the end, it all comes down to practice.</p> <p><img src="@assets/2025/DJI_20250418144003.jpg" alt="" /></p> <p><em>Fish and coral</em></p> <p>A few days later, the wind died down. We chose a sunny morning for my first dive.</p> <p>There's a lighthouse on the south side of town, the busiest spot around. Thanks to its unique geography, it's the best place to dive. The shallow waters nearby are perfect for snorkeling, while farther out you can do scuba or freediving.</p> <p>We found a café, and the coach brought out a buoy from the backyard. I learned that when he teaches others, he either partners with a dive shop or works independently. He just needs to notify the café in advance, store his gear there temporarily, and order some food and drinks during classes—a mutually beneficial arrangement.</p> <p>I first put on a wetsuit. It felt like synthetic fiber, thick and soft to the touch, as if insulated inside. Wearing a wetsuit means covering your entire body, head to toe. Even with long sun exposure, the seawater remains cold, and if you're not properly covered, you risk hypothermia. Also, because seawater is buoyant, you need to wear extra weights to sink. Finally, I put on the mask and snorkel, grabbed the long fins, and was ready to go. Freediving gear is quite a bit, but much lighter than scuba gear, which sometimes requires a cart to carry.</p> <p>Once in the water, the shallow beach was very shallow—I remember the water only reached my thighs. The coach guided me through some simple moves like spitting out water, lying down to relax, and how to use the fins. The most interesting part was breathing relaxation: lying on the surface, scanning my body, relaxing, and then holding my breath at the most relaxed moment. My body was buoyed by the water, floating motionless, with only my brain seemingly active. Thoughts surged—sometimes colorful and flying, sometimes silent and empty, leaving only pure sensation. My body was suspended between sky and sea, as thin as a cicada's wing, melting at the boundary and drifting with the waves.</p> <p>People use various methods to calm their racing nerves; the textbook suggests imagining a burning candle. Some imagine animals, others plants. I lacked experience and had no method, even resorting to reciting the "Heart Sutra" to calm myself, but it didn't work well because I forgot the words, which annoyed me and made me more unstable. In the end, I held my breath for one and a half minutes, an average result. Compared to professionals who can hold their breath for several minutes, the gap was clear.</p> <p>After the basic training, we moved to a slightly deeper spot, about seven or eight meters deep. The water was clear, and I could see the seabed. After the coach secured the buoy, I tried to dive.</p> <p>I inverted myself, held the rope, and gently pulled down. The pressure in my ears changed suddenly, followed by pain. The pain started broad and then sharpened. Every time I dived, I tried to equalize the pressure, but no matter how well I trained on land, it failed immediately underwater. Even when calm and carefully focusing on my throat, vocal cords, pharynx, and facial muscles, the sensation became confused as waves passed. The seawater pressure came from all directions, tight and unyielding, seeping into every possible gap. The deeper I went, the tighter the pressure and the greater the pain. The pressure was inevitable, like causality; facing it, there was no choice but to use the correct technique. That feeling of helplessness was magnified underwater; either you do it poorly or you can't do it at all, with no solution. I had no complaints but quickly surfaced after only one or two meters.</p> <p>I tried several times to invert and dive but failed each time. A yellow tennis ball hanging at five meters glowed like a mirage, beckoning me, but sadly I couldn't meet its call. Water pressure and technique were the barriers.</p> <p>After spending over an hour in the sea, my body started to get cold, so I told the coach we could stop. Back on shore, the sun was strong, but my body still trembled involuntarily. Reflecting on the dive, most of my feelings were excitement about the new experience, but part of me was frustrated, like I had missed the final step. Though imperfect, I could accept it and wasn't ready to give up diving. The good news is that by the time I'm writing this, I've mastered the ear equalizing technique but still need to practice and adjust underwater.</p> <p><img src="@assets/2025/DJI_20250418144205.jpg" alt="" /></p> <p><em>More fish and coral</em></p> <p>Later, I talked about this experience with my girlfriend. She said the coach commented on our training spot's distance from shore, calling the location and depth very "magical" and "subtle."</p> <p>I completely agree and empathize. People who don't dive stay close to shore; those eager to start go farther out. I'm somewhere in between—not quite a beginner, but not a non-diver either. This perfectly describes my conflicted hesitation on the path to learning diving.</p> <p>Freediving itself seems inherently contradictory. Successful diving requires performing specific actions while maintaining total relaxation. Most of the time, these two contradict each other: actions tense the muscles, while relaxation resists tension and halts movement. This contradiction is fully triggered in this special environment. I believe every skilled freediver is a master of balance. They know how to find equilibrium between tension and relaxation, moving fluidly like a fish in the space between motion and stillness, pursuing the greatest depth and the deepest self.</p> Dahab - Abu Galumhttps://blog.kaiyikang.com/posts/2025/dahab-5-abu-galum/https://blog.kaiyikang.com/posts/2025/dahab-5-abu-galum/Tue, 15 Apr 2025 00:00:00 GMT<p>North of Dahab lies the Blue Hole, and further north is Abu Galum. There are a few ways to get there: you can take a boat directly from Dahab, or you can go to the Blue Hole first and then continue by boat or on foot.</p> <p>This trip was planned entirely by my girlfriend's friend; we just had to follow her. Hearing that the place was quite remote and supplies were scarce, we packed our bags the night before with just the essentials. The next morning, we took a taxi together to the port, where quite a few boats were already docked. It was less a port and more a small beach, situated next to some ruin-like buildings. The boats were small and haphazardly scattered along the shore, with no sense of order. The distant sea and the rising sun cast a filter over the harbor, making it just about presentable.</p> <p><img src="@assets/2025/L1090997.JPG" alt="" /></p> <p>A number of people stood on the shore, gathered in a cluster, their luggage and supplies spread out at their feet. I stole a glance and saw that besides the basic water and gas canisters, there were also plenty of fresh fruits and vegetables. Seeing this, I could guess just how scarce resources must be at our destination. My girlfriend's extremely extroverted friend went up to greet them and introduced us. It turned out that one of them, an Egyptian man, was the owner of the lodge we were heading to. The others were either his friends or fellow guests.</p> <p>The owner wasn't tall, and while not burly, he was solidly built. He was dressed comfortably and casually, looking less like a boss and more like someone randomly pulled off the street. But I heard he was a major landlord with multiple properties in the area and a thriving business. While listening to their idle chatter, I looked around. Several locals, mostly children, were scattered on the beach, chattering in Arabic. They wore faded white robes, and I couldn't guess what they were doing there at that time.</p> <p>After waiting for about half an hour, we saw them start to load things onto the boats and knew it was time to depart. With no dock, the boats were beached on the sand, so getting on was like climbing a wall: you had to heave your things aboard first, then use your hands to hoist yourself up, pushing off with your rear to get over the side. Those with a smaller build even needed a helping hand.</p> <p>Besides us passengers, there were two locals in white robes on the boat: an older man and a boy. I figured the older man would be steering, but to my surprise, when the boat started, it was the boy at the helm. He looked to be no more than a teenager, probably of elementary or middle school age. He barely spoke a word the entire time; it was the older man who would occasionally call out instructions.</p> <p>The boat finally set off. The Red Sea was not calm; the after-effects of a huge storm from a few days prior still lingered. This translated into violent and unpredictable lurches on the boat. As our small vessel cut through the Red Sea, I briefly felt like a carefree pirate. But then a huge wave crashed over us, making my butt ache from the impact and splashing salty water all over my face and glasses, snapping me back to reality. I was no pirate, just a helpless, ordinary person trembling on a strange sea. The only other time I'd felt a similar jarring motion was at an amusement park, but this was the most basic version—no safety measures, the only things keeping you alive were this one boat, and a child.</p> <p>We headed north, passing the Blue Hole and encountering boats on their return journey. Whenever two boats crossed paths, many people would stand and raise a hand in a gesture of salute, which had an air of solemnity and mystery. After about 30 to 40 minutes on the vast Red Sea, we finally reached Abu Galum.</p> <p><img src="@assets/2025/L1100045.JPG" alt="" /></p> <p>Stepping off the boat, our feet landed on a pebble beach. Carefully stacked stones nearby marked the docking spot. This was a vast coastal plain. North of Dahab, the mountains meet the sea, and any paths are only wide enough for hiking. Thus, Abu Galum, as the first plain we encountered, felt exceptionally precious. Looking around, all we could see were huts of various sizes—the only shelters available for human existence. The huts were backed by towering mountains and rocks, and though they faced the sea, the scene was far from idyllic.</p> <p>This land was filled with a raw, immense energy that would crush any delicate, refined imagery or fantasy.</p> <p>We walked into a large hut that served as the "reception," and an incredible feeling flooded my brain: that a scene like this could actually exist. It was like stepping into a surreal play, but this play was real, and my eyes struggled to adjust. The roof was woven from large leaves and branches, supported by a thick wooden pillar worn smooth to the touch. Carpets were laid out layer upon layer on the ground, with a patch of earth left bare in the middle where a huge aloe vera plant grew. The space was filled with many small decorative objects—exquisite, creative, and natural, true handmade crafts. The main hall had hammocks, musical instruments, books, and a chessboard, but not a single piece of high-tech equipment in sight.</p> <p><img src="@assets/2025/L1100006.JPG" alt="" /></p> <p>Behind the reception was the kitchen, filled with jars and bottles containing unknown spices and herbs. If you wanted tea, you could go to the kitchen anytime to boil water and brew it yourself. Most of the objects, though old, were still functional. I vividly remember a dented iron kettle with a crooked handle and a lid that wouldn't close properly. Yet, people used this very kettle day after day to boil water for tea.</p> <p>Our huts were scattered nearby. Their roofs and walls were also woven from grass and wood, offering just enough shelter from the sun and wind. Inside, a mattress lay on the floor, and that was it. The nearby toilet was small, and showering was a luxury, as fresh water was extremely limited. All the water was stored in huge white barrels, which were replaced periodically by someone who drove in. Being in the desert, the simple conditions were completely understandable; or rather, they were a rare treasure, for such an atmosphere is not something you can find just anywhere.</p> <p>Besides the know-it-all owner, a few other people left a deep impression on me. One was a French woman who had been living there for a while. She was tall and slender and often wore a long white dress. One evening, I saw her walking down from the mountain, her dress billowing in the wind, looking ethereal. She also sang; her voice was a bit raspy but full of character. Another was the camp's manager. He was even thinner, with a large beard and a huge turban wrapped around his head, and he never wore shoes. I could feel that he was incredibly close to nature, almost one with it. He knew the natural lore of this land, could identify medicinal herbs, and knew how to cook. He played instruments and chanted poetry, like a conduit for nature in the human world.</p> <p><img src="@assets/2025/L1100008.JPG" alt="" /></p> <p>There was almost no passive entertainment here to kill time. If you wanted to have fun, you had to be proactive and create it yourself. You could either let your imagination run wild playing with stones, or pick up a guitar and try to pluck out a few notes, or grab a book filled with religious tales. For those who don't know how to pass the time, the sense of boredom would be magnified. But for those who know how to feel and create, it was like being a fish in water.</p> <p>When we had nothing to do, we would chat with the other people. A couple from the UK struck up a conversation with us, curious about the Kindle in my hand. He was a music and dance curator, here to seek inspiration. His wife, however, didn't seem to like the place very much; we would even occasionally hear them arguing. There was also a mother and daughter from Ukraine. In the evening, I sat with them and a few young people from Cairo around a fire we had built on the pebble beach. We gathered the scarce surrounding weeds and branches to feed the flickering flames, listening to them share all kinds of experiences and stories.</p> <p><img src="@assets/2025/L1100123.JPG" alt="" /></p> <p>The arrival of night was full of drama.</p> <p>First, we heard someone shout. Following the direction they were pointing, we looked toward the distant mountains and saw a giant moon slowly emerging from behind them. It was close to a full moon, a nearly perfect circle, its width spanning several mountain peaks. It rose from behind the mountains at a speed visible to the naked eye. Though the light wasn't blinding, the craters on its surface were clearly visible. A hazy twilight hue enveloped the moon, making it look like a black hole with immense gravity, constantly pulling at our gaze and stirring our senses. The moon was the protagonist, the sky its backdrop, changing color from a soft orange-red to a deep black as the star of the show moved. Night was the inverse of day, and the moon was like a giant incandescent bulb hanging high in the sky.</p> <p><img src="@assets/2025/L1100079.JPG" alt="" /></p> <p>Silvery moonlight spilled over the Red Sea, the land, and the mountains. Infected by the atmosphere, people grew quiet, no longer speaking loudly. After their evening tea, they sat or lay scattered about. The manager took out a nearby guitar and began to play and sing. Those who could sing joined the melody, while those who couldn't quietly tapped out the rhythm. The gentle sea breeze blew in gusts, as if keeping time for us. Music and laughter drifted from the hut, melting into the bright moonlight and the tender night.</p> <p><img src="@assets/2025/L1100101.JPG" alt="" /></p> <p>The deep night passed in a flash. The sun prepared to rise, beginning the next cycle.</p> <p>The immense energy of Abu Galum seemed to affect everyone who came. My girlfriend's cold symptoms actually worsened here, while my emotions, on the other hand, churned and surged dramatically. It felt unusual; we were still on Earth, after all, but this place felt as if it were exposed to some strange radiation. Once a person steps in, they are bound to be affected. It was all so mysterious.</p> <p><img src="@assets/2025/L1100149.JPG" alt="" /></p> Einbuergerungstestfragen Practice Toolhttps://blog.kaiyikang.com/posts/2025/einbuergerungstestfragen-practice-tool/https://blog.kaiyikang.com/posts/2025/einbuergerungstestfragen-practice-tool/Tue, 02 Dec 2025 00:00:00 GMT<p><a href="https://einbuergerungstestfragen.com/en/">Link to the tool</a></p> <p>This tool was born out of a period of waiting.</p> <p>I remember submitting my application for permanent residence to the official website early in 2025. The preparation process was incredibly tedious. It involved numerous documents and required signatures from both my company and my landlord. I originally assumed the whole process would wrap up in a few months, but it dragged on for more than half a year before I received any feedback.</p> <p>It was a system-generated email with ugly formatting and full of errors. Only one line appeared to be a reply from a visa officer, which seemed to have a faint "human touch." It stated that I had to submit proof of passing the naturalization test within two weeks, or my application would be forcibly withdrawn. I was astonished by this long-awaited response because the official website clearly stated the requirements. To prove integration into Germany, one could provide a German university degree <strong>or</strong> a naturalization test certificate. I stared at that word "or" for a long time to make sure I wasn't seeing things. Later, I appealed to them with legal statutes and website information, but the response remained the same: "we need the test certificate." It felt like talking to a wind-up machine, and it was even worse than a reply from GPT.</p> <p>I eventually figured that arguing was a waste of energy, so I decided to let it go and just take the exam. I checked online and found the earliest available test was three months away, likely because it was fully booked. The registration method was also extremely retro, requiring an in-person visit. I can understand offline exams to verify identity and prevent cheating, but the fact that registration still required a physical presence in 2025 truly baffled me.</p> <p>About a month before the exam, I started slowly gathering information related to the test, such as rules and question types. Simply typing keywords into Google revealed a pile of question banks, including official ones, community-made ones, and PDF collections.</p> <p>I browsed through the top-ranked websites and found that none of them really met my needs.</p> <p>First, logically speaking, the naturalization test is for non-Germans, yet not a single website offered multi-language support. My mother tongue is Chinese. Although I have learned German and can practice in German without issues, I sometimes want to see translations to ensure my understanding is correct. I could not find any website that supported this.</p> <p>Second was the practice method. Perhaps inspired by the official website, almost all sites used a "one question per page" format. You select an answer, the result pops up, and you have to click a button to go to the next question. I couldn't see the results of previous answers, nor did I have a global overview. If I wanted to review incorrect questions specifically, I had to click back one by one, which was very inconvenient.</p> <p>Finally, there was the style. In an age where web and AI technologies are changing daily, the styles of most webpages were surprisingly primitive and cluttered. There were all sorts of strange styles, fonts, and inexplicable images flying everywhere. I just wanted to practice questions, so giving me the question, the answer, and the result would suffice. I didn't understand why it had to be so flashy and complicated.</p> <p>After some simple research and thought, I realized the technical implementation wasn't complex, so I immediately decided to build a practice tool myself. The medium would be a website because it is cheap enough, simple, clean, and satisfies the need to "practice anywhere."</p> <hr /> <p>The overall approach was as follows. First, design the most basic data structure, which naturally supports multiple languages. Second, fill in the data and add the translations. Next comes building the website by adding styles and core logic functions. Finally, deployment. Although the description is sequential, the actual work was done in parallel. For instance, "website deployment" and "data collection" were completed simultaneously. I really enjoyed the moment the website came to life.</p> <p>Initially, I used Google's AI Studio, which allows free use of Gemini 2.5 Pro. This model is very friendly towards long contexts. For general data processing tasks, I would provide the desired input and output formats and ask it to return Python or Bash scripts. In every prompt, I requested it to output scripts that were as simple and easy to understand as possible. This ensured I could quickly browse and comprehend the code, which is friendlier for debugging and gave me a greater sense of control.</p> <p>The original data language was German, and the format was JSON. For the multi-language part, I used additional scripts combined with the Qwen/Qwen3-Max model via OpenRouter. It is cheap and has excellent support for multiple languages.</p> <p>For the domain and deployment, I used Cloudflare. The domain price isn't too high, and it allows one-click configuration for the deployed platform. The platform used is their Pages service. Whenever code is written, the pipeline triggers automatically to generate the static site and deploy it. It also provides an extra branch preview function, which is very developer-friendly. Incidentally, my blog uses similar technology.</p> <p>With the basic platform set up and data filled, I could finally officially start designing the website. The framework used is AstroJS. It has the simple virtues of HTML and JS while supporting modularity and compatibility with other modern frameworks. I feel it is a framework with a very "Eastern" flavor, inclusive and eclectic.</p> <p>When developing web pages, I hold a belief that code and files must remain concise. If something can be done in one file, I will never put it in a second one. Too many modules and files make the application complex and make it difficult for me to understand. Additionally, if I want to add new features or fix bugs, I can send the code containing the full context directly to Gemini. The model can quickly locate and solve the problem.</p> <p>After a dozen rounds of dialogue, the tool began to take shape. It included the most basic styles, could display all questions, and allowed clicking to show correctness. In the days that followed, I successively added language translation, answer resetting, result previews, and other functions. I was very satisfied with it. I could open the browser and quickly practice questions even while on the subway or bus.</p> <p>As I used it myself, I experienced some inconveniences, such as the lack of a dark mode and a favorites mode. The latter acts like a bookmark. If I got a question wrong, I could simply mark it. Even if the answers were reset, I could jump back and do it repeatedly to reinforce my memory.</p> <p>Later, prompted by my friend's inquiry, I bought a one-month membership for Claude Pro out of curiosity, costing about 20 Euros. I wanted to experience if the "Vibe Coding" talked about online was truly that amazing. After using it, I want to say yes, it is indeed amazing. It can call tools and modify code based on just a few of my sentences to solve problems. For the first few days of the membership, I was addicted to using it and couldn't wait to open it and modify the webpage as soon as I got off work.</p> <p>However, the "thrill" came at a price. After requesting it to refactor the code several times, I found that I gradually lost control over the code details. The code in my memory used to be line by line, precisely coupled together, but now it had turned into a fog. I had to dive into the fog to barely make out the location of the code and how it functioned. Although it would confidently tell me that the code's current state was the most concise and clean, I could still occasionally spot some very verbose logic. It would insist on going from A to B and then to C, finally solving the problem through C, when doing A directly would have sufficed.</p> <p>Perhaps my own expression and train of thought were unclear, causing its work to become very arbitrary at times. For example, I am used to handling all styles with TailwindCSS rather than writing extra custom CSS. I feel that whether it is code, a project, or a team, there is a tacit understanding and habit. Reading the "air" they transmit is very important. This thing, which is like physical inertia, saves a lot of communication costs. However, the model doesn't understand this. It is oriented by prompts and results. It cannot read the "inertia" of the current situation, but humans can.</p> <p>Often, I am not even aware of my own intent. I feel this might be an opportunity to correct the model through its mistakes and use this feedback to gradually clarify my own intent, then express it through natural language. But the point of this back-and-forth discussion lies in learning, not in completing a very solid tool or product through engineering. I can understand why people who don't know much about code are fanatical about it because it has a sense of magic. I can also understand why people who know code sometimes don't like it as much because it breaks away from your control and tricks you into thinking things are done. In reality, it hasn't followed the author's true intent. After all, you can see that the code it writes sometimes really isn't that great.</p> <p>In this small project, I strove to find a balance between adding new features and learning code (or maintaining control over the code). I rate myself as doing okay. Although I can't write a lot of the logic code, I at least know roughly how it operates, which is enough to identify the location of small bugs.</p> <hr /> <p>After finishing the website tool, I also completed the exam, so I had time to further optimize and promote this tool. I specifically invited my friend to be the Product Manager for this micro-project. She tried the tool and asked friends to try it too. The feedback received was that the page layout was poor and the color indicators were unclear. Crucially, the website had no memory function, so answered questions would disappear upon refreshing the page.</p> <p>To achieve better teamwork, we used Trello as a Kanban Board. However, since we were still figuring things out, the scale and boundaries for creating tickets weren't defined very clearly, so the practice was sporadic. Sometimes when encountering very small bugs, I would skip them or merge several to handle them at once, bypassing the ticket process.</p> <p>Apart from improving the user experience, the most important thing was promotion. I knew promotion was vital, but due to my personality—especially as someone used to writing programs—I felt a bit shy about strutting around the internet showing off what I had made. Therefore, I had never seriously done any promotion. To shift my mindset, I redefined the action with a different phrase. I didn't call it "advertising" but rather "helping others." This made me feel much better.</p> <p>My intention was to help others, specifically those in a similar situation to mine. With this belief, subsequent tasks seemed much more natural. I wrote a short piece of copy and made some screenshots, placing them in a photo template of a hand holding an iPhone. It looked rough, but it still seemed somewhat professional.</p> <p>The results of the promotion are showing slightly, but it still lacks fire. We still need to learn more about search SEO optimization and recommendation mechanisms.</p> <hr /> <p>Now, this tool runs stably with normal logic. Although there are some pop-up ads, they absolutely do not affect usage. In addition to using tools to ensure the questions are up-to-date, we also plan to add more useful extra information, which will also support multiple languages.</p> <p>Of course, even though I have taken the exam, regarding the permanent residence status, I still need to submit again, pay again, and wait again. I expect everything will drag on until next year before the dust finally settles.</p> Notes on the conversation between Andrej and Dwarkeshhttps://blog.kaiyikang.com/posts/2025/notes-on-the-conversation-between-andrej-and-dwarkesh/https://blog.kaiyikang.com/posts/2025/notes-on-the-conversation-between-andrej-and-dwarkesh/Thu, 23 Oct 2025 00:00:00 GMT<h2>Finding a Suitable Application</h2> <p>In the conversation, they talked about the application of models. He thinks call centers are an example. In the future, it is very likely that manual systems in call centers will be replaced by models because they involve "fixed processes" and "similar tasks," which can be correctly handled and completed within the system. In addition, he abstracted some characteristics. Tasks with these characteristics are suitable for replacement by large models. Here are the key points I recorded:</p> <ul> <li><strong>Exclude external factors:</strong> No interference from external factors, which reduces the possibility of the model making mistakes. Just as a stable factory assembly line requires a specific workshop, so does a model system.</li> <li><strong>Sufficiently closed:</strong> Similar to the point above, this is also to minimize external interference with the system.</li> <li><strong>Digitalized:</strong> This is also easy to understand, because large models are based on tokens. We can't use a large model to bake bread.</li> <li><strong>80% AI / 20% Human:</strong> This point is rather counterintuitive. We hope that models can replace humans 100% and achieve complete automation, but from a real engineering practice perspective, almost all systems cannot avoid the 20% human part. We can reduce it, but we can never eliminate it. For some black swan events or very strange new situations, only humans can handle them properly.</li> </ul> <p>Following his summary, I have also been thinking about what kind of use cases are suitable for using large models, in other words, looking for requirements. One possibility that I can think of and have encountered in my actual work is summarizing test results: in daily meetings, someone needs to check all the test results in all environments, compare them with historical results, share the new or strange test results, and finally ask for others' opinions and ideas.</p> <p>In this example, it first meets the characteristics of being digitalized and sufficiently closed. As for excluding the interference of external factors, it is not fully realized because the upstream and downstream relationships and configuration of the service system are very complex, and the dependencies are not simple. However, external interference is a low-probability event, which can be temporarily ignored here. The final 80/20 ratio is also met. Although it is all done by us now, large models can complete tasks such as historical comparison of test results, finding new error tests, summarizing test reports, and preliminary cause analysis. As for those very abnormal results, they can be directly analyzed with human intervention.</p> <p>If this could be realized, it would save a programmer about half an hour to an hour every day.</p> <h2>I Need to Know What I'm Doing</h2> <p>Andrej uses models with a strong sense of purpose. He knows what good code is and what result he wants. What the model does is to implement the specific things in his mind. If the model happens to be unable to implement it, he is capable enough to implement it himself.</p> <p>There is a way to measure how we use models: one end is not to use them at all, believing that AI is a product of evil, or completely adhering to the old school style. The other is to use them completely, even if I don't understand a single line of code, I let it implement all the functions.</p> <p>Everyone is using the same set of models and following the same measurement method, so the way a model is used largely reflects a person's experience and personality. AI will not produce correct results just by inputting wrong information.</p> <p>Therefore, as Andrej said, it is very important to know what you are doing, to have a strong opinion and will to realize something, so that the core belongs to yourself. You implement this core, and AI is just an assistant to help you complete the things you already know.</p> <p>This also illustrates the limitations of AI in another aspect, that is, it does not know where my limitations are. It's like it can't tell me what I don't know or what I lack. As a user, I can only stare at the text it generates, as if I have understood and learned something, but this is an illusion. I have not mastered these, nor have I experienced them before.</p> <p>So my attitude towards using it is also optimistic but cautious, to prevent it from training me into a fool.</p> <h2>I Have to Consume the Original Content</h2> <p>After this interview came out, my social media timeline was filled with video clips, personal reflections, and summaries about the content. The personal reflections were fine, but the AI summaries were so boring. They had almost the same structure, paragraphs, and content. The only difference was the wording, probably because everyone used different prompts. There is some irony here; the most summarized content by AI was a long video about profound insights into AI, which is a real paradox.</p> <p>After I spent some time watching the entire interview, I am very sure that those summaries are of no value to me, and sometimes even have a negative impact, affecting my impression and understanding of the original video. In the original video, besides the long sections on the theme of AI, there was also a lot of content related to learning and understanding knowledge. This content was very specific. For example, they would talk about how to deal with a code snippet worth learning, starting from using it, paying attention to each functional block and the relationship between each functional block, and grasping the main thread of the matter. In other words, it is very interesting to me, but it may not be to you, and even less so to the model. If you only read the model's summary, you get a so-called essence summarized for you by a ghost, which is not even the idea of a real person. In the entire chain from summary to final publication of the summary, the only humanity that may exist is the taste and choice of the account owner, and such choices often come with advertisements.</p> <p>As humans, I understand that we don't tend to like second-hand things, such as second-hand smoke. But we consume a huge amount of second-hand needs, second-hand opinions, and second-hand information every day. Sometimes it's not even second-hand, but a product that has been layered over and over. They are all digital and transmitted in binary, so they are very deceptive.</p> <p>In order to ensure that more real and individual content can be transmitted, I need to consume the original content.</p> <h2>On Learning</h2> <p>They described the benefits of learning physics, that is, through systematic training, one can acquire an ability to quickly grasp the essence and core of a field, build models through abstraction, and remove temporarily useless noise. A metaphor using mathematics is to Taylor expand a new field of knowledge, and then only keep its first and second order terms, the most essential parts, the fundamental waves with stable frequencies.</p> <p>In terms of content, starting from understanding a module, asking questions, and then throwing out another module that can cleverly solve the problems of the current module, thus triggering the student's "aha" moment, and then gradually expanding into a concise and solid system. When the most basic system appears, everything else is just optimization of efficiency. Most of the systems or tools we use have been baptized by countless optimizations and have become extremely complex, so much so that we cannot understand them at all from external observation.</p> <h2>Source Video</h2> <p>&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/lXUZvyajciY?si=JgsRgmanWJ4IYIr_" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;</p> In the Hospital's Emergency Roomhttps://blog.kaiyikang.com/posts/2025/in-the-hospitals-emergency-room/https://blog.kaiyikang.com/posts/2025/in-the-hospitals-emergency-room/Fri, 19 Sep 2025 00:00:00 GMT<p>This was my first time in an emergency room in Germany, and my first night ever spent in a hospital.</p> <p>The story begins on the evening of the day before.</p> <p>After work, I was at the bouldering gym near my home. The air was thick with the smell of sweaty feet and airborne white magnesium powder. The gym was crowded, with people filling the pathways. I made my way inside and found an empty spot. The wall in front of me was overhanging, and not many people were on it. I managed to climb two or three meters before my strength gave out while I was hanging, and I had to let go. Being inexperienced, I landed butt-first. My body failed to react in time and remained upright, sending a massive shock to my lumbar spine.</p> <p>In that instant, the world stood still. When feeling returned, the pain in my lower back was so intense I couldn't breathe. Even now, the memory makes my palms sweat. I writhed on the mat like a worm, hearing a mix of concerned German and English voices. My mind was a blank slate, and I mumbled that I was fine, I was fine. But I wasn't. I just wanted to avoid drawing attention and conserve my mental energy to focus on recovering.</p> <p>After lying there for a while, I was able to get up and walk. I dragged my battered body home. After washing up through the pain, I lay in bed and immediately began searching for information on my phone. The pain intensified at night, and I knew I couldn't get an appointment with an orthopedic specialist. So, I quickly booked an appointment with a general practitioner for the next morning. I put my phone down and turned off the lights, but sharp pains shot through my spine, and my mind was filled with a whirlwind of anxieties. Unable to sleep, I picked up my phone again, spiraling into a vicious cycle of worry until I finally drifted off in the early hours of the morning.</p> <hr /> <p>The next morning, I first went to the general practitioner's clinic. The doctor listened to my story, briefly examined me, and gave me two options: take painkillers and go for physical therapy, or go to the emergency room for further examination. Without a second thought, I chose the latter. With the doctor's referral letter in hand, I took a bus straight to the large hospital near my home.</p> <p>Perhaps because it was morning, the hospital wasn't very crowded. As soon as I entered, I was hit by that distinct hospital smell, which completely changed the atmosphere. I followed the signs and found the entrance to the emergency room. It was a wide, frosted glass door, completely opaque from the outside, with only the faint, flickering shadows of figures visible from within. I rang the bell, and the door opened without much of a wait. The emergency room was also not crowded, and the medical staff seemed relaxed. They looked at my referral letter and asked me to wait in one of the rooms. Soon after, a doctor who appeared to be of Indian descent came in to see me. She took detailed notes on my condition and instructed me to go for an X-ray first.</p> <p>The radiology department was a separate facility that accepted outside appointments but also worked with the hospital, with the latter taking priority. The waiting area for the X-ray was in a glass corridor. Outside was a long passageway with an ambulance parked at the end. I thought to myself that many critically ill people must have passed through here.</p> <p>After the X-ray, I expected to see the results quickly, but they told me to wait in the main hall. The center of the hall was spacious, with stainless steel chairs lining the walls. Many elderly people were sitting there, but very few young people, which made me feel out of place. The hall connected to elevators and several doors. Every so often, doctors and nurses would appear, pushing patients into the emergency room. The moment the frosted glass doors of the ER opened, I peeked inside and saw numerous beds and doctors moving about. My mind conjured up all sorts of terrible scenarios, probably gleaned from movies and TV shows. In that moment, I truly understood the hardships of an emergency room doctor.</p> <p>When things seemed to have quieted down, I was called in. The emergency room was much noisier now. Paramedics and the people they had brought in were crowded in the hallway. In addition to the female doctor from before, a male doctor had joined her. We stood together in front of a computer in the ER hallway. He pointed at my lumbar X-ray and said, "There's a shadow here. It could be a fracture. We need to do an MRI for a better look. You have two options: either you book an appointment for the scan yourself, or you stay in the hospital overnight for the examination." Without thinking, I chose the latter. The hesitation came only after the decision was made, but regret was pointless, so I had to go with the flow.</p> <p>I sat to the side, and the female doctor skillfully inserted an IV catheter into my arm. At that very moment, the nationwide emergency alert test went off. The sharp, beeping sound emanated from everyone's phones, echoing through the emergency room. I suddenly felt that the alarm sound was a perfect match for the ER, like some kind of symphony on a stage, a special background melody that depicted all the urgency and pain in people's hearts. The emergency room became even more urgent.</p> <hr /> <p>With the paperwork in hand, I left the emergency room and went upstairs to the inpatient ward. The nurse at the front desk asked what I needed. I said I was being admitted and handed her the papers. She paused for a moment, and a colleague came over and asked me, "Where is your doctor?" I said she was busy and had told me to come up by myself. Their final reply, "Who tells a patient to come up all by themselves?" left a deep impression on me.</p> <p>After a phone call, a nurse led me to room 28.</p> <p>There were four large beds in the room, occupied by two elderly men who were resting. I felt completely out of place. A nurse from Myanmar meticulously showed me the facilities. There was a bedside table, and next to it stood two stands: one for an IV drip and another holding a clunky tablet screen that looked like it was from the last century, complete with a help button. I took off my shoes and hung my empty bag to the side. I lay flat on the bed, staring at the ceiling, a mix of novelty and bewilderment washing over me.</p> <p>I knew I had a lot of time, but I didn't know what to do with it. I unconsciously bent my arm, and the IV needle shifted in my muscle, a constant reminder that I was in a hospital bed. I scrolled through my phone, then lay flat and stared into space, repeating this cycle over and over.</p> <p>Every time a nurse came in, she would ask with great enthusiasm if we needed any help. I asked her about the plan, and she said the tests might be tomorrow and that I should just wait for news. It wasn't until the afternoon that I felt a pang of hunger. The meal tray held boiled broccoli, some short, thick pasta, and beef chunks in a broth. It reminded me of my university cafeteria, where the food was always just passable. I choked down the coarse meal, washing it down with an energy drink. Since it was food and could fill my stomach, I didn't complain.</p> <p>The hospital was only a few minutes' walk from my home. I put on my jacket, covered the IV site, and went home under the guise of taking a walk. I grabbed a charger and some simple toiletries, then returned to lie in bed. I don't know how many hours passed. I had assumed the check-up would be delayed until the next day, but a nurse informed me that I could get the MRI scan done. I was pleasantly surprised and hurriedly put on my jacket, went downstairs, and waited for the scan.</p> <p>Some people say that going into an MRI machine can trigger claustrophobia. Online comments circulate, and some people are resistant to the machine, talking about it as if it's as daunting as getting a tooth pulled or undergoing major surgery. I thought they were being dramatic. Lying on the machine, I closed my eyes, put on headphones, and felt it slowly slide me in. It felt like the sky was gradually darkening. Because I couldn't move, my senses of hearing and touch became exceptionally sharp. The machine's humming was extremely rhythmic, sounding like techno music. My mind drifted uncontrollably with the rhythm, and I thought about the enthusiastic nurses: the nurse with a Spanish accent, perhaps a supervisor, who organized everything impeccably; the nurse from Myanmar who could actually speak Chinese, which she said she had learned from her Yunnanese boss at a restaurant. Her tone was energetic yet gentle, and her movements were swift and efficient—truly impressive.</p> <p>The examination was over in less than twenty minutes. I returned to my hospital bed to find a simple dinner: slices of bread, sliced pork, pickles, and butter. To my surprise, I found that even with such a small meal, I felt sufficiently full. Perhaps it was because I had been lying in bed for so long with no physical exertion, but it was enough to make me reflect on whether dinner really needed to be so lavish, or if it was just a habit or a ritual.</p> <p>As it grew dark outside, the daytime nurses said goodbye to us, and a new shift of night nurses arrived. Two of them were younger than me and looked like they were still interns. They said they needed to give me an anti-coagulant injection. Hearing the word "injection," I panicked and tried to refuse. They patiently explained that it was the doctor's order and had to be done. Knowing I couldn't win, I gave in. The younger boy clearly used me as a practice subject, giving me the shot in my stomach. His technique wasn't the smoothest, but thankfully it didn't cause me much more pain.</p> <hr /> <p>It felt like two different worlds. The night in the hospital ward was exceptionally unique, completely different from a normal night. I had thought that a night's rest would be the best form of healing for patients, but it was actually the beginning of suffering.</p> <p>My watch had run out of battery, so I didn't pay much attention to the time. I could see from the corner of my eye that the sky outside was already dark. The ward wasn't completely dark; a few small spotlights still illuminated the outlines of things. Besides me, there were two old men, one to my side and one diagonally across from me. They were both very ill, seemingly having just undergone major surgery. They lay there listlessly, barely able to move on their own. Pain pumps were connected to their bodies, and various monitoring devices flickered with different colored lights at staggered intervals, emitting rhythmic beeping sounds. Every so often, a low humming sound would emanate from some machine somewhere.</p> <p>I was half-awake, half-asleep, and thought someone was setting off fireworks outside. I looked towards the small window. The wall blocked my view, and I could only see some colorful reflections on one side of the window, along with the booming sounds. A line from a song came to mind: "Looking at the light outside the window, I can't tell if it's a streetlight or the sun." I wondered what special day it was. What was the city celebrating? We were trapped in another dimension, unable to move, only able to perceive what was happening in that other world through our faint senses. There is no shared human experience in this world. At this thought, I felt a pang of loneliness, so I shifted my body and tried to go back to sleep.</p> <p>Perhaps because the night was so quiet, one's senses become magnified, and so does the pain. Around midnight, I heard their heavy groans. At first, I didn't pay much attention, thinking that the pain pumps should be helping them. But as the clicking sound of the pain pumps being operated became more frequent, the groans grew louder, eventually turning into screams. I could vaguely make out some swear words, but the words were blurred by the screaming. The old man diagonally across from me cried out the most pitifully. The constant vocalization made his throat hoarse, yet the sounds continued. In the early morning, a nurse came to help him once, but perhaps because it was too deep into the night, his pain was not alleviated. He kept crying out until, in the latter half of the night, he lost all his strength and could only let out soft whimpers. The ward fell silent again, leaving only the machines running, and I was able to drift off to sleep in a daze.</p> <hr /> <p>I endured a rather difficult night. The next morning, the nurse from Myanmar came for the early shift and woke us up with her usual energetic tone. The daylight streamed through the window, brightening the ward and immediately dispelling the heavy atmosphere of the night. Breakfast was bread, cheese, and coffee. I have to say, the coffee in the hospital tasted better—mild yet refreshing.</p> <p>After tidying up a bit, I leaned back in bed and zoned out. The old man diagonally across from me was in much better shape. He could barely move, so he needed the nurses' help for everything from eating and drinking to using the toilet. I saw a nurse lift his blanket, pull up the large surgical gown that was essentially two pieces of cloth, and place what looked like a disposable diaper underneath him so he could relieve himself. Afterwards, the nurse deftly cleaned up the area and went to the bathroom to dispose of the waste. The whole process was extremely professional and swift; it was clear they had done it many times before.</p> <p>On one hand, I was struck by the hardship of being a nurse or caregiver. No wonder such positions are in high demand in Germany. The pay isn't high, the work is dirty and tiring, and it requires immense patience and meticulousness. You'd have to be a saint to do it. On the other hand, I thought that replacing real people with robots is a terrible idea. Technology will certainly improve, and in the future, robots will indeed be able to replace humans. But the problem is, this idea comes from "healthy people," not from "patients." All patients can do is passively accept; they can't decide whether their caregiver is warm or cold. I am more inclined to believe that, despite technological progress, most of the wealthy and powerful will choose to be cared for by real humans, not robots.</p> <p>Another thought I had is that people should maintain a lifelong habit of exercise. Before this experience, I only thought of exercise as a mental boost that could make people feel energetic and full of vitality. Now, having experienced pain, I understand its physical importance. Muscles, tendons, bones, and nerves all benefit from exercise. Once they are strong, even when we get older and our physical functions decline, they can still help us resist various external risks. I hope that when I am old, my body will still be able to support me in performing necessary actions, rather than being frail and full of aches and pains.</p> <hr /> <p>After breakfast, the doctors began their rounds of the entire ward. Just like in TV dramas, a slightly older doctor led the way, with a retinue of younger doctors following behind. When they entered the ward, they checked on each patient one by one. I was the last one they saw.</p> <p>A very experienced-looking female doctor first explained the test results to me in German. Worried that I might misunderstand, I asked her to explain it again in English. Of course, summoning the courage to "ask" was a feat, especially with the young doctors behind her taking notes. I wasn't sure if a foreigner like me would also be documented as a special case in their records. In any case, the final diagnosis wasn't too bad. Although there was a compression fracture in my lower back, thankfully the bone hadn't shifted, which meant no surgery or brace was needed. I just needed to rest. The doctor also emphasized that for at least six weeks, I should not do any exercise or any activity that strained my lower back.</p> <p>Faced with the overwhelming and anxiety-inducing information online, the doctor's conclusion and advice put me at ease. Before I packed my things to leave, the doctor gave me the report and specifically told me to rest well and not to Google any information. If my condition worsened, I should just come straight to the emergency room. Hearing that made me feel incredibly reassured.</p> <p>I walked out of the ward, found the head nurse, and asked her to remove my IV. Finally, with my halting German, I thanked them emphatically for their care and encouragement. While waiting for the elevator, I glimpsed many elderly people being pushed out of their rooms, and I could still hear the sharp, heart-wrenching screams of pain echoing from deep down the hallway.</p> <p>I walked out of the hospital's main entrance, feeling as if I had passed through a boundary between life and death. Inside the boundary were pain and death, while outside were vitality and hope. And delicate warmth and love are the rare rainbows that can cross this boundary.</p> Dahab - Bumpy Canyonshttps://blog.kaiyikang.com/posts/2025/dahab-3-bumpy-canyons/https://blog.kaiyikang.com/posts/2025/dahab-3-bumpy-canyons/Sat, 12 Apr 2025 00:00:00 GMT<p>Our schedules in "Dahab" were separate. She had arranged freediving training and certification, while I wasn't quite that intense and only signed up for a beginner's scuba diving course that could be completed in a day. So during the staggered times, I was free to explore and make my own plans.</p> <p>On the second day after we arrived, she started her training early, while I rented a bike and wandered around town. That evening, when everyone gathered to share their experiences from the day, she noticed I had nothing particularly exciting to say and strongly recommended I join a one-day canyon tour. Without hesitation, I immediately posted in the hostel group. The manager Mr. Z responded quickly and contacted the landlord Mr. M.</p> <p>Mr. M had many friends here and special connections for almost every activity. Within a few hours, we received the price and itinerary. Since I had no special plans the next day, I happily signed up. Documentary filmmaker Mr. A, staying in another room, saw the message and spontaneously decided to join us.</p> <p>In "Dahab", most activities or day trips revolve around nearby areas, usually within one or two hours' drive. The distances aren't far, but due to language barriers and different social environments, arranging things on your own is nearly impossible. So using a travel agency is the best option. There are many travel agencies along the city's coastal center. Even if you try to ignore them, the young men stationed at the doors will eagerly approach you, handing out flyers. The competition is fierce, as you can imagine.</p> <p>Though the activity options vary somewhat, the content is largely similar. So the key is to negotiate a good price with the travel agency. We were lucky to skip this step entirely since the hostel helped us contact them and secure the best rates. Every time I think about it, I'm grateful I chose the right accommodation instead of a rundown resort hotel—otherwise, it would have been a big loss.</p> <p>Early the next morning, a strong northeast wind was blowing. Though not ideal for snorkeling, it didn't stop us from hiking the canyon. We packed up early and waited in the living room for departure. When the time came, Mr. Z hurried over to tell us the vehicle had arrived. We got up and set off for the day's adventure.</p> <p>The vehicle was a modified large van with all the rear seats removed and two rows of benches installed near the windows. There were no fixed seats or seat belts. I felt like I was being treated like cargo in the back of the van. The van started and we drove through town to pick up other travelers.</p> <p>When the door opened the first time, a woman got on. She was dressed in modern, somewhat flashy clothes and immediately started chatting loudly in English, saying she was from Cairo and here to travel. Early in the morning, I was low on energy and responded quietly and politely. After a while, another woman boarded, warmly greeting the first. They seemed to be friends traveling together. She carried a cardboard tray holding two cups of iced coffee. The coffee in plastic cups jostled with every bump, looking like it might spill any second. Watching their flamboyant and carefree personalities, I had a feeling some trouble was coming.</p> <p>Sure enough, as I watched the coffee about to spill, the woman stepped onto the van's edge but lost her footing. The cup slipped from her hand, and coffee spilled just as I had feared. The rear of the van was covered in coffee. They quickly grabbed tissues and started wiping, muttering to each other in Arabic. The driver and guide didn't ask much and moved to chat in the shade under a nearby tree, occasionally glancing back as if waiting for them to finish cleaning up. The coffee didn't splash on my side but landed on the pants of a French guy sitting opposite me. They apologized quickly, and he, quiet and reserved, said it was fine. Though he said so, I could see a hint of fatigue and resignation in his expression.</p> <p>Once the coffee was cleaned up, we finally set off. People's personalities always have unique traits. Friends form through similar temperaments, and certain personality types tend to have consistent and harmonious ways of doing things. Just after the coffee incident was resolved and the van left town, the woman pulled out a small JBL speaker and loudly asked who wanted to listen to music. When no one responded, she played modern Arabic songs on her own. After a few songs from the back, the driver seemed to notice. While shifting gears with his right hand, he poked at the control panel and suddenly a melodious classical Arabic song came from the front speakers. The two music sources clashed like two people walking with a limp, completely out of sync. We were stuck in the middle like the filling in a sandwich, mentally exhausted and tested. After some time, the music faded away, and our nerves slowly relaxed.</p> <p>During the trip, another music-related oddity annoyed Mr. A. Driving through the desert, I noticed the music kept switching every few seconds. At first, I thought it was a technical glitch with the car's audio system, so I didn't pay much attention. Later, Mr. A told me privately it was the driver's doing. He pointed to the driver's right hand, which kept clicking buttons, each click switching to the next song. I realized the driver was secretly controlling everything. Mr. A was irritated by the noise and kept asking the driver and guide in English to quiet down. I didn't catch the details but saw the driver muttering in Arabic while his hand never stopped working. Mr. A gave up trying to communicate and complained to me instead. I wasn't bothered by the music and saw it as a curious human behavior. Out of curiosity and observation, I preferred to think of the driver as a meticulous DJ with his own tastes and rules. He was so strict he judged songs within seconds and cut almost all of them. The one or two songs he kept weren't particularly pleasant to me. I reassured Mr. A that the driver had his own preferences and we just needed to accept it calmly.</p> <p>The last music episode happened when we left the canyon and entered a vast desert. I walked ahead while two companions behind carried a small speaker. As I admired the desert scenery, a powerful tune came from behind—"dong dong dong"—like a film soundtrack. When the main melody started with a female vocal "ha—ah—ei—", I realized it was the theme from "Dune". I wanted to laugh and felt a bit helpless. The choice fit the atmosphere perfectly, but it was ironic that even in the wilderness, we couldn't escape the influence of man-made things. It shows how deeply ingrained they are.</p> <p>About an hour after leaving town, the van slowly left the main road and entered the desert. There was no phone signal, and the map app couldn't locate us. Civilization slowly faded, and the wild side of nature gradually awoke. When we arrived, we all got out. The guide explained the itinerary in simple English: the canyon trail was one-way. Leave your bags in the van, take water with you, and just keep walking forward.</p> <p><img src="@assets/2025/L1090680.JPG" alt="" /></p> <p>The path down the canyon wasn't difficult but was steep. Many tourists had passed, and the rocks had clear footholds. A rope hung down to the valley floor. Holding it tightly, step by step, we followed the guide's instructions and quickly reached the bottom. Huge white rock walls surrounded us, stretching upward and blocking part of the sky and sunlight. Without sunlight, I suddenly felt a chill. The milky white rocks were round and smooth with a matte texture. Touching them gently, fine white sand came off easily, feeling somewhat fragile. Layers of rock had accumulated over time, with different colors and textures forming stripes. The flowing shapes looked like threads drifting on the walls. Being inside felt like entering a funhouse maze, full of surprises and far from boring.</p> <p>We walked forward at uneven paces. Sunlight filtered through the rock walls, casting dappled shadows. The sandy ground and sparse plants shone under the sun. Occasionally, beetles passed by. These rare creatures seemed to walk inside a picture frame, making it feel like watching a high-definition desert documentary.</p> <p><img src="@assets/2025/L1090809.JPG" alt="" /></p> <p>The canyon trail wasn't always smooth. Sometimes we reached forks and had to wait for the guide's instructions. The guide was a Bedouin man wearing a red headscarf, T-shirt, and cloth pants, with simple sandals. He carried no special gear, just a bottle of water. He was very responsible, moving back and forth to ensure no one was left behind or lost.</p> <p><img src="@assets/2025/L1090837.JPG" alt="" /></p> <p>His dark skin and weathered face looked like rock textures—ancient and wise, as if part of the land itself. From a distance, I saw Mr. A chatting with him, then Mr. A excitedly shared the wisdom the guide conveyed. Everything felt harmonious and natural. Perhaps because he had lived on this powerful land all his life, he possessed such simple yet profound wisdom.</p> <p><img src="@assets/2025/L1090817.JPG" alt="" /></p> <p>The canyon's end was an oasis filled with signs of local life. The guide led us into a shelter to rest. The roof was made of palm leaves, and underneath were Arabic carpets and cushions. But after long use and many visitors, the carpets were gray, messy, and worn. Light filtered through the sparse palm leaves, swaying gently in the breeze, creating a dreamlike atmosphere. Next to the shelter was a fire pit, where another Bedouin squatted, brewing tea. He slowly lifted the kettle of boiling water and poured tea steadily into cups.</p> <p><img src="@assets/2025/L1090938.JPG" alt="" /></p> <p>We took the tea and thanked him. The black tea was delicious. While sipping, I sat in a corner chatting with Mr. A. Having interacted before and being in this magical place, I felt freer to express myself. Expressing deep thoughts in a non-native language has always been a challenge for me. I've never had such an experience—sharing my life philosophy and insights in a foreign tongue. Whenever I reach this crossroads, I either retrace my steps, listen quietly to others, or find an excuse to leave. My ability is fading, but my thoughts remain alive and stirring. I'm grateful he tried to listen and follow my vague ideas, politely showing understanding. I felt like a beginner learning table tennis, trying hard to rally. I could feel him using greater skill to keep the ball moving. I deeply appreciate his effort and will always remember that wonderful moment. May I have the strength to face such challenges in the future. (By the way, <a href="https://www.hunchmaker.com">hunchmaker</a> is Mr A's page and there is a lot of interesting content for you to explore.)</p> <p><img src="@assets/2025/L1090942.JPG" alt="" /></p> <p>Lunch was prepared by the guide in advance. Though simple and vegetarian, it smelled fragrant and tasted natural. After eating, we felt comfortable. After a short rest, we set off again for the color canyon.</p> <p>I expected the vehicle to stay on a straight asphalt road, but the driver turned sharply and drove straight into the desert. Before I could react, my body began to bounce with the rough terrain. We rode in a basic van with no advanced suspension. Underfoot was a large metal plate. Every bump on the four tires was amplified and transmitted to our bodies.</p> <p>Cars drove on stone roads, leaving intertwined tire tracks. Though the driver tried to stay on the tracks, he sometimes went off course. We were forced to shuttle through different time tunnels, trying to find our mark. Each time travel was a jolt. At first, I felt excited, thinking this trip was worth it, with a roller coaster ride included. The bouncing lasted a long time and was intense, so I focused solely on not flying out. My muscles seemed to vanish, leaving only hard bones colliding.</p> <p>I had no energy to share my feelings with Mr. A beside me. To ease motion sickness, I looked out the window, forced to accept the shaking scenes imposed by the road and driver. The van sped on, and another jeep flew past from the side. I stared at the jeep in the vast desert, still feeling immersed in a wilderness documentary.</p> <p><img src="@assets/2025/L1090961.JPG" alt="" /></p> <p>Suddenly, the desert became an ocean. The bumpy road was waves stirred by strong winds, and the gravel was the foam. We were like on a small boat sailing in a vast, empty sea. Letting the terrain guide us, it took us to a mysterious shore.</p> <p>The journey wasn't smooth. When the van tried to climb a sand dune, the angle was too steep, and no matter how it adjusted, the wheels couldn't get enough grip. The driver tried many times but failed, then jumped out to adjust the tires. Under the scorching sun, his white robe was dazzling. I didn't know exactly what he did, only heard hissing sounds, maybe releasing air. He squatted fiddling for a few minutes, then restarted the van. The wheels moved in an S shape on the soft dune, like a snake wriggling. After a while, it finally climbed over.</p> <p>Drivers here always face various problems, from environmental challenges to vehicle breakdowns. My girlfriend once had a similar experience. She took a taxi north of town for pool training session, and the vehicle suddenly stalled. While deciding whether to switch cars, the driver ran to the front and started fiddling without hesitation. According to her, he first tested two wires repeatedly with no response. Then he pulled out a wire, peeled off the plastic with his long fingernail, and wrapped shiny copper wire around the old wire. The driver said nothing and worked silently. After a while, the engine roared back to life.</p> <p>The vast desert and roads force you to trust and rely on the driver, no matter how unreliable they seem. They always face problems and solve them in rough, primitive ways. We tend to prefer reliability and avoid uncertainty, but these drivers seem to live on the edge of uncertainty. We seem tied to them by a rope. When they're safe, we breathe easy; when they nearly fall off a cliff, we panic, unsure of what's next. Only when we arrive safely and close the door does our anxious heart finally settle.</p> <p>At speed, we arrived at the color canyon.</p> <p>Compared to the pure white canyon, I prefer to call this one the red canyon. The rock walls seemed dyed with color, showing richer textures than the white canyon. I admit it was beautiful, but the morning's itinerary and the bumpy ride left me exhausted and unable to appreciate it calmly, only wanting to get through quickly and reach the end.</p> <p><img src="@assets/2025/L1090973.JPG" alt="" /></p> <p>Even in my hurry, the rock formations left a deep impression and sparked my imagination. Weathering had created many gaps at the top of the walls. Over time, these gaps tore and widened, forming complete passages inside, with only thin stone pillars supporting the outside. Dragging my tired body, I looked up and thought these pillars and passages resembled temple corridors. Miniature monks lodged between stones, wandering the stone pillar corridors above, conveying spirit and chanting poetry, as if trying to summon me to another mysterious realm. I paused briefly, then regained focus and moved on.</p> <p>We reached the end and got back in the van. The previously noisy tourists no longer fiddled with their speakers, only the driver still kept pressing the skip button, but we were too tired to care.</p> <p>The van drove from dirt back onto asphalt, moving quietly like a soft mattress. I leaned against the window, my back forced upright, yet I still fell asleep. I had no dreams—maybe they were left in the canyon. When I woke, we were already back in town.</p> <p><img src="@assets/2025/L1090985.JPG" alt="" /></p> Slow Writing, Fast Typinghttps://blog.kaiyikang.com/posts/2025/slow-writing-fasttyping/https://blog.kaiyikang.com/posts/2025/slow-writing-fasttyping/Mon, 13 Jan 2025 00:00:00 GMT<h2>Slow Writing, Fast Typing</h2> <p>I just tried writing some characters by hand. Not only was the writing painfully slow, but it took considerable mental effort to form each character in my mind. Compared to typing on a computer, the difference in speed was striking.</p> <p>Having not written by hand for so long, this contrast startled me. My hands trembled like those of an elderly person as I wrote, my eyes fixed on the movement, while my mind raced ahead impatiently. The words I wanted to express had already cycled through my thoughts several times before I could finish writing even the first few characters.</p> <p>I'm not certain, but I feel there's something more natural about the deliberate pace of handwriting that aligns with how our brains are meant to work. In contrast, the lightning-fast pinyin input method allows us to process multiple lines at once, our thoughts racing ahead at breakneck speed, never pausing for breath. As a result, people have grown to dismiss handwriting, no longer giving it a chance. Once we leave school and enter adulthood, pen and paper rarely grace our desks anymore, save for the occasional quick note - gone are the days of lengthy handwritten texts.</p> <p>This inherently natural slowness has crystallized over time into something precious - handwritten letters and greeting cards that find their way into everyone's cherished keepsake boxes. Though the writing may fade and the paper yellow with age, the tactile quality and emotional resonance remain unchanged.</p> <p>This is the unique magic of genuine handwriting.</p> <h2>The Depths of Deep</h2> <p>There was Cal's Deep Work, and then came David's Depth Year. It reminds me of city dwellers who purchase high-end outdoor gear they rarely need. When my partner questioned this, I explained it as aspirational - though we haven't reached that level, simply owning and wearing such gear makes us feel closer to embodying those capabilities, making our aspirations feel slightly more tangible.</p> <p>The concept of "Deep" fascinates me, though it remains elusive. I find myself drawn to related topics, casually exploring their theories while imagining myself achieving such profound focus in my own work. Even now, I haven't reached what I consider a truly deep state, which is why I'm captivated when watching my partner work with intense concentration. In those moments, it's as if the room's energy gravitates toward her; at peak times, it's like the universe itself channels its force through her. She simply immerses herself in her work, naturally embodying this state without conscious effort. There's something beautifully primal and pure about it that I find mesmerizing.</p> <p>My partner is a freediver, where depth is literally the goal. This physical manifestation of depth often connects in my mind with these abstract concepts of deep work and focus. When I attempt to delve deep into a task, rather than achieving true immersion, my wandering thoughts often pull me back to the surface of reality, reminding me of the task's importance and my fear of breaking concentration. It's like performing precise, repetitive work - each motion requires momentum, accuracy, and patience. One moment of distraction can throw everything off balance. I might start fully engaged, my mind either completely focused or peacefully empty, but then larger thoughts emerge, breaking my rhythm. Instead of remaining immersed, I become anxious about losing focus, worried about maintaining the flow. This anxiety amplifies itself, like a cascade of disruption, until I feel completely disconnected, floating in a void of lost concentration.</p> <p>Beyond the physical and temporal aspects of depth, there's also a mental dimension. I experience this when encountering half-forgotten vocabulary, attempting coding problems I vaguely remember, or revisiting books that exist as shadows in my memory. These encounters feel like grasping at gossamer threads of memory - delicate connections that might break at any moment. There's a slight apprehension, mixed with frustration at the lack of clarity. It's not the clean, satisfying feeling of complete understanding, but rather a resistant kind of engagement. While these moments sometimes make me hesitate, I usually push through, forcing myself to grasp these nebulous concepts until they become clear and defined. Overcoming this resistance brings its own unique satisfaction and sense of achievement.</p> <p>Perhaps there's wisdom in this approach - taking things slowly and diving deep.</p> Separate Business and Control Logichttps://blog.kaiyikang.com/posts/2025/separate-business-and-control-logic/https://blog.kaiyikang.com/posts/2025/separate-business-and-control-logic/Thu, 15 May 2025 00:00:00 GMT<p>The core logic is to separate the parts of the program that handle business and domain knowledge (domain or business logic) from the parts responsible for process control, coordination, and decision-making (control logic), thereby improving code maintainability, testability, and reusability.</p> <p>In DDD, business logic corresponds to domain logic, and control logic corresponds to the "application layer" or "service layer."</p> <p><strong>Business Logic</strong></p> <ul> <li><strong>Content</strong>: Business rules, data validation, calculation formulas</li> <li><strong>Responsibility</strong>: Ensure system behavior complies with business requirements and specifications</li> <li><strong>Examples</strong>: <ul> <li>Calculating the total price of an order</li> <li>Determining user permissions</li> <li>Business state transitions</li> </ul> </li> </ul> <p><strong>Control Logic</strong></p> <ul> <li><strong>Content</strong>: Process control, call sequence, conditional judgment</li> <li><strong>Responsibility</strong>: Organize and manage the execution of business logic to ensure correct system flow</li> <li><strong>Examples</strong>: <ul> <li>Handling state transitions in multi-step business processes</li> <li>Controlling transaction start and commit</li> </ul> </li> </ul> <h2>Examples</h2> <h3>Using Python</h3> <h4>Conventional Approach</h4> <pre><code>class UserService: def __init__(self): self.users = [] def register_user(self, username, password): # 1. validate username if username in self.users: return {"success": False, "message": "Username already exists"} # 2. validate password length if len(password) &lt; 6: return "Password too short" # 3. store user self.users.append(username) return {"success": True, "message": "User registered successfully"} </code></pre> <h4>Separated Approach</h4> <pre><code>class UserRepository: def __init__(self): self.users = {} def exists(self, username): return username in self.users def save(self, user): self.users[user["username"]] = user # Domain Logic class UserDomainService: def __init__(self, user_repository): self.user_repository = user_repository def is_username_available(self, username): # Check if the username is already taken return self.user_repository.exists(username) def validate_password(self, password): # Validate the password according to your criteria return len(password) &gt;= 6 def create_user(self, username, password): user = {"username": username, "password": password} self.user_repository.save(user) return user # Application Logic class UserApplicationService: def __init__(self, user_domain_service): self.user_domain_service = user_domain_service def register_user(self, username, password): # 1. Check if the username is available if self.user_domain_service.is_username_available(username): return {"success": False, "message": "Username already taken."} # 2. Validate the password if not self.user_domain_service.validate_password(password): return {"success": False, "message": "Password does not meet criteria."} # 3. Create the user user = self.user_domain_service.create_user(username, password) return {"success": True, "user": user} repo = UserRepository() domain_service = UserDomainService(repo) app_service = UserApplicationService(domain_service) print(app_service.register_user("john_doe", "password123")) # Should succeed print( app_service.register_user("john_doe", "pass") ) # Should fail due to password criteria print( app_service.register_user("john_doe", "newpassword") ) # Should fail due to username already taken </code></pre> <ul> <li><strong>UserDomainService</strong> focuses only on business rules (whether the username exists, password validation, user creation).</li> <li><strong>UserApplicationService</strong> is responsible for process control (call sequence, return results).</li> </ul> <h3>Using Java</h3> <h4>Conventional Approach</h4> <pre><code>import java.util.HashSet; import java.util.Set; public class UserService { private Set&lt;String&gt; users = new HashSet&lt;&gt;(); public Result register(String username, String password) { // 1. Check if the username is available if (users.contains(username)) { return new Result(false, "Username is already in use"); } // 2. Validate the password if (password.length() &lt; 6) { return new Result(false, "Password must be at least 6 characters"); } // 3. Create the user users.add(username); return new Result(true, "success"); } public static class Result { private boolean success; private String message; public Result(boolean success, String message) { this.success = success; this.message = message; } public boolean isSuccess() {return success;} public String getMessage() {return message;} } } </code></pre> <h4>Separated Approach</h4> <pre><code>import java.util.HashMap; import java.util.Map; // Domain Logic class UserDomainService { private UserRepository userRepository; public UserDomainService(UserRepository userRepository) { this.userRepository = userRepository; } public boolean isUsernameTaken(String username) { return userRepository.exists(username); } public boolean validatePassword(String password){ return password != null &amp;&amp; password.length() &gt;= 6; } public void createUser(String username, String password) { User user = new User(username, password); userRepository.save(user); } } // Persistence Layer class UserRepository{ private Map&lt;String, User&gt; users = new HashMap&lt;&gt;(); public boolean exists(String username){ return users.containsKey(username); } public void save(User user){ users.put(user.getUsername(), user); } } // User Entity class User { private String username; private String password; public User(String username, String password) { this.username = username; this.password = password; } public String getUsername() { return username; } } class Result { private boolean success; private String message; public Result(boolean success, String message) { this.success = success; this.message = message; } public boolean isSuccess() {return success;} public String getMessage() {return message;} } // Application Logic class UserApplicationService { private UserDomainService userDomainService; public UserApplicationService(UserDomainService userDomainService) { this.userDomainService = userDomainService; } public Result register(String username, String password) { if(userDomainService.isUsernameTaken(username)) return new Result(false, "Username taken"); if(!userDomainService.validatePassword(password)) return new Result(false, "Invalid password"); userDomainService.createUser(username, password); return new Result(true, "success"); } } public class scratch_46 { public static void main(String[] args) { UserRepository userRepository = new UserRepository(); UserDomainService userDomainService = new UserDomainService(userRepository); UserApplicationService userApplicationService = new UserApplicationService(userDomainService); Result r1 = userApplicationService.register("alice", "12345"); System.out.println(r1.getMessage()); Result r2 = userApplicationService.register("alice", "123456"); System.out.println(r2.getMessage()); Result r3 = userApplicationService.register("alice", "1234567"); System.out.println(r3.getMessage()); } } </code></pre> <ul> <li><strong>UserDomainService</strong> is responsible for business rules (whether the username exists, password validation, user creation).</li> <li><strong>UserApplicationService</strong> is responsible for controlling the process (call sequence, return results).</li> <li><strong>UserRepository</strong> simulates data storage.</li> <li><strong>User</strong> is the domain entity.</li> </ul> <h2>Tips and Suggestions</h2> <ol> <li>Start by designing simple methods.</li> <li>Move simple methods to the control logic layer.</li> <li>Separate out the business logic.</li> </ol> Digital Certificates Flowhttps://blog.kaiyikang.com/posts/2025/digital-certificates-flow/https://blog.kaiyikang.com/posts/2025/digital-certificates-flow/Mon, 24 Feb 2025 00:00:00 GMT<h2>Understanding Digital Certificates</h2> <p>Digital certificates serve three crucial purposes: proving your identity, enabling secure communication, and preventing identity theft.</p> <p>Here's how the certification process works:</p> <ol> <li>Generate your unique cryptographic keys (private and public)</li> <li>Complete an identity verification application</li> <li>Get your application validated by a trusted third party</li> <li>Securely store your credentials together</li> </ol> <p>Using these digital credentials involves:</p> <ol> <li>Creating a digital signature with your private key</li> <li>Having others verify your signature using your public key</li> <li>Allowing third parties to authenticate your identity</li> </ol> <h2>The Certification Process</h2> <p>Getting a digital certificate involves four main steps:</p> <ol> <li><strong>Creating Your Key Pair</strong></li> </ol> <ul> <li>Generate a private key (must remain secret)</li> <li>Create its matching public key</li> </ul> <ol> <li><strong>Applying for Certification</strong></li> </ol> <ul> <li>Compile your identification details</li> <li>Create a Certificate Signing Request (CSR)</li> </ul> <ol> <li><strong>CA Verification</strong></li> </ol> <ul> <li>Submit to a Certificate Authority (CA)</li> <li>Receive your verified certificate</li> </ul> <ol> <li><strong>Setting Up Your Certificate</strong></li> </ol> <ul> <li>Combine your private key with the certificate</li> <li>Store everything securely</li> </ul> <h2>Technical Implementation</h2> <p>The process begins with creating a P12 file (PKCS#12 format), which serves as a secure container for:</p> <ul> <li>Your private key</li> <li>Your certificate (once issued)</li> <li>The certificate chain (once established)</li> </ul> <pre><code>keytool -genkeypair \ -keysize 4096 \ -keystore keystore_file.p12 \ -storetype PKCS12 \ -alias keystore_file \ -dname "CN=keystore_file,OU=dataproxy-services" \ -keyalg RSA \ -storepass abc </code></pre> <p>The generated p12 file itself is a type of keystore that can be directly used as a CSR (Certificate Signing Request):</p> <pre><code>keytool -certreq \ -keystore keystore_file.p12 \ -alias keystore_file \ -file certificate.csr \ -storepass abc </code></pre> <p>After the CA verifies and signs your request, you'll need to merge the original p12 containing the private key to complete the certificate installation:</p> <pre><code>keytool -importcert \ -keystore keystore_file.p12 \ -file certificate.p7b \ -alias keystore_file \ -trustcacerts \ -noprompt \ -storepass abca </code></pre> <h2>Usage</h2> <p>For practical use, I recommend integrating P12 with Vault for enhanced security. Since P12 files are binary and not transmission-friendly, we first convert them to base64 format:</p> <pre><code>base64 -i keystore_file.p12 -o p12.base64 </code></pre> <p>Vault includes encrypted storage functionality, and kv is suitable for storing certificate-type data. We store this file in Vault:</p> <pre><code>vault kv put custom/internal/certificates/kafkaKeyStore [email protected] </code></pre> <p>Finally, configure your application to use the certificate:</p> <pre><code>jks: secretList: - name: kafkaStore vaultPath: "custom/internal/certificates/kafkaKeyStore" </code></pre> <p>This setup allows your application to automatically retrieve the certificate at startup, streamlining the secure authentication process.</p> Traveling Between Frankfurt and Munichhttps://blog.kaiyikang.com/posts/2025/traveling-between-frankfurt-and-munich/https://blog.kaiyikang.com/posts/2025/traveling-between-frankfurt-and-munich/Sun, 30 Mar 2025 00:00:00 GMT<p>We never really discussed a specific time to wake up or leave, just vaguely aimed for around nine or ten in the morning. After finally getting up and quickly packing, we saw it was already nearing 11 AM when we reluctantly headed for the parking garage.</p> <p>On the walk from home to the garage, we passed the train station. The sights and sounds activated familiar pathways in my brain, as if in the next second I would veer off my path and head towards the platform. But then I remembered: with a car, we mostly wouldn't need to take the Deutsche Bahn anymore. A fleeting sense of nostalgia washed over me, but it quickly evaporated when I recalled the frustrations caused by Deutsche Bahn delays.</p> <p>She drove the entire way there. I asked if she wanted me to take over at some point, but she seemed energetic and said she was fine, so she handled the whole drive.</p> <p>We took the A5 south, then near Stuttgart, switched to the A8 heading directly towards Munich.</p> <p>I remember how the weather shifted from overcast to sunny during the drive. It started with dense clouds and a light drizzle, but as we headed south, the clouds broke apart, revealing the sun. Sitting in the passenger seat, I watched the changing scenery fly by. The transition from narrow stretches to wide-open sections was particularly impressive. The road opened up into four wide lanes in our direction, allowing speeds over 130 km/h even in the rightmost lane. Green forests lined both sides, with hazy hills in the distance, and the clouds above were remarkably full-bodied–sometimes light and wispy, other times dense and heavy, the weather shifting as erratically as the needle on the speedometer.</p> <p>We hadn't prepared any music beforehand, only starting to choose songs once we were on the highway. We picked them in an "impressionistic" manner–tracks that left a strong impression came first, followed by those with lighter ones. The strongest impression, undoubtedly, belonged to playlists titled something like "Super High Quality - Essential Car Anthems". I recall seeing similar CDs in the cars of many older folks ("uncles and aunts"), albums with no set name but always some combination of these descriptive adjectives. The artist was invariably listed as "Various Artists" or some other vague term. The flavor was strong, almost overwhelming, yet genuinely authentic.</p> <p>Elements like "Neimenggu" (Inner Mongolia), "Caoyuan" (Grassland), and "Xizang" (Tibet) seemed especially popular with the older generation, pushing "tangled love stories" aside. It was either the deep voice of a middle-aged man or the soprano of a "national treasure" female artist; all sorts of disparate styles converged on a single CD. I heard too much of it as a kid and grew tired of it, but driving my own car now, it felt surprisingly energizing. Could this be the wisdom of the elders they talk about? "Ei hei~ hei ei~"–a sudden playful voice emerged from a mountain folk song, pulling me back, wave after wave, to childhood memories in China. It seems this wasn't such a bad way to reconnect with that feeling of home after all.</p> <p>Along the highway, rest stops appeared at regular intervals. Their placement seemed remarkably deliberate. Just as your bladder started sending signals, you'd find a place for relief within another ten minutes or so of driving. I boldly hypothesize that the spacing of these stops might be calculated based on the average adult bladder capacity and tolerance time–hence their exquisite timing, which had us clicking our tongues in admiration from our seats.</p> <p>The rest stops had a strong commercial atmosphere; stopping almost forced you to spend money. Besides the pricey fuel, this was evident in how toilets and food were handled. Using the restroom cost one Euro, but inserting the coin dispensed a one Euro voucher usable for purchases in the adjacent shop. The shop prices felt about 1.5 times higher than usual, so with the voucher, the final cost wasn't too bad. However, you could only use one voucher per item, meaning any extra vouchers had to be saved for the next time. Since using the toilet was often necessary, getting a voucher was unavoidable. To feel like you were getting "some value", you felt compelled to use the voucher, which often meant buying another coffee. Then, after drinking it in the car, you'd wait for the caffeine to work its magic, setting up the cycle of needing the toilet and spending money at the next rest stop. These people are sharp, and rather shrewd.</p> <p>After about five hours of driving, we finally reached Munich. She was exhausted and quickly fell asleep. I didn't disturb her, my thoughts drifting to EU regulations on long-haul driving safety–the rule requiring a 45-minute break after every 4.5 hours of driving. It makes sense, as driving is undeniably tiring, but it contrasts with practices back in China. I recall that buses in China rarely seemed to stop for driver breaks. It reminded me of Turkish drivers too, often in white shirts, who might stop mid-route to pick up more passengers, their breaks consisting of just a quick coffee or tea. They handled those huge steering wheels like wizards, weaving through winding mountain roads with incredible ease, almost nonchalantly. But ultimately, all that rushing was about earning more money and getting more rest time later, invisibly cultivating and refining various driving skills in the process. It's admirable, yet undeniably tough work.</p> <p>Having experienced the narrow A5, I opted for the A9 to A3 route for the return trip–heading north first, then west. This proved to be a better choice; the roads were wider, and the roadside facilities were more comprehensive.</p> <p>Learning from our previous long drive, we knew to prepare a playlist beforehand to avoid scrambling for music. The quest for our favorite songs inadvertently turned into a late-night music appreciation session, resulting in a very long list. We barely added any duplicates, except, curiously, for "'Tao Ma Gan'", which appeared twice. Perhaps the allure of grasslands and stallions led us independently to the same choice. Well played!</p> <p>Compared to her steady approach, my driving style was a bit more spirited. The actual speed limit was dictated by whichever was lowest: the posted signs, the car's ability, or my own comfort level. Once comfortable, I found a certain pleasure in overtaking–always prioritizing safety, of course.</p> <p>This time, the weather pattern was reversed: it went from overcast to sunny. The sun gradually peeked through the clouds, its rays warming my arms and body. Thanks to the wide roads and abundant greenery, the views were excellent. Watching small towns perched on hills appear and then recede, I imagined their entirely different ways of life, ones not centered around major urban hubs.</p> <p>After driving for over two hours, we found a "bladder break" rest stop. We parked, used the facilities, but skipped buying food, instead retrieving the simple fish burgers she had made. The sun was veiled by mist, the weather turned grey, and a wind picked up. We sat in the car listening to a finance podcast, munching on our burgers and discussing the content, sipping coffee when thirsty, and carefully trying not to drop crumbs in the clean car. Perhaps because I wasn't fully accustomed to driving yet, my eyes felt strained. So, I relinquished the driver's seat to her for the remainder of the journey.</p> <p>With practice, her driving had improved noticeably. She managed the speed well and overtook with more confidence. This led us to another realization: when something happens, it's better to state it calmly rather than exclaiming. Shouting can startle the driver, leading to erratic maneuvers, poor driving, and frayed nerves. Suppressing the urge to yell and maintaining composure benefits everyone.</p> <p>The rest of the trip was uneventful. We entered the city and pulled into the parking garage. I didn't feel as tired as before and was getting more accustomed to driving and being driven. It seems these trips might become more frequent from now on.</p> From Batch Processing to Granular Tasks - Elegantly Refactoring Error Trackinghttps://blog.kaiyikang.com/posts/2026/from-batch-processing-to-granular-tasks---elegantly-refactoring-error-tracking/https://blog.kaiyikang.com/posts/2026/from-batch-processing-to-granular-tasks---elegantly-refactoring-error-tracking/Fri, 06 Mar 2026 00:00:00 GMT<p>There is a classic scenario where we need to shift our system from batch level status tracking to granular single task error tracking. Doing this improves both maintainability and observability.</p> <p>To achieve this goal, we can let the Data Object carry the target state itself instead of passing the state uniformly through method parameters. This keeps code changes minimal and maintains an elegant structure.</p> <h2>Current Situation</h2> <ul> <li><strong>Fetching tasks</strong>: The use case layer directs the service layer to pull pending tasks from the database in batches.</li> <li><strong>Fetching data and calling</strong>: The infrastructure layer sends requests where each request carries the batch payload.</li> <li><strong>State transition</strong>: The service layer wraps the results into a custom <code>BatchStatus</code> like <code>SUCCESS</code>, <code>PARTIAL_SUCCESS</code> or <code>FAILED</code> based on the external service response or thrown exceptions.</li> <li><strong>Batch update</strong>: The use case layer categorizes this batch of tasks based on the <code>BatchStatus</code> and iteratively calls Repository layer methods to update the database state.</li> </ul> <h2>Core Objective</h2> <p>We want to improve the observability and granular troubleshooting capabilities of our services during batch task processing.</p> <p>When an underlying service call fails, the system should do more than just mark the task with a generic FAILED or RETRY status. It should accurately propagate native underlying error details such as HTTP status codes, specific Client Error Codes and Service Response Messages. These details should then be persisted into the database record of each specific task.</p> <h2>Encountered Issues</h2> <p>Under the current mechanism, the service suffers from a severe Context Dropping issue:</p> <ul> <li><strong>Information disconnect</strong>: The underlying infrastructure captures error codes and messages but these debug details are merely printed to the logs and then discarded.</li> <li><strong>Flat state</strong>: The service layer only wraps a bare <code>BatchStatus</code> and a collection of affected task IDs.</li> <li><strong>High debugging costs</strong>: All failed tasks look exactly the same in the database. If developers want to find the root cause for a specific task they have to take the task ID and search through the log system manually.</li> </ul> <p><img src="@assets/2026/202603_refactoring_error_tracking_01.webp" alt="" /></p> <h2>Code Example</h2> <p>We need to fetch the Task from the persistence layer, request the external Client to get the outcome, pass the Result containing the success or error details back to the Task and finally update the persistence layer.</p> <h3>Step 1: Equip BatchProcessResult with Error Carrying Capability</h3> <p>Assume we currently have a <code>BatchProcessResult</code> that contains a unified <code>BatchStatus</code> after calling the external Client which includes multiple ItemIds.</p> <p>Previously we could only return a simple <code>BatchStatus</code>. Now we want to record the specific details of each item. To do this we introduce a new <code>ErrorInfo</code> record and establish a mapping between the item ID and the error information inside the Result.</p> <pre><code>// 1. Add record to encapsulate error details for each item public record ErrorInfo( String failedReason, String errorLabel, String errorDetails ) {} // 2. Refactor the batch processing return result class public class BatchProcessResult { // Previously only had the Batch result private BatchStatus batchStatus; private final Set&lt;String&gt; failedItemIds = new HashSet&lt;&gt;(); // [New] Used to store what error occurred for which Item private final Map&lt;String, ErrorInfo&gt; errorInfoByItemId = new HashMap&lt;&gt;(); // omitted getters and setters public void addFailedItemWithError(String itemId, ErrorInfo errorInfo) { failedItemIds.add(itemId); errorInfoByItemId.put(itemId, errorInfo); } public ErrorInfo getErrorInfo(String itemId) { return errorInfoByItemId.getOrDefault(itemId, null); } } </code></pre> <h3>Step 2: Catch Exceptions at the Lowest Level</h3> <p>At the location where the network call is initiated at the infrastructure or service layer, we must change the old habit of simply printing logs and returning a vague FAILED status. We need to capture HTTP status codes, Messages returned by third party systems and other details. We then wrap them into <code>ErrorInfo</code> and pass them upwards.</p> <pre><code>// Service layer interacting with client to get result public BatchProcessResult processBatch(String clientId, Set&lt;String&gt; itemIds) { RemoteClient client = ClientRegistry.get(clientId); if (client == null) { // [New] If client fails then all items in this batch share a uniform error ErrorInfo errorInfo = new ErrorInfo( "CLIENT_CONFIG_ERROR", "NO_CLIENT", "more details" ); return createBatchResultWithError(BatchStatus.FAILED, itemIds, errorInfo); } try { Response response = client.sendItems(itemIds); if (response == null) { return createBatchResult(BatchStatus.SUCCESS); } // [New] Can return a BatchProcessResult containing different item errors return createBatchResultForPartialSuccess(itemIds, response); } catch (ClientRequestException e) { // Further enrich the client error return information ErrorInfo errorInfo = new ErrorInfo( "CLIENT_REQUEST_ERROR", e.getErrorCode(), e.getMessage() ); return createBatchResultWithError(BatchStatus.RETRY, itemIds, errorInfo); } catch(Exception e) { ErrorInfo errorInfo = new ErrorInfo( "SYSTEM_ERROR", "UNKNOWN_EXCEPTION", e.getMessage() ); return createBatchResultWithError(BatchStatus.UNKNOWN, itemIds, errorInfo); } } </code></pre> <p><img src="@assets/2026/202603_refactoring_error_tracking_02.webp" alt="" /></p> <h3>Step 3: Orchestrate in the Use Case Layer and Stitch Data Before Persistence</h3> <p>This is the step to integrate or align the task and the result. In the use case layer after getting the <code>BatchProcessResult</code> from the lower levels, we do not throw it directly to the Repository. Instead we enrich the task information based on the result by assigning the error details passed from below to the Task entity according to the itemId via <code>enrichTasksWithErrorInfo()</code>.</p> <pre><code>// Use case layer responsible for orchestrating services public void processBatchTasks(String clientId) { // Get tasks or Entities List&lt;Task&gt; tasks = taskService.getNextTasks(); if (tasks.isEmpty()) return; Set&lt;String&gt; itemIds = extractItemIds(tasks); BatchProcessResult result = batchProcessor.processBatch(clientId, itemIds); // Since batch succeeded with no errors there is no need to bind to tasks if (result.getBatchStatus() == BatchStatus.SUCCESS) { taskService.updateTasks(tasks, TaskStatus.SUCCESS); return; } // [New] Before persisting stitch the error details from result onto the tasks enrichTasksWithErrorInfo(tasks, result); // Dispatch TaskStatus based on BatchStatus switch (result.getBatchStatus()) { case PARTIAL_SUCCESS -&gt; handlePartialSuccess(tasks, result); // Extra handling for failed items needed case RETRY -&gt; taskService.updateTasks(tasks, TaskStatus.RETRY); default -&gt; taskService.updateTasks(tasks, TaskStatus.FAILED); } } // Method to stitch task and result together private void enrichTasksWithErrorInfo(List&lt;Task&gt; tasks, BatchProcessResult result) { for (Task task : tasks) { ErrorInfo errorInfo = result.getErrorInfo(task.getItemId()); if (errorInfo != null) { task.setFailedReason(errorInfo.failedReason()); task.setErrorLabel(errorInfo.errorLabel()); task.setErrorDetails(errorInfo.errorDetails()); } } } </code></pre> <h3>Step 4: A Clean Service Layer for Persistence</h3> <p>This service layer can focus solely on processing task related information without being polluted by details like <code>failedReason</code> from the result.</p> <pre><code>// 1. The current Service layer update method becomes extremely clean public void updateTasks(List&lt;Task&gt; tasks, TaskStatus status) { // tasks already contain errorLabel and errorDetails so Repository will naturally flush them to DB upon update // Here we do final processing based on TaskStatus switch (status) { case SUCCESS -&gt; taskRepository.deleteTasks(tasks); case FAILED -&gt; handleFailed(tasks); default -&gt; handleRetry(tasks); } } // 2. Task entity class prepared to receive this data @Entity @Table(name = "tasks") public class Task { @Column(name = "error_label", length = 255) private String errorLabel; @Column(name = "error_details", length = 255) private String errorDetails; // omitted code } </code></pre> <h2>Conclusion</h2> <p>By introducing <code>ErrorInfo</code> as an additional Context carrier we decouple the lifecycles of capturing exceptions and persisting exceptions. The bottom layer only needs to record truthfully and the service and use case layers simply follow the map to assign values.</p> <p>This ensures the single responsibility of methods and modules while greatly enhancing the observability of the system in the production environment.</p> <blockquote> <p>Disclaimer: The code examples provided are simplified and generalized for educational purposes, focusing on architectural patterns rather than specific project implementation.</p> </blockquote> Ascending into Silencehttps://blog.kaiyikang.com/posts/2026/ascending-into-silence/https://blog.kaiyikang.com/posts/2026/ascending-into-silence/Thu, 08 Jan 2026 00:00:00 GMT<p><img src="@assets/2026/202601_dahab_climbing_01.webp" alt="" /></p> <p>Less than half an hour by pickup truck takes you into the canyon. This is Bedouin territory where self-driving is virtually impossible, so having a local guide lead you into the mountains is almost the only choice. Perhaps it is because it is December, or simply that the mountains flanking the canyon are too high, but for most of the time, sunlight only graces the peaks and cannot penetrate to the foot of the mountains. The air is dry and crisp, and the temperature is exceptionally comfortable.</p> <p>The mountains, composed of masses of grey-yellow granite, loom large and blot out the sun. Standing before them creates a veritable dizzying sensation. The various structures and crevices on the exterior of the rocks are seen as excellent handholds and footholds in the eyes of climbers, making this a renowned climbing destination.</p> <p><img src="@assets/2026/202601_dahab_climbing_02.webp" alt="" /></p> <p>Rows of metal hangers are pre-installed on the rock face. Adopting the Lead Climbing method requires the climber to start from the bottom carrying a rope, clipping a quickdraw and the rope into every hanger they pass. If physical strength fails and an accidental fall occurs, the body will be braked by the nearest quickdraw. Even if a single anchor point fails, the existence of multiple protection points makes total system failure highly unlikely. Therefore, although rock climbing looks frightening, the actual safety factor is very high. When the lead climber reaches the top and sets up the anchor station, the belayer below can tighten the rope to assist their descent. Beginners can subsequently utilize this pre-set rope to perform the most basic Top Roping.</p> <p><img src="@assets/2026/202601_dahab_climbing_03.webp" alt="" /></p> <p>A more primitive form is known as Free Solo, which involves climbing upward relying solely on body contact with the rock wall without the use of any ropes or protective gear. Our local guide, Mohamed, sometimes chooses this method to reach the top and set up top-rope safety systems for beginners. Although he typically selects routes of lower difficulty, the mere act of climbing without any protection, and doing so barefoot, is enough to make one's heart pound.</p> <p>The tactile sensation of real rock is superb. Although the surface is somewhat slippery, it is far harder and more solid than the artificial holds in a gym. The interior is a solid core that feels reassuring to the touch. The structure is endlessly varied, with protrusions of all sizes combined in wild abandon.</p> <p>Donning the gear, I approach the rock face and wait until the moment before climbing to put on my climbing shoes. While the rock will not destroy the shoes, the fine grit and gravel will. They are hard and sharp, and they quickly score the rubber soles with mottled patterns. After tying the knot in the rope Mo just lowered from above, and mutually checking that the safety settings for both climber and belayer are correct, the climb can finally begin.</p> <p><img src="@assets/2026/202601_dahab_climbing_04.webp" alt="" /></p> <p>It is different. The feeling is completely different from gym climbing. Without fixed colors and specific routes, the path feels like entering a fog or being assaulted by a sandstorm, making it difficult to identify with the naked eye. Consequently, all the body's sensory receptors must be opened. Arms and fingers begin to probe the rock like radar. If an attempt to find balance here fails, one might as well try over there. With so many variations, there is always one that suits you. Touch a little, turn the body, and touch again. This trial and error may last a long time, but the rock will always wait for you with infinite patience. Then, suddenly, in a specific instant, the fingers find a special protrusion. It feels just the right size to grip. Hanging onto it, the whole body seems filled with balance and power. The "Aha moment" arrives. The body feels as if it has received some kind of summons and can suddenly lift upward with strength, immediately locking a toe onto a solid rock point. A new stability is achieved, and one can explore and scan once again. Every mysterious contact brings me joy, making me unable to help but shout, "So cool!"</p> <p>Of course, that thrill cannot last forever. The vast majority of the time is still filled with confusion and tension. Within the visible scope, I cannot find a usable point for what feels like ages. The strength in my legs and arms drains away bit by bit. As I encounter my critical limit, words starting with F cannot help but slip out. At this moment, all I hear is Mo faintly saying from below, "Do not Fxxx." This sense of casualness is also reflected in the wording of his commands. One phrase that remains fresh in our memory is "Stand Up." It is such a simple instruction, yet on the rock wall, it becomes an incredibly difficult action to realize. Either the angle is insufficient or the strength is lacking. It makes me think of a phrase: Great truths are always simple, but behind them lies so much painstaking effort.</p> <p><img src="@assets/2026/202601_dahab_climbing_05.webp" alt="" /></p> <p>Upon reaching the summit, after experiencing muscle soreness and toil, what you get is not a ceiling, but the vast, towering mountains and the sky. This is the best reward for rock climbing.</p> <p><img src="@assets/2026/202601_dahab_climbing_06.webp" alt="" /></p> <hr /> <p>Time slips away. The sun gradually glided behind the valley, and the sky began to darken.</p> <p>People gradually departed in their vehicles, and the valley became somewhat quieter. The light dimmed, and the colors reflecting off the valley shifted from yellow to greyish-black. The massive rocks appeared much sterner, connecting with the sky to create an oppressive sensation.</p> <p><img src="@assets/2026/202601_dahab_climbing_07.webp" alt="" /></p> <p>Before it went completely dark, we tried to squeeze in a few more routes to save this precious time. Taking advantage of this interval, Mo had already lit a bonfire with his back against the rock wall. Beside the fire lay firewood gathered from the valley, placed upon a large Arab carpet. He told us this was the most traditional Bedouin custom.</p> <p>He and his assistant uncovered dish after dish of dinner from the back of the pickup truck. There was a stew made of potatoes and onions, a simple salad, and roasted chicken. We asked who cooked it, and he said it was his mother. Although it sounded simple, the taste was surprisingly delicious. Paired with the valley bonfire and the fatigue after climbing, I felt it was the best dinner of the entire trip. I felt my stomach was like a bottomless pit, continuously sucking in food, yet I did not feel uncomfortably full at all. We thought about saving some food for Mo and his assistant, but he humbly told us that this was prepared specially for us and asked us to enjoy it to the fullest. While my mouth was saying thank you, another piece of chicken suddenly appeared on my fork.</p> <p>Night fell. This time there was no bright full moon, so the stars became significantly clearer. The flames flickered, and the wood crackled. After eating our fill, people sat together. Some lay on the carpet while others leaned against the rock wall, but everyone's eyes drifted between the flames and the stars. The relationships between people seemed to grow closer, and the topics of conversation appeared to answer the call of the night, becoming deep and complex.</p> <p><img src="@assets/2026/202601_dahab_climbing_08.webp" alt="" /></p> <p>I took the tea Mo had just brewed. Its warm and sweet flavor was delightful as I lay on the carpet, staring blankly at the sky. As the firewood was gradually consumed, the flames were tamed and became less fierce, while the quiet firelight made the stars stand out even more brightly. The black silhouettes of the rocks outlined several distinct dividing lines against the sky, and my field of view was narrowed into a single beam.</p> <p>The flames finally dimmed, and the sounds of conversation faded away. All sounds were absorbed by the rocks and sand, leaving the valley incredibly quiet. In my memory, I have never experienced such a quiet moment. The rubbing sound of any movement, and even the beating of my heart, became incredibly clear. I floated up, drifting in the canyon, bathed beneath the stars. All elements were natural and primitive, continuously radiating ancient scents toward me, enabling me to strip away modern technology and thought to return to the most original state and feeling. At this moment, I realized how noisy and complex the noise existing in daily life is. There are always ticking sounds in our ears, coming either from neighbors or devices within the room. Even when closing one's eyes, there is always some light penetrating through. When sleeping, noise continues to haunt us. They seem to have become the culprit of nightmares, forcing the body to unceasingly accept and feel them throughout the entire life cycle. It has never been truly dark, nor has it ever been truly quiet.</p> <p><img src="@assets/2026/202601_dahab_climbing_09.webp" alt="" /></p> <p>The silence in the valley was like a clear, cold spring flowing over my sensory receptors, cleansing them and making them sensitive. I could feel its ability to cleanse, which most likely did not come from childhood memories of the city, but felt more like being in the cradle, or even before life was born. Biological genes whispered in my ear, "Feel it well. This is a gift, the most precious present of life." Similar perceptions flowed between the people sitting around the fire. There was no need to speak, nor to transmit any signals, only to float in the here and now. Everyone shared this primordial tranquility, rising and falling at the same frequency between the starry sky and the rocks.</p> <p><img src="@assets/2026/202601_dahab_climbing_10.webp" alt="" /></p> <p>Time passed even faster. The bonfire eventually burned out, and we had to get up and leave. A quiet dream was startled awake, and everyone was reluctant to part, wishing we could wander here all night. Sitting on the back of the truck, I watched the black mountains on both sides retreating continuously. Illuminated by the car lights, they changed shape, assuming a completely different posture from the daytime. As the truck drove onto the main road, the streetlights on both sides and the distant small town gradually lit up, and the stars in the sky became dim, receding into another dimension.</p> <p>We drove back toward that artificial sensory world filled with harsh light and noise, while that dreamlike tranquility had already extinguished along with the bonfire, left behind in the valley forever.</p> The Humble Clinic and the Time Weighed Down by a Stonehttps://blog.kaiyikang.com/posts/2026/the-humble-clinic-and-the-time-weighed-down-by-a-stone/https://blog.kaiyikang.com/posts/2026/the-humble-clinic-and-the-time-weighed-down-by-a-stone/Sat, 10 Jan 2026 00:00:00 GMT<p>With only two days left before our departure, I wanted to make the most of my time by the sea and find more opportunities to dive. I asked my diving instructor for advice and she recommended that I see a local doctor for a diagnosis to check if I was fit to dive. She also suggested a driver she knew and told me that I could contact him for help.</p> <p>His name was Mustafa. While I was adding his contact information, I noticed that his status was a long string of Arabic text. I copied it and asked Gemini for the meaning and it explained that the signature contained the six core wishes of a Muslim's life. These included beneficial knowledge, a clean livelihood, deeds that are accepted, a humble heart, a tongue that remembers the divine with kindness, and a resilient body.</p> <p>We communicated briefly so that I could give him my location. After a while, he seemed to realize that my app's language was set to German and he suddenly sent me a voice message. It is uncommon for drivers to send voice notes because most of them only speak Arabic while their English is just basic. Usually, the most common way to communicate is by texting in English about the time, location, and price. When I played the message, I found it interesting to hear him greet me in a mix of English and German. I replied that I lived in Germany but I was originally from China.</p> <p><img src="@assets/2026/dahab_clinic_02.webp" alt="" /></p> <p>He arrived on time the next morning. Once I got in the car, we began to chat casually. I used the share live location feature in the app to help him find me but I was unfamiliar with the tool and forgot to turn it off after I got in. A few hours later, as we were walking along the beach, our real-time location continued to update. He later told me with a smile that his son had seen the phone and was confused because the passenger’s location was already so close to the doctor and he wondered why I still needed a ride.</p> <p>I was curious about how he knew German and he told me that he had worked in dive shops for over a decade. There were so many guests from Germany that he eventually learned how to speak and understand the language through constant communication. I spoke a few sentences in German and he understood everything while providing simple replies.</p> <p>We reached the clinic after a ten-minute drive. The storefront was small and sat next to a construction site for a new resort which was as chaotic as ever. I asked how to register for a visit and Mustafa told me to leave it to him. He pulled a piece of paper from his pocket that looked like a photocopy of a passport on one side. He folded the printed side inward so that the blank side faced out and turned it into a long strip. Holding the paper, we walked to the entrance where a woman was already waiting. Mustafa asked for her name and wrote it at the top of the strip before writing my name below it. Since no one else was waiting, he found a stone on the ground and used it to weigh the paper down at the clinic door. He told me that I could wander around for a bit and come back when it opened because he would message me as soon as the check-ups started.</p> <p><img src="@assets/2026/dahab_clinic_01.webp" alt="" /></p> <p>I imagined that he had helped many people like me before but what really made me think was this minimalist or even primitive booking system. It was simple and cheap yet completely effective. So many automated electronic systems have made me feel numb because although they usually work correctly, they still experience mysterious errors from time to time. Electronic booking systems are modern and beautiful but they are also mostly the same. This slip of paper carried different names and handwriting along with the faint marks of a pen running out of ink. It was dynamic and constantly changing and anyone could understand it and use it immediately. It was truly an elegant and efficient solution and I loved such a simple system.</p> <p>When it was time for the clinic to open, an elderly man appeared in the room wearing a yellow robe and a tan vest. He mopped the floor and opened the door before picking up the small piece of paper that had been held down by the stone. I followed the others into the waiting room which was a small space with standard steel hospital chairs and children's drawings of the ocean on the walls. There were tattered comic books on the tables and some art pieces on the far walls including Arabic calligraphy and depictions of Islamic women's eyes.</p> <p><img src="@assets/2026/dahab_clinic_03.webp" alt="" /></p> <p>I thought the man was just an assistant but I was surprised when he started calling the name of the first patient. When the person answered, he crossed off the name and led them into the exam room. It turned out he was actually the doctor. I was second in line so I was called in quickly. The exam room was small and dim with only a single overhead light. There was a large anatomical chart of the ear, nose, and throat on the wall as well as models on the desk. He asked me to sit down and I explained my situation in English. He listened and nodded slowly before asking a few minor questions and using professional equipment to check my ears and nose. During the exam, he also asked me to try equalizing my ear pressure.</p> <p>After the examination, he explained that it was not a common injury but rather an inflammation of the nasal mucosa that caused the Eustachian tube to become blocked. This meant that fluid could not drain properly and that was why I was hearing strange noises. His English was incredibly fluent and he explained everything with a slow, calm, and rhythmic tone. I did not understand many of the technical terms but I could guess the general meaning. He prescribed several standard medications that were so common that I could name them fluently when I described them to other diving instructors later. He also specifically told me to use a nasal spray before and during my dives to avoid the risk of further inflammation.</p> <p><img src="@assets/2026/dahab_clinic_05.webp" alt="" /></p> <p>Once the session ended, I paid him 300 Egyptian pounds which is about 6 Euros. I told him that he was likely the best ENT doctor since he practiced in such a famous diving destination. He placed his hand over his heart and smiled humbly to express his gratitude. With that, my visit was complete.</p> <p>We returned to my accommodation and I said goodbye to Mustafa. He stepped on the gas and drove off to find the next guest who might share a story with him.</p> <p>I stood by the road with many thoughts in my mind. Both digital booking systems and expensive health insurance felt unnecessary in that moment. My problem was finally solved by a stone, a piece of paper, and a fee that was almost free. This is perhaps the unique character of this land because it is raw and casual yet it possesses a simple wisdom that goes straight to the essence of things.</p> <hr /> <p>Magical stories always seem to unfold on this enchanted land and I have another anecdote about my search for medical care.</p> <p>We often think that rock climbing or diving are the most dangerous activities but the most troublesome thing I encountered during this trip was actually a case of paronychia. I had trimmed my nails too short which led to a minor infection and a small bump formed that felt painful when pressed. It was around nine or ten in the evening and I was worried that my foot would get worse and ruin my climbing plans for the next day. While I was feeling distressed about it my girlfriend offered to go out and find some medicine for me.</p> <p>She left in such a hurry that she did not check the location beforehand and only realized after she stepped outside that her phone had no signal. She had to find her way by memory alone and walked along the dark streets for a while until she reached a building only to discover it was a dental clinic. She was disappointed and ready to head back but she coincidentally ran into Mo who is the manager of our hostel at the street corner. She quickly asked him where she could find a pharmacy and Mo pointed across the road to show her where it was. She thanked him and crossed the street until she finally found a pharmacy that was luckily still open.</p> <p>Inside the pharmacy the clerk did not speak much English so she took out her phone to show a photo of my infected nail. While she was showing the picture another customer walked in who looked like an intellectual. By a stroke of luck he spoke Arabic and English along with a little bit of Chinese. The clerk brought out two identical tubes of antibiotic ointment from a large shelf in the back and gave one to my girlfriend and one to the man. They were surprised to discover such a strange coincidence because he was also there to buy medicine for a friend who had the exact same nail infection.</p> <p>The clerk then brought out a small white bottle with an Arabic label that carried an air of ancient Middle Eastern mystery. He explained to both of them that they should use this medicine for the first two days to draw the pus out and then apply the antibiotic cream if the infection remained. The intellectual became very interested and asked for more details about the secret of this bottle. He originally intended to buy only one tube of ointment but he decided to purchase the mysterious bottle as well after hearing the explanation.</p> <p>When she returned to our accommodation she excitedly shared the story of her evening adventure. I listened to her and examined the bottle as it felt increasingly mysterious to me. It seemed as though all the coincidences in the world were gathering around this object and I felt that failing to use it to heal myself would be a slight against the fate of the universe. I opened the cap and saw a greyish-yellow ointment submerged in clear glycerin. I looked it up on my phone and learned that the mud was actually kaolin clay which has powerful physical adsorption properties. It works like a magnet to pull pus from deep within the skin to the surface.</p> <p>I was half-skeptical but I applied the mud to the gap in my nail for three or four consecutive days. The swelling miraculously disappeared and I could finally walk without feeling any pain. In a literal sense, my toe was healed by a series of accidents and chance encounters. It truly is a magical land.</p> A Simple Understanding of Java Exceptionshttps://blog.kaiyikang.com/posts/2026/a-simple-understanding-of-java-exceptions/https://blog.kaiyikang.com/posts/2026/a-simple-understanding-of-java-exceptions/Thu, 19 Feb 2026 00:00:00 GMT<p>Program execution is not always smooth sailing. It is terrifying if a program exits directly upon encountering an error, or continues running with errors until we have no idea where the problem originated.</p> <p>To solve this problem, people introduced Exceptions. They can isolate errors, gracefully exit programs, and make the system more robust.</p> <h2>What is an Exception?</h2> <p>An Exception is the objectification of an unexpected state.</p> <p>Executing a program is essentially a process of pushing and popping the stack.</p> <p>A calls B, B calls C, C finishes running and returns to B, and B then returns to A.</p> <p>An unexpected state means that if an exception occurs during the execution of C, it cannot follow the normal return path.</p> <p>After an exception occurs, the JVM will look for an exception handler, which is the catch block. If B does not have one at this time, it will directly pop B, then look for A, and finally return.</p> <p>Objectification means that Java will wrap the error into an Object, which contains useful information:</p> <ul> <li><strong>Type:</strong> What happened <code>e.getClass().getName()</code></li> <li><strong>State:</strong> Detailed description of the error <code>e.getMessage()</code></li> <li><strong>Context:</strong> Stack Trace <code>e.printStackTrace()</code></li> </ul> <p>For example:</p> <pre><code>public class ExceptionDemo { public static void main(String[] args) { try { calculate(10, 0) } catch (ArithmeticException e) { // e is the wrapped Object System.out.println("1. Type (Class): " + e.getClass().getName()) System.out.println("2. State (Message): " + e.getMessage()) System.out.println("3. Context (Stack Trace):") e.printStackTrace() } } public static void calculate(int a, int b) { int res = a / b // An exception object is generated here } } </code></pre> <p>Exceptions that occur in a try block all inherit from <code>Throwable</code> and are mainly divided into three categories:</p> <ul> <li><strong>Checked Exception:</strong> Must be handled at compile time using try catch or throws, otherwise it will not compile. An example is <code>IOException</code>.</li> <li><strong>Runtime Exception:</strong> Also called Unchecked Exception. These are usually code logic errors that the compiler does not force you to catch. Examples include <code>NullPointerException</code> or <code>ArithmeticException</code>.</li> <li><strong>Error:</strong> These are usually severe errors at the JVM level, such as <code>OutOfMemoryError</code>. The program generally cannot recover, and it is not recommended to catch them.</li> </ul> <p>The <code>ArithmeticException</code> in the example belongs to Runtime Exception.</p> <p>It will be thrown at runtime.</p> <h2>Throw and Throws</h2> <p>We can also manually throw an exception using the <code>throw</code> keyword. For example:</p> <pre><code>public void setAge(int age) { if (age &lt; 0 || age &gt; 150) { throw new IllegalArgumentException("Invalid age: " + age + ", must be between 0 and 150") } this.age = age } </code></pre> <p>There is another similar keyword <code>throws</code> used in the method signature.</p> <p>It means that this method explicitly will not handle the exception, and it needs to be handled by the caller.</p> <p>For example:</p> <pre><code>public void loadConfig() throws FileNotFoundException { FileReader fr = new FileReader("config.txt") } </code></pre> <p>Here <code>FileNotFoundException</code> must be declared because the compiler knows that reading a file might fail.</p> <h2>Custom Exception</h2> <p>Java native Exceptions are usually technical errors, such as null pointers, network disconnections, and so on, but they cannot describe business errors.</p> <p>In most cases, we should inherit <code>RuntimeException</code>.</p> <p>For example:</p> <p>Java</p> <pre><code>public class InsufficientBalanceException extends RuntimeException { // 1. Besides the message, it can carry specific business data private final double balance private final double amountRequested // 2. Provide a constructor to pass information to the parent class public InsufficientBalanceException(double balance, double amountRequested) { // Call parent constructor to generate standard error description super("Transfer failed: Current balance " + balance + " yuan, attempted to transfer " + amountRequested + " yuan") this.balance = balance this.amountRequested = amountRequested } // 3. Provide Getters to easily extract data in the catch block for compensation logic public double getBalance() { return balance } } </code></pre> <p>We can precisely catch this specific exception:</p> <pre><code>try { // InsufficientBalanceException will occur here bankService.withdraw(100) } catch (InsufficientBalanceException e) { // Only handle the case where there is not enough money showDepositDialog() } </code></pre> <p>In the <code>super</code> of the example, we passed the concatenated string to the parent class. When <code>e.getMessage()</code> is called later, this information will be printed.</p> <p>In addition, we can also pass a <code>Throwable</code>:</p> <pre><code>public InsufficientBalanceException(double balance, double amountRequested, Throwable cause) { super("Transfer failed: Current balance " + balance + " yuan, attempted to transfer " + amountRequested + " yuan", cause) this.balance = balance this.amountRequested = amountRequested } </code></pre> <p>The <code>cause</code> represents the root cause. It can accept other exception information. For example:</p> <pre><code>try { // 1. Simulate getting balance from database, which might throw SQLException currentBalance = database.getBalance(userId) if (currentBalance &lt; amount) { // 2. Scenario A: Logic error, proactively throw business exception throw new InsufficientBalanceException(currentBalance, amount, null) } // do something } catch (SQLException sqlEx) { // 3. Scenario B: Technical error, database is down // Wrap it as a business exception and pass the root cause sqlEx to record it throw new InsufficientBalanceException(currentBalance, amount, sqlEx) } </code></pre> <h2>How the JVM Handles Exceptions</h2> <p>Let us look at this example.</p> <pre><code>try { a / 0 } catch (ArithmeticException e) { // Do something } </code></pre> <p>When the JVM execution reaches <code>a / 0</code>, an error occurs, and it should immediately jump to the catch block. It does not do this by scanning the code line by line to find the catch block, as that would be too inefficient.</p> <p>In fact, to skip the code that does not need to run between the error occurrence and the catch block, the Java compiler creates an Exception Table in the <code>.class</code> file during compilation.</p> <p>It contains the following information:</p> <ul> <li><strong>From:</strong> The bytecode instruction line where the try block starts.</li> <li><strong>To:</strong> The bytecode instruction line where the try block ends.</li> <li><strong>Target:</strong> The bytecode instruction line number where the catch block starts.</li> <li><strong>Type:</strong> The type of exception that can be caught.</li> </ul> <p>When <code>a / 0</code> triggers an error, the JVM immediately looks up the table using the current line number.</p> <p>If it is between From and To, and the type matches, it jumps directly to the Target to execute. This is very fast.</p> <h2>What if there is no try catch?</h2> <p>What if there is no try catch block? If an error occurs but there is no try catch at this time, the JVM has no way to find a matching entry in the exception table. What should it do?</p> <p>It will perform a very heavy operation called Stack Frame Unwinding.</p> <p>For example, if an exception occurs executing C:</p> <ul> <li><strong>Force pop:</strong> The JVM will ignore the local variables in C and directly pop the Stack Frame from the virtual machine stack.</li> <li><strong>Restore context:</strong> It restores the execution environment of the previous method B.</li> <li><strong>Continue looking up the table:</strong> It takes the exception object from C and looks up the table in B.</li> <li><strong>Loop until termination:</strong> It loops the above process until it retreats to <code>main</code> with nowhere else to go, ultimately causing the current thread to terminate unexpectedly, or even causing the entire program to crash and exit.</li> </ul> <p>Therefore, do not forget to write try catch blocks, otherwise the program will fall into an abyss.</p> <h2>Performance Issues of Exceptions</h2> <p>In high concurrency scenarios, throwing a large number of exceptions will cause the server CPU to spike.</p> <p>The reason lies in <code>e.printStackTrace()</code> which we mentioned earlier, as it records the Stack Trace.</p> <p>At the lowest level, when <code>new InsufficientBalanceException()</code> is called, the native method <code>fillInStackTrace()</code> in the constructor of the ancestor class <code>Throwable</code> is invoked.</p> <p>The key point is that calling <code>fillInStackTrace()</code> pauses the Java execution flow or the current thread to capture every Stack Frame from the top of the stack down to the <code>main</code> method, including class names and method names, and finally records them.</p> <p>This consumes a huge amount of resources.</p> <p>Therefore, for custom business exceptions like <code>InsufficientBalanceException</code>, we only care about the status code and specific business message. We do not care much about the stack trace information. Thus we can directly return <code>this</code>:</p> <pre><code>// Add this inside InsufficientBalanceException @Override public synchronized Throwable fillInStackTrace() { // Block the JVM from capturing the stack to improve performance return this } </code></pre> <h2>Conclusion</h2> <p>A Java Exception wraps an interrupted unexpected execution path into an Object carrying context data and stack information.</p> <p>In practice, we can trigger exceptions via <code>throw</code>, declare risks via <code>throws</code>, and use exception chaining to decouple business logic while ensuring underlying traceability.</p> Freediving into the New Yearhttps://blog.kaiyikang.com/posts/2026/freediving-into-the-new-year/https://blog.kaiyikang.com/posts/2026/freediving-into-the-new-year/Thu, 01 Jan 2026 00:00:00 GMT<p><img src="@assets/2026/dahab_diving_03.webp" alt="" /></p> <p>We came to Dahab for a holiday this time, and I signed up for the freediving Wave 1 course, which is the beginner level. However, I ultimately could not complete all the requirements because my ears suffered some injury due to pressure equalization problems. Consequently, I have to wait for a future opportunity to retake the exam.</p> <p>This is a true story. It is not a complete epic of heroism, nor does it have a dramatic plot where I overcome difficulties and fears to finally reach a happy ending.</p> <p>One training day, the wind and waves were very strong, especially as we swam back to shore at the end of the session. The wind blew towards the land while the waves curled and crashed in the same direction. The sand on the seabed was stirred up like a dust storm, making the water completely murky. My snorkel became almost useless as it flooded every few meters, and I swallowed several mouthfuls of seawater. Pah, pah, pah, it was incredibly salty. It was a bitter saltiness that numbed my mouth and made my taste buds feel like a burden. I was worried about hurting my stomach, so I had not taken motion sickness pills beforehand, but I regretted that decision after floating for a while. My body's internal regulation simply could not keep up with the churning of the waves and failed. My stomach turned upside down, and about ten meters from the shore, I finally vomited. Since I had not eaten much before, nothing really came out. It was just a muscle spasm and a token gesture. I am sorry to disappoint the fish in the sea because you lost out on a delicious meal. Scrambling, I finally inched my way to the shore. The seawater pounded against the coastal reef and whipped up huge waves. We stumbled, dragged, and pulled each other until we finally stood on dry land. Looking back at the white-capped waves, I felt a lingering fear mixed with a sense of wonder about what kind of terrifying thing I had just been fighting against.</p> <p>After several months of intermittent practice and testing it in the sea, I can confidently say that I have indeed learned Frenzel equalization. Mastering this technique depends entirely on fate, specifically whether you have been "chosen" by Herman Frenzel, the man who invented this technique. Those who are chosen can learn it in a few hours, whereas those who are not must spend months. Perhaps my name is too hard to pronounce, but unfortunately, I was not chosen, so I spent several months learning and verifying whether I had actually mastered it. All the parts involved are hidden beneath the skin and are concentrated around the nose and mouth. If you cannot distinguish the movement of the internal muscles, it feels like a plate of spaghetti is coiled inside your face. It is not only intricate but also covered in cheese, making it sticky and messy. It took me a long time to identify where the soft palate is, which part is the middle ear, and where the vocal cords are located. After dismantling this plate of pasta, I finally gained a clear understanding of my facial anatomy and realized how terrible my equalization technique had been last year and the year before. My technique was wrong the moment I entered the water. I could not equalize my ear pressure, and it hurt when the pressure increased, yet I did not know what was happening. At one point, I even suspected my ears were disabled and thought I might have to say goodbye to diving forever. From my current understanding, the old me was simply extremely lacking in self-awareness. Therefore, even though I harbor some regrets, I will not force myself to finish all the items. After all, an injury is an injury, and since I can physically feel it, the only thing I can do is wait until I recover to find a chance to try again in the future.</p> <p><img src="@assets/2026/dahab_diving_07.webp" alt="" /></p> <p>It is hard to say what I was fighting against. Maybe it was myself, or maybe it was the sea, but I cannot describe exactly what was stopping me from diving deeper even though I had enough air and my ear pressure was properly equalized. I want to argue that it was pure panic. My body twitched on its own. I felt it, and I felt like I could not make it, so I came up. However, an abstract explanation probably will not convince others because it is not a visible problem. To put it bluntly, it is a mental issue. Abstract problems naturally get abstract answers, and the final conclusion is simply: do not panic, just go down and do it. I am satisfied with this answer. In terms of personality, I am the type who stands there screaming that it is all over and I am doomed even when danger is ten thousand miles away. Being extremely cautious means that even if I try my hardest to "court death," the result is usually just a scrape. My skin might "die," but I remain very much alive. My mental will seems unable to make me a true adventurer.</p> <p>Beneath the surface, it is relatively calm. The body is wrapped and squeezed by water from all directions, and time becomes strangely distorted. Sometimes when watching videos, I would see beginners enter the water kicking their fins like they were pedaling a tricycle, their legs spinning rapidly. I would lie in bed laughing at their strange movements and thinking I would be fine, but when I actually got into the water, reality slapped me in the face because I was pedaling my little tricycle just as vigorously. Later analysis showed that I was too nervous. My attention was entirely on other muscles, and I could not care less about my legs, so my subconscious chose the fastest kicking posture, which is cycling. Although it feels fastest for the body, it is the least efficient method underwater. The body's intuition does not conform to the common sense of this new world. To become a native of this new world, the body needs to learn new etiquette. One important rule of etiquette is slow and rhythmic movement. Equalization must be soothing and rhythmic, kicking fins should be slow and powerful, and the body must be adjusted slowly to ensure correct posture. You cannot be a sloppy young person. You must be a precise and powerful grandmother.</p> <p>Ka, ka, ka, rather than kakaka.</p> <p><img src="@assets/2026/dahab_diving_00.webp" alt="" /></p> <p>The standard process sounds tempting, but it really depends on the right timing, favorable location, and harmonious people. A calm sea surface, warm and gentle sunshine, and a well-rested diver make absolutely the best combination. Every detail of movement is sorted and combined in the correct way, and then displayed at the right moment. The body perceives it and controls it. Oh heavens, it is wonderful. Of course, this is the ideal situation. In reality, my body is more like a chaotic amateur troupe where various movement elements become very disordered and unknowable because they are manipulated by fear and tension. Sometimes I focus on breathing and forget to kick, and by the time I remember to kick, my body has already crashed into the rope. The upper body and lower body seem to be two separate systems. Not only do they have their own ideas, but they are also hostile to each other. The general in my mind becomes numb and entranced due to fear, unable to play its proper role, and the body thus becomes a mere shell.</p> <p>Space also becomes strange. Usually, we live in a planar space where we can only move forward, backward, left, and right. Even if we jump up, we can only raise ourselves a little bit. However, once underwater, forward, backward, left, and right are not very important. Instead, you need to control moving up and down. If feet are down and head is up, it means you have to travel upwards for tens of meters. As mentioned above, the feet are very important as the power source. They need to work stably and effectively. Body posture is another key point. It needs to be slightly straight, with hips thrust forward and buttocks tightened. The arms and head act as guides, leading the rocket to launch in the correct direction. Later, I reviewed my own movements. I did not look like a rocket but rather like a nimble dead fish, either spinning inexplicably around the rope or turning horizontal and being pushed back to the surface by strong pressure. From this perspective, if I were the instructor, I would be very reluctant to admit that these were the movements of a proper beginner. I collected all the relevant videos in a folder and named it "Collection of Idiotic Behaviors," which I feel is quite appropriate.</p> <p><img src="@assets/2026/dahab_diving_02.webp" alt="" /></p> <p>Correct movement is no longer just a written theory but has particularly strong practical significance. Our experienced Instructor A once mentioned that after solving many small problems correctly, the whole will not go wrong. I think he is right. Some movements felt extremely weird to me, but after the small problems were corrected, the movements surprisingly became much smoother. The underwater world is like a relatively ideal sandbox environment where default parameters are reset every time you enter the water. As long as you operate and run your body with the correct steps and processes, you will always get the correct result in this environment.</p> <p>Getting used to floating on the sea makes it less scary. Although the bottom of the sea is deep and invisible, I cannot reach it anyway, so the fear is mostly visual. I like the mask treated with shower gel because it is clear, transparent, and fog-free. When the sunlight penetrates, there is a touch of bright orange amidst the pure blue, which is the color of the guide rope. Of course, it could be other colors, but I quite liked the small orange rope from these training sessions. Once or twice, I saw a small creature passing by. I do not know its name, but it looked like a type of miniature jellyfish. Its body was long and transparent, and its vital organs were orange-red, wrapped small inside the transparent body, with wispy tendrils underneath. It floated away gracefully with the current right before my eyes.</p> <p>I watched it, fascinated. This was a fantasy moment unique to the ocean.</p> <p>I am leaning back on a soft sofa, which is solid and stable, yet my body still feels the rising and falling motion just like the undulation of the waves. It is still affecting my body's sensors at every moment, which is quite amazing. I think this will drive me to try diving again.</p> <p><img src="@assets/2026/dahab_diving_06.webp" alt="" /></p> The Journey and Reflections on Refactoring Simple Use Casehttps://blog.kaiyikang.com/posts/2025/the-journey-and-reflections-on-refactoring-simple-use-cases/https://blog.kaiyikang.com/posts/2025/the-journey-and-reflections-on-refactoring-simple-use-cases/Tue, 18 Nov 2025 00:00:00 GMT<p>In <a href="a-simple-example-for-optimizing-architecture/">the previous post</a>, the core theme we dealt with was <strong>ModuleStatus</strong>, and we discussed a simple example. This example contained two Use Cases: one for updating data and another for resetting data. The former is triggered via HTTP, while the latter is triggered via Events. The main logical difference between the two is that the former requires fetching existing data from an external service before storage, whereas the latter simply stores the data directly.</p> <p>After the last post was published, I received feedback from colleagues and had a discussion with them. Following that discussion, I realized there were some issues with the previous post: not only was the definition of Use Cases insufficiently clear, but there was also ambiguity and confusion regarding classic DDD (Domain-Driven Design) and Clean Architecture concepts.</p> <p>Therefore, I plan to perform a relatively complete refactoring of the current Implementation to gain a clearer understanding of it myself. This journey will start from the core Domain, pass through several Use Cases (one of which involves interaction with external systems and is relatively complex, so I will examine it as a key point), and finally end with how to invoke these Use Cases.</p> <p>Looking at the file directory structure, I have divided it into three parts:</p> <ul> <li><strong>Application</strong>: Responsible for application logic, primarily containing Use Cases and Ports.</li> <li><strong>Domain</strong>: Where the core logic resides, containing only the most basic algorithms and independent of any external technical implementation.</li> <li><strong>Infrastructure</strong>: Primarily responsible for the Adapter implementations of Ports—i.e., various external adapters that depend on the Ports in the Application.</li> </ul> <p>Here is the complete architecture map. Don't worry, we will build it from scratch.</p> <pre><code>. ├── application │ ├── exception │ │ └── ModuleStatusApiException.java │ ├── port │ │ ├── inbound │ │ │ └── ModuleStatusQueryPort.java │ │ └── outbound │ │ ├── ModuleStatusRepositoryPort.java │ │ ├── RegistryModulePort.java │ │ ├── EventServicePort.java │ │ └── TenantDirectoryPort.java │ └── usecase │ ├── ModuleStatusQueryUseCase.java │ ├── ResetModuleStatusUseCase.java │ └── UpdateModuleStatus.java ├── domain │ ├── exception │ │ ├── EnterpriseModuleNotFoundDomainException.java │ │ ├── ModuleNotFoundDomainException.java │ │ └── TenantNotFoundDomainException.java │ ├── model │ │ ├── EnterpriseModuleStatus.java │ │ ├── TenantModuleStatuses.java │ │ └── TenantModuleStatus.java │ └── service │ └── ModuleStatusService.java └── infrastructure └── adapter ├── inbound │ └── rest │ ├── EnterpriseModuleStatusResource.java │ └── dto │ └── TenantModuleStatusesRequest.java └── outbound ├── mapper │ └── RegistryModuleToEnterpriseModuleStatusMapper.java ├── RegistryModuleAdapter.java ├── EventServiceAdapter.java └── TenantDirectoryAdapter.java </code></pre> <h2>Creating the Domain and Simple Use Cases</h2> <p>In the beginning, data structure is the starting point for everything. I remember someone once said, "Software system = Data Structure + Algorithm." A recent blog post I browsed also mentioned that data structure determines the <a href="https://www.google.com/url?sa=E&amp;q=https%3A%2F%2Fnotes.mtb.xyz%2Fp%2Fyour-data-model-is-your-destiny">shape of the product</a>.</p> <p>Therefore, the first thing we need to do is create the data models and structures. Undoubtedly, this belongs to the Model within the Domain. Here is the code for the data model: the Module ID and its status form the most basic <strong>Status</strong>, while <strong>Statuses</strong> contain a list of Statuses and a tenant identifier (TenantId).</p> <pre><code>package com.example.modulesystem.domain.model; public record TenantModuleStatus(@NonNull ModuleId moduleId, Boolean isActive) { public TenantModuleStatus withStatusReset(){ return new TenantModuleStatus(this.moduleId(), false); } } </code></pre> <pre><code>package com.example.modulesystem.domain.model; public record TenantModuleStatuses(@NonNull TenantId tenantId, @NonNull List&lt;TenantModuleStatus&gt; tenantModuleStatus) { public Optional&lt;TenantModuleStatus&gt; getModuleStatusFor( @NonNull final ModuleId moduleId) { return tenantModuleStatus.stream() .filter(fs -&gt; fs.moduleId().toString().equalsIgnoreCase(moduleId.toString())) .findFirst(); } } </code></pre> <p>The corresponding Database Entity and Table have already been created in advance.</p> <p>Next, we need to define the Repository interface. Although its structure is simple and fits the templated CRUD (Create, Read, Update, Delete) pattern, the key is: <strong>It defines what interfaces I need to use within the Use Case.</strong> Interfaces and their corresponding implementations usually contain a lot of boilerplate code, so only the Port part is listed here, ignoring specific implementation details.</p> <pre><code>package com.example.modulesystem.application.port.outbound; public interface ModuleStatusRepositoryPort { TenantModuleStatuses findByTenantId(@NonNull final TenantId tenantId); boolean save(@NonNull final TenantModuleStatuses tenantModuleStatuses); } </code></pre> <p>In Clean/Hexagonal Architecture, the Repository Port belongs to the Application Outbound layer; it exists to serve the Use Case. As the leader of the business logic, the Application explicitly knows what services and tools it needs from the external world to implement specific business logic. It does not care about specific implementation details, nor does it care about the source or acquisition method of the data.</p> <p>Having prepared the Port for interacting with the database, we can now start from a Product Manager's perspective and create the most basic Use Cases in the Application. Here are two simple examples:</p> <p>The first Use Case fetches data directly from the database, assuming no logging is required.</p> <pre><code>package com.example.modulesystem.application.usecase; public class ModuleStatusQueryUseCase { private final ModuleStatusRepositoryPort moduleStatusRepository; public ModuleStatusQueryUseCase(final ModuleStatusRepositoryPort moduleStatusRepository) { this.moduleStatusRepository = moduleStatusRepository; } public TenantModuleStatuses apply(@NonNull final TenantId tenantId) { return moduleStatusRepository.findByTenantId(tenantId); } } </code></pre> <p>The second Use Case is slightly more complex, but still only relies on the Repository Port: read data from the database, reset the status, and save it back to the database.</p> <p>The Use Case controls data via the Port and does not need to—nor should it—know any details of the underlying implementation.</p> <pre><code>package com.example.modulesystem.application.usecase; @Slf4j public class ResetModuleStatusUseCase { private final ModuleStatusRepositoryPort moduleStatusRepository; public ResetModuleStatusUseCase(final ModuleStatusRepositoryPort moduleStatusRepository) { this.moduleStatusRepository = moduleStatusRepository; } public void apply(@NonNull final TenantId tenantId) { log.info("Invoking factory reset of module statuses for TenantId={}", tenantId); final TenantModuleStatuses currentStatuses = moduleStatusRepository.findByTenantId(tenantId); final TenantModuleStatuses resetStatuses = new TenantModuleStatuses( currentStatuses.tenantId(), currentStatuses.tenantModuleStatus() .stream() .map(TenantModuleStatus::withStatusReset) .toList() ); moduleStatusRepository.save(resetStatuses); } } </code></pre> <p>At this point, we face a question: Suppose we have a Controller or Resource that needs to query data from the database. Can it connect directly to the Repository Port via the Controller, bypassing the Use Case? The answer is no. Because the Controller, located in the Inbound Adapter layer, is responsible for translating external calls. It is like a front-desk employee; they cannot run to the data center to retrieve data without the approval of a middle manager (Use Case). Strictly speaking, even very simple logic should belong to a specific Case and should not be allowed to use a "backdoor" just because it is simple.</p> <p>This is the current directory tree structure:</p> <pre><code>. ├── application │ ├── port │ │ └── outbound │ │ └── ModuleStatusRepositoryPort.java │ └── usecase │ ├── ModuleStatusQueryUseCase.java │ └── ResetModuleStatusUseCase.java └── domain └── model ├── TenantModuleStatus.java └── TenantModuleStatuses.java </code></pre> <p>The third Use Case is the most complex, but also the closest to reality. We need to complete the following steps in order:</p> <ol> <li>Get the TenantId from the input parameters.</li> <li>Ensure the TenantId exists via the external TenantDirectory service.</li> <li>Get the <code>RegistryModule</code> list from the external <code>RegistryLookup</code> and <code>RegistryModuleService</code>, and determine if the Modules exist in this list.</li> <li>Determine if the Modules belong to the Enterprise type.</li> <li>Transform the data type and store it.</li> <li>Call the external EventService to trigger cache invalidation.</li> </ol> <p>The original core code briefly describes this process:</p> <pre><code>public void apply(final ModuleStatusModel moduleStatusModel) { // Step 1 final TenantId tenantId = moduleStatusModel.tenantId(); // Step 2 guardTenantExists(tenantId); // Step 3 final List&lt;RegistryModule&gt; registryModules = lookupModulesForRegistry(tenantId, getContext()); guardModuleExists(moduleStatusModel, registryModules); // Step 4 guardModuleIsEnterprise(moduleStatusModel, registryModules); // Step 5 final ModuleStatusModel mappedModuleStatusModel = mapModuleIdToMatchRegistry(moduleStatusModel, registryModules); final boolean stateHasChanged = moduleStatusRepository.save(mappedModuleStatusModel); // Step 6 if (stateHasChanged) { log.debug("Invalidating ModuleCache for TenantId={}", tenantId); eventService.invalidateCacheForTenants(List.of(tenantId.toString())); } } </code></pre> <h2>Deep Dive and Breakdown of the Use Case</h2> <p>Step 1 is to get the TenantId from the input parameters. It is very simple, so we can skip directly to Step 2. The TenantId type defaults to a core type defined by the system, so I won't elaborate on it here.</p> <pre><code>final TenantId tenantId = moduleStatusModel.tenantId(); </code></pre> <h3>Step 2</h3> <p>Step 2 requires checking whether the TenantId exists via the external system <code>TenantDirectory</code>. The original method is as follows:</p> <pre><code>private void guardTenantExists(final TenantId tenantId) { // Simulating proprietary driver connection control try (...) { final boolean tenantExists = tenantDirectory.tenantExists(tenantId); if (!tenantExists) { log.warn("No tenant found for id '{}'", tenantId); throw ModuleStatusApiException.unknownTenant(tenantId); } } } </code></pre> <p>This code looks simple, but it fuses three distinct meanings:</p> <ul> <li><strong>Infrastructure</strong>: Specific technical control, e.g., <code>InternalDriver.controlRouting</code>.</li> <li><strong>Outbound Port</strong>: Calling external dependencies, e.g., <code>tenantDirectory.tenantExists</code>.</li> <li><strong>Domain</strong>: Business rules, i.e., the tenant must exist <code>if(!tenantExists)</code>.</li> </ul> <p>We need to split it up.</p> <p>First, isolate the Domain logic. As the Domain, it shouldn't know how to query, nor should it care about logging. It has no dependency on the outside world and knows no technology stack; it only knows "If it doesn't exist, throw an error." Following this train of thought, the simplified logic is:</p> <pre><code>package com.example.modulesystem.domain.service; public class TenantValidator { public void guardTenantExists(final TenantId tenantId, final boolean exists) { if (!exists){ throw new TenantNotFoundDomainException(tenantId); } } } </code></pre> <p>And the Exception needed here:</p> <pre><code>package com.example.modulesystem.domain.exception; public class TenantNotFoundDomainException extends RuntimeException { private static final long serialVersionUID = 1L; private final TenantId tenantId; public TenantNotFoundDomainException(final TenantId tenantId) { super("No tenant found for TenantId: "+tenantId.toString()); this.tenantId = tenantId; } public TenantId getTenantId() { return tenantId; } } </code></pre> <p>A specific Exception should be created specifically for the Domain, which is then caught in the Use Case. The purpose of doing this is to decouple the Domain from the outside world. To put it simply, even if we delete the Application and Infrastructure parts, the IDE will not report errors, and the compilation will pass. This is what we know as <strong>dependency</strong> management—the Domain should not depend on the external world.</p> <p>Next, we expand outward to create the external capability needed by the Use Case, namely <code>TenantDirectory</code>. It can pass the boolean value of <code>exists</code> into the internal system, and finally hand it over to the Domain for judgment. To do this, we need to define an Outbound Port in the Application. This Port should be defined by the internal Application, not the external one.</p> <pre><code>package com.example.modulesystem.application.port.outbound; public interface TenantDirectoryPort { boolean tenantExists(TenantId tenantId); } </code></pre> <p>The implemented Adapter is located in the outermost Infrastructure layer. It depends on the Port defined internally and imports external libraries, working together to implement the functions required internally.</p> <pre><code>package com.example.modulesystem.infrastructure.adatper.outbound; public class TenantDirectoryAdapter implements TenantDirectoryPort { private final TenantDirectory tenantDirectory; public TenantDirectoryAdapter(final TenantDirectory tenantDirectory) { this.tenantDirectory = tenantDirectory; } @Override public boolean tenantExists(final TenantId tenantId) { // Simulating technical implementation details try (var rc = InternalDriver.controlRouting( InternalDriver.READ_ONLY, getClass().getName())) { return tenantDirectory.tenantExists(tenantId); } } } </code></pre> <p>Looking back at the previous code, we find there is also a logging logic: <code>log.warn("No tenant found for id '{}'", tenantId);</code>. Where should it be placed? Since the Domain only expresses core rules (i.e., failure if rules are not met) and does not care about specific recording, logging can be handed over to the Use Case or Infrastructure.</p> <p>Here, I understand it as recording the state of the external system, so I add <code>log.debug("Checked tenant existence for ID {}: {}", tenantId, exists);</code> in the Infrastructure as a technical log.</p> <p>Or you can choose to add it to the Use Case.</p> <p>The complete code for this step in the Use Case is:</p> <pre><code>// Step 2 UseCase final boolean exists = tenantDirectoryPort.tenantExists(tenantId); try{ tenantValidator.guardTenantExists(tenantId, exists); } catch (final TenantNotFoundDomainException ex) { log.warn("No tenant found for id '{}'", tenantId); throw ModuleStatusApiException.unknownTenant(tenantId); } </code></pre> <h3>Step 3</h3> <p>First, look at the original code, which is divided into two parts: first get <code>RegistryModule</code>, and then call <code>guardModuleExists</code> to compare with the input parameters.</p> <pre><code>final List&lt;RegistryModule&gt; registryModules = lookupModulesForRegistry(tenantId, getContext()); guardModuleExists(moduleStatusModel, registryModules); </code></pre> <p>Note that we have introduced the external model <code>RegistryModule</code>. To avoid introducing information irrelevant to the Domain, or to avoid "polluting" the system, we first need to define a data type exclusive to the Domain.</p> <pre><code>package com.example.modulesystem.domain.model; public record EnterpriseModuleStatus(@NonNull ModuleId moduleId, @NonNull boolean isEnterprise) { } </code></pre> <p>Then we need a Mapper that can convert the external <code>RegistryModule</code> into the internal data model.</p> <p>I put it in <code>infrastructure.outbound</code> because it represents the conversion of external data to internal data, and this conversion happens at the edge of Infrastructure -&gt; Application. I interpret the "edge" as akin to a return position or a method input parameter.</p> <pre><code>package com.example.modulesystem.infrastructure.adatper.outbound.mapper; public class RegistryModuleToEnterpriseModuleStatusMapper { public static List&lt;EnterpriseModuleStatus&gt; toDomain(final List&lt;RegistryModule&gt; registryModules) { return registryModules .stream() .map(module -&gt; new EnterpriseModuleStatus( ModuleId.of(module.getModuleId()), hasEnterpriseTag(module) )).toList(); } private static boolean hasEnterpriseTag(final RegistryModule module) { return module.getTags().stream().anyMatch(tag -&gt; tag.equalsIgnoreCase(ModuleTag.IS_ENTERPRISE.value())); } } </code></pre> <p>With the internal data type and converter, we can create a Port in the Application's Outbound to get <code>EnterpriseModuleStatus</code> (or <code>RegistryModule</code>), specifically for Use Case usage. The implementation of this Port, because it is not the focus of the Application, can be dumped entirely into the Infrastructure Adapter.</p> <p>This is what is often called Dependency Inversion: high-level modules do not depend on the external implementation's Infrastructure but only on abstract Ports, whereas the Infrastructure must depend on the Ports.</p> <p>First, we define a Port:</p> <pre><code>package com.example.modulesystem.application.port.outbound; public interface RegistryModulePort { List&lt;EnterpriseModuleStatus&gt; findByTenantId(TenantId tenantId); } </code></pre> <p>Then, the Adapter implementing this Interface is created. Most of the code can be copied directly. You don't have to read the full details, but take a look at the conversion part at the end of the method, which converts the external <code>RegistryModule</code> into <code>EnterpriseModuleStatus</code>.</p> <pre><code>package com.example.modulesystem.infrastructure.adatper.outbound; @Slf4j public class RegistryModuleAdapter implements RegistryModulePort { private final EventService eventService; private final RegistryLookup registryLookup; private final RegistryModuleService registryModuleService; public RegistryModuleAdapter( final EventService eventService, final RegistryLookup registryLookup, final RegistryModuleService registryModuleService){ this.eventService = eventService; this.registryLookup = registryLookup; this.registryModuleService = registryModuleService; } // Omitting specific context building methods... @Override public List&lt;EnterpriseModuleStatus&gt; findByTenantId(final TenantId tenantId) { // Simulating context final var context = new Object(); log.debug("Fetching RegistryModules for TenantId={}", tenantId); final List&lt;RegistryInfo&gt; registries = registryLookup.getRegistriesFor(tenantId, null, context); if (registries.isEmpty()) { return Collections.emptyList(); } final List&lt;RegistryModule&gt; associatedModules = new ArrayList&lt;&gt;(); for (final RegistryInfo registry : registries) { final Optional&lt;RegistryModules&gt; modulesFromCache = Optional.ofNullable( registryModuleService.findUnfilteredByRegistryIdCached(registry.getRegistryId())); final RegistryModules modules = modulesFromCache.orElseGet( () -&gt; registryModuleService.findUnfilteredByRegistryId(registry.getRegistryId())); if (modules != null) { associatedModules.addAll(modules.getAssociatedModules()); } else { log.debug("No Modules for registry '{}' found", registry.getRegistryId()); } } return RegistryModuleToEnterpriseModuleStatusMapper.toDomain(associatedModules); } } </code></pre> <p>With the Port and the actual Adapter, we can finally start building the logic for the Use Case and Domain. The core logic is: we need to ensure that the input modules match the existing modules. If a module does not exist at all, an error is thrown directly.</p> <pre><code>package com.example.modulesystem.domain.service; public class ModuleGuard { public void guardModuleExists( final TenantModuleStatuses tenantModuleStatuses, final Collection&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses) { final List&lt;String&gt; moduleIds = enterpriseModuleStatuses.stream() .map(moduleIsEnterprise -&gt; moduleIsEnterprise.moduleId().toString()) .toList(); final List&lt;String&gt; missingIds = tenantModuleStatuses.tenantModuleStatus().stream().map( dfs -&gt; dfs.moduleId().toString().toLowerCase() ).filter(dfs -&gt; !moduleIds.contains(dfs)).toList(); if(moduleIds.isEmpty() || !missingIds.isEmpty()) { throw new ModuleNotFoundDomainException(tenantModuleStatuses.tenantId(), missingIds); } } } </code></pre> <p>Finally, the Use Case utilizes this logic:</p> <pre><code>final List&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses = registryModulePort.findByTenantId(tenantId); try { moduleGuard.guardModuleExists(tenantModuleStatuses, enterpriseModuleStatuses); } catch (final ModuleNotFoundDomainException ex) { throw ModuleStatusApiException.noModuleForRegistryFound(tenantId, ex.getMissingModuleIds().toString()); } </code></pre> <p>Similarly, for Exceptions, simply define them in the layer they belong to:</p> <ul> <li><code>ModuleNotFoundDomainException</code> is located in <code>domain.exception</code>.</li> <li><code>ModuleStatusApiException</code> is located in <code>application.exception</code>.</li> </ul> <p>To summarize the process of Step 3:</p> <ol> <li>Define Domain Models and Mappers to ensure correct data format.</li> <li>Define Ports and Adapters to ensure external data can enter.</li> <li>Prepare Domain logic and add it to the Use Case.</li> </ol> <h3>Step 4</h3> <p>In this step, we need to determine if the module category belongs to "Enterprise". We can see that the original code relied heavily on external data structures:</p> <pre><code>private static void guardModuleIsEnterprise( final TenantModuleStatusesModel moduleStatusModel, final Collection&lt;RegistryModule&gt; registryModules) { final List&lt;RegistryModule&gt; enterpriseModules = registryModules.stream() .filter(cf -&gt; cf.getTags().stream() .anyMatch(tag -&gt; tag.equalsIgnoreCase(ModuleTag.IS_ENTERPRISE.value()))) .toList();; // Filter Request for any non-Enterprise modules final List&lt;String&gt; nonEnterpriseModules = moduleStatusModel.moduleStatusModel() .stream() .map(fs -&gt; fs.moduleId().toString().toLowerCase()) .filter(fid -&gt; enterpriseModules.stream() .map(pf -&gt; pf.getModuleId().toLowerCase()) .noneMatch(fid::equals)) .toList(); if (!nonEnterpriseModules.isEmpty()) { final TenantId tenantId = moduleStatusModel.tenantId(); throw ModuleStatusApiException.nonEnterpriseModuleProvided(tenantId, nonEnterpriseModules); } } </code></pre> <p>Thanks to the Domain data model <code>EnterpriseModuleStatus</code> we built in advance in Step 3, the logic for this step can be significantly simplified, and the structure is very clear.</p> <pre><code>package com.example.modulesystem.domain.service; public class EnterpriseModuleGuard { public static void guardModuleIsEnterprise( final TenantModuleStatuses tenantModuleStatuses, final Collection&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses) { final List&lt;String&gt; enterpriseModules = enterpriseModuleStatuses .stream() .filter(EnterpriseModuleStatus::isEnterprise) .map(module -&gt; module.moduleId().toString()) .toList(); final List&lt;String&gt; nonEnterpriseModules = tenantModuleStatuses .tenantModuleStatus() .stream() .map(dfs -&gt; dfs.moduleId().toString().toLowerCase()) .filter(fid -&gt; enterpriseModuleStatuses.stream() .map(pfs -&gt; pfs.moduleId().toString()) .noneMatch(fid::equals)).toList(); if (!nonEnterpriseModules.isEmpty()) { final TenantId tenantId = tenantModuleStatuses.tenantId(); throw new EnterpriseModuleNotFoundDomainException(tenantId, nonEnterpriseModules); } } } </code></pre> <h3>Step 5</h3> <p>After the input data is checked and confirmed to be correct, we only need a simple conversion to prepare it for storage in the system database. Since this is a data conversion internal to the Domain, we place the Mapper directly inside the Domain. We just create a new Mapper and wait for the subsequent Use Case to call it.</p> <pre><code>package com.example.modulesystem.domain.mapper; public class EnterpriseModuleToStatusMapper { public TenantModuleStatuses toTenantModuleStatuses( final TenantModuleStatuses tenantModuleStatuses, final Collection&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses ) { final Map&lt;String, String&gt; mappedModuleIds = enterpriseModuleStatuses .stream() .collect(Collectors.toMap( module -&gt; module.moduleId().toString().toLowerCase(), module -&gt; module.moduleId().toString(), (existingValue, newValue) -&gt; existingValue)); final List&lt;TenantModuleStatus&gt; correctlyCapitalizedModuleId = tenantModuleStatuses.tenantModuleStatus() .stream() .map(dfs -&gt; { final String moduleId = mappedModuleIds.get(dfs.moduleId().toString().toLowerCase()); return new TenantModuleStatus(ModuleId.of(moduleId), dfs.isActive()); }).toList(); return new TenantModuleStatuses(tenantModuleStatuses.tenantId(), correctlyCapitalizedModuleId); } } </code></pre> <p>In the Use Case, convert and store.</p> <pre><code>final boolean statusHasChanged = moduleStatusRepositoryPort.save(enterpriseModuleToStatusMapper.toTenantModuleStatuses(tenantModuleStatuses,enterpriseModuleStatuses)); </code></pre> <h3>Step 6</h3> <p>The final step is simple: trigger a method in the external system to invalidate the Cache. Similar to Step 2, we only need to create a Port and an Adapter.</p> <pre><code>package com.example.modulesystem.application.port.outbound; public interface EventServicePort { void invalidateModuleCacheForTenants(List&lt;String&gt; tenantIds); } </code></pre> <pre><code>package com.example.modulesystem.infrastructure.adatper.outbound; public class EventServiceAdapter implements EventServicePort { private final EventService eventService; public EventServiceAdapter(final EventService eventService) { this.eventService = eventService; } @Override public void invalidateModuleCacheForTenants(final List&lt;String&gt; tenantIds) { eventService.invalidateCacheForTenants(tenantIds); } } </code></pre> <h3>Consolidation and Organization</h3> <p>Finally, integrating all steps, we get the final Use Case.</p> <pre><code>package com.example.modulesystem.application.usecase; @Slf4j public class UpdateModuleStatus { // outbound port private final TenantDirectoryPort tenantDirectoryPort; private final RegistryModulePort registryModulePort; private final ModuleStatusRepositoryPort moduleStatusRepositoryPort; private final EventServicePort eventServicePort; // domain service private final TenantValidator tenantValidator; private final ModuleGuard moduleGuard; private final EnterpriseModuleGuard enterpriseModuleGuard; private final EnterpriseModuleToStatusMapper enterpriseModuleToStatusMapper; public UpdateModuleStatus(final TenantDirectoryPort tenantDirectoryPort, final RegistryModulePort registryModulePort, final ModuleStatusRepositoryPort moduleStatusRepositoryPort, final EventServicePort eventServicePort, final TenantValidator tenantValidator, final ModuleGuard moduleGuard, final EnterpriseModuleGuard enterpriseModuleGuard, final EnterpriseModuleToStatusMapper enterpriseModuleToStatusMapper) { this.tenantDirectoryPort = tenantDirectoryPort; this.registryModulePort = registryModulePort; this.moduleStatusRepositoryPort = moduleStatusRepositoryPort; this.eventServicePort = eventServicePort; this.tenantValidator = tenantValidator; this.moduleGuard = moduleGuard; this.enterpriseModuleGuard = enterpriseModuleGuard; this.enterpriseModuleToStatusMapper = enterpriseModuleToStatusMapper; } public void handle(final TenantModuleStatuses tenantModuleStatuses) { final TenantId tenantId = tenantModuleStatuses.tenantId(); // Step 2 final boolean exists = tenantDirectoryPort.tenantExists(tenantId); try { tenantValidator.guardTenantExists(tenantId, exists); } catch (final TenantNotFoundDomainException ex) { log.warn("No tenant found for id '{}'", tenantId); throw ModuleStatusApiException.unknownTenant(tenantId); } // Step 3 final List&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses = registryModulePort.findByTenantId(tenantId); try { moduleGuard.guardModuleExists(tenantModuleStatuses, enterpriseModuleStatuses); } catch (final ModuleNotFoundDomainException ex) { throw ModuleStatusApiException.noModuleForRegistryFound(tenantId, ex.getMissingModuleIds().toString()); } // Step 4. try { enterpriseModuleGuard.guardModuleIsEnterprise(tenantModuleStatuses, enterpriseModuleStatuses); } catch (final ModuleNotFoundDomainException ex) { throw ModuleStatusApiException.nonEnterpriseModuleProvided(tenantId, ex.getMissingModuleIds()); } // Step. 5 final boolean statusHasChanged = moduleStatusRepositoryPort.save( enterpriseModuleToStatusMapper.toTenantModuleStatuses(tenantModuleStatuses,enterpriseModuleStatuses)); if (statusHasChanged) { log.debug("Invalidating ModuleCache for TenantId={}", tenantId); eventServicePort.invalidateModuleCacheForTenants(List.of(tenantId.toString())); } } } </code></pre> <p>Of course, this isn't the most concise form; we should simplify it further.</p> <p>First, consolidate the Catch blocks. Second, we can assume that all Domain Service boundaries are within <code>ModuleStatusService</code>. After this organization, something magical happens—it turns out to be very close to the logic we saw initially! And in appearance, it looks very much like the classic DDD model that relies on a large Service.</p> <p>The simplified code is as follows:</p> <pre><code>package com.example.modulesystem.application.usecase; @Slf4j public class UpdateModuleStatus { // outbound port private final TenantDirectoryPort tenantDirectoryPort; private final RegistryModulePort registryModulePort; private final ModuleStatusRepositoryPort moduleStatusRepositoryPort; private final EventServicePort eventServicePort; // domain service private final ModuleStatusService moduleStatusService; public UpdateModuleStatus(final TenantDirectoryPort tenantDirectoryPort, final RegistryModulePort registryModulePort, final ModuleStatusRepositoryPort moduleStatusRepositoryPort, final EventServicePort eventServicePort, final ModuleStatusService moduleStatusService ) { this.tenantDirectoryPort = tenantDirectoryPort; this.registryModulePort = registryModulePort; this.moduleStatusRepositoryPort = moduleStatusRepositoryPort; this.eventServicePort = eventServicePort; this.moduleStatusService = moduleStatusService; } public void handle(final TenantModuleStatuses tenantModuleStatuses) { final TenantId tenantId = tenantModuleStatuses.tenantId(); final boolean exists = tenantDirectoryPort.tenantExists(tenantId); try { moduleStatusService.guardTenantExists(tenantId, exists); final List&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses = registryModulePort.findByTenantId(tenantId); moduleStatusService.guardModuleExists(tenantModuleStatuses, enterpriseModuleStatuses); moduleStatusService.guardModuleIsEnterprise(tenantModuleStatuses, enterpriseModuleStatuses); final boolean statusHasChanged = moduleStatusRepositoryPort.save( moduleStatusService.toTenantModuleStatuses(tenantModuleStatuses, enterpriseModuleStatuses)); if (statusHasChanged) { log.debug("Invalidating ModuleCache for TenantId={}", tenantId); eventServicePort.invalidateModuleCacheForTenants(List.of(tenantId.toString())); } } catch (final TenantNotFoundDomainException ex) { log.warn("No tenant found for id '{}'", tenantId); throw ModuleStatusApiException.unknownTenant(tenantId); } catch (final ModuleNotFoundDomainException ex) { throw ModuleStatusApiException.noModuleForRegistryFound(tenantId, ex.getMissingModuleIds().toString()); } catch (final EnterpriseModuleNotFoundDomainException ex) { throw ModuleStatusApiException.nonEnterpriseModuleProvided(tenantId, ex.getMissingModuleIds()); } } } </code></pre> <p>Finally, I also noticed that the flow of this Use Case is very close to the Acceptance Criteria in the Jira Ticket. Understood from another angle, the Ticket can be an abstraction of the Implementation, with the latter depending on the former.</p> <h2>Invoking Use Cases</h2> <p>In the above Use Cases, we assume there are two ways to call them: one using a Java Interface, and the other using a Resource (i.e., HTTP Request).</p> <p>The Java Interface is the simplest; you just need to expose the interface of the Use Case class, located in <code>application.port.inbound</code>.</p> <pre><code>package com.example.modulesystem.application.port.inbound; public interface ModuleStatusQueryPort { TenantModuleStatuses apply(@NonNull final TenantId tenantId); } </code></pre> <p>The second way is slightly more complex. HTTP requests belong to a technical implementation, so they can be completely placed in the Inbound Adapter of the Infrastructure. It <strong>can directly</strong> call the implementation in the Use Case because its responsibility is to handle boundary data conversion and connect internal systems.</p> <p>The code is as follows:</p> <pre><code>package com.example.modulesystem.infrastructure.adatper.inbound.rest; @Slf4j @Path("/") @Component public class EnterpriseModuleStatusResource { private final UpdateModuleStatus updateModuleStatus; public EnterpriseModuleStatusResource(final UpdateModuleStatus updateModuleStatus) { this.updateModuleStatus = updateModuleStatus; } @PUT @Path("tenants/{tenantId}/provisioned") @Consumes({MediaType.APPLICATION_JSON}) public Response updateModuleStatusForTenants( @PathParam("tenantId") final TenantId tenantId, @Valid final TenantModuleStatusesRequest subscriptionStatusRequest) { log.info("Updating module statuses for TenantId: {}", tenantId); updateModuleStatus.handle(mapToDomainModel(tenantId, subscriptionStatusRequest.modules())); log.debug("Successfully updated module statuses"); return Response.ok().build(); } private static TenantModuleStatuses mapToDomainModel(final TenantId tenantId, final Map&lt;String, Boolean&gt; moduleStatusList) { final List&lt;TenantModuleStatus&gt; tenantModuleStatusModels = new ArrayList&lt;&gt;(); moduleStatusList.forEach((moduleId, isActive) -&gt; tenantModuleStatusModels.add(new TenantModuleStatus(ModuleId.of(moduleId), isActive))); return new TenantModuleStatuses(tenantId, tenantModuleStatusModels); } } </code></pre> <p>Here, the Mapper is an optional split; it can be placed in the Resource or a separate Mapper can be created.</p> <p>It is worth noting that <code>TenantModuleStatusesRequest</code>, as a protocol with the outside world (HTTP), should not be placed in Application or Domain because the technology adopted by the Adapter may change (for example, from HTTP to Kafka), and the Use Case should not be affected in any way.</p> <pre><code>package com.example.modulesystem.infrastructure.adatper.inbound.rest.dto; public record TenantModuleStatusesRequest(Map&lt;String, Boolean&gt; modules) { } </code></pre> <p>Here is the relevant folder structure:</p> <pre><code>└── infrastructure └── adatper ├── inbound │ └── rest │ ├── EnterpriseModuleStatusResource.java -&gt; resource │ └── dto │ └── TenantModuleStatusesRequest.java -&gt; DTO └── outbound ├── mapper │ └── RegistryModuleToEnterpriseModuleStatusMapper.java ├── RegistryModuleAdapter.java ├── EventServiceAdapter.java └── TenantDirectoryAdapter.java </code></pre> <h2>Conclusion and Coda</h2> <p>Let's review the map we built again:</p> <pre><code>. ├── application │ ├── exception │ │ └── ModuleStatusApiException.java │ ├── port │ │ ├── inbound │ │ │ └── ModuleStatusQueryPort.java │ │ └── outbound │ │ ├── ModuleStatusRepositoryPort.java │ │ ├── RegistryModulePort.java │ │ ├── EventServicePort.java │ │ └── TenantDirectoryPort.java │ └── usecase │ ├── ModuleStatusQueryUseCase.java │ ├── ResetModuleStatusUseCase.java │ └── UpdateModuleStatus.java ├── domain │ ├── exception │ │ ├── EnterpriseModuleNotFoundDomainException.java │ │ ├── ModuleNotFoundDomainException.java │ │ └── TenantNotFoundDomainException.java │ ├── model │ │ ├── EnterpriseModuleStatus.java │ │ ├── TenantModuleStatuses.java │ │ └── TenantModuleStatus.java │ └── service │ └── ModuleStatusService.java └── infrastructure └── adapter ├── inbound │ └── rest │ ├── EnterpriseModuleStatusResource.java │ └── dto │ └── TenantModuleStatusesRequest.java └── outbound ├── mapper │ └── RegistryModuleToEnterpriseModuleStatusMapper.java ├── RegistryModuleAdapter.java ├── EventServiceAdapter.java └── TenantDirectoryAdapter.java </code></pre> <p>We started from the core Domain data structure design, and in conjunction with the Repository Port, designed two simple Use Cases. Then, we delved into how to handle the most complex Case, including introducing external data, data conversion at the boundary and inside the Domain, how to handle Exceptions, and how to trigger Use Cases in different ways.</p> <p>Of course, this is just a review of this simple example, and the situations encountered in actual development are often much more complicated. What I want to say to you, and equally to myself, is: This is definitely not the perfect solution, nor is it a universal architecture suitable for all situations. The complicated data type conversions and over-engineering problems remain unsolved and have even intensified, as can be quickly understood by looking at the examples above.</p> <p>Currently, I interpret Hexagonal/Clean Architecture as a refinement of classic DDD. It retains the core of a pure Domain, the difference being that it delegates part of the functions from the large, all-encompassing Service to the Use Case. (Personally, I feel this trend is somewhat like a shift from being centered on functional technical development to being centered on quickly adapting to different user needs).</p> <p>Using the characteristics of the Interface, we achieved Dependency Inversion, and the dependency path became <strong>Infrastructure -&gt; Application -&gt; Domain</strong>. Before, I only focused on the fact that an Interface could be Implemented, without focusing on its characteristic of being able to define variable types. It is precisely because of this difference in focus that control has quietly shifted.</p> <p>As for the specific project, it will continue to be very large and ancient, and it is almost difficult to change the structure of the original code. Old-style Services will persist for a long time, and the new structure will be slow to appear, but its existence will help me establish a clear architectural awareness in my mind and help me organize and categorize legacy logic.</p> <blockquote> <p>Disclaimer: The code examples provided are simplified and generalized for educational purposes, focusing on architectural patterns rather than specific project implementation.</p> </blockquote> What I Lost and Gained from Pulling My Wisdom Teethhttps://blog.kaiyikang.com/posts/2026/what-i-lost-and-gained-from-pulling-my-wisdom-teeth/https://blog.kaiyikang.com/posts/2026/what-i-lost-and-gained-from-pulling-my-wisdom-teeth/Sat, 07 Feb 2026 00:00:00 GMT<p><img src="@assets/2026/202602_wisdom_teeth.webp" alt="" /></p> <p>Bright red blood kept pouring out of my mouth.</p> <p>I had never experienced anything like this outside of movies and television shows. I gathered my senses and leaned back. I used a little force to clench my teeth together and swallowed the bloody water while fighting back the metallic taste. After a while the bleeding stopped and the wound gradually settled down.</p> <p>I had struggled with this for years. Urged by various dentists, I finally gathered the courage to get my wisdom teeth extracted. The custom in Germany is to pull all four at once. I decided to do as the Romans do to avoid more trouble later.</p> <p>The dentist was very kind and full of encouraging words but was also a bit dismissive at times. For example, he gave no recovery advice after the surgery. Fortunately the extraction went fairly smoothly. Only the bottom left tooth presented a slight challenge. I heard plenty of electric saw noises and smelled something burning. I did not feel a thing due to the anesthesia but my mind kept picturing a construction zone inside my mouth. After the extraction, I mumbled numbly through the follow up instructions with the medical staff and quickly took the bus home.</p> <p>The wound was at its worst that night, which led to the scene at the very beginning.</p> <p>Bloody water covered the trash bag and the sink. I even touched something that felt like blood pudding. It is called a blood clot and it is very important for closing the wound. For the next few hours, I did not dare to look down or open my mouth. I just leaned back on my cushions with my mouth shut and swallowed a mysterious fluid with a very complex taste and texture.</p> <p>It sounds terrifying to describe, but the most fearful time was actually the few days before the extraction. The fear of the unknown was fueled by posts on social media. My heart rate would spike just thinking about it even while walking down the street in broad daylight. I learned that you should avoid looking at apps when facing something vague and uncertain. Situations vary drastically and their experiences might not happen to me. The course of events is usually determined by specific details. Finding solutions based on your exact situation is the most appropriate approach.</p> <p>I also discovered that short videos truly act like painkillers during physical recovery. As I kept scrolling, my attention completely shifted to the phone screen. I could no longer feel the wound. I realized that my daily habit of scrolling through short videos borderline resembled substance abuse. This was not right and I needed to change.</p> <p>So I put down my phone and turned to reading.</p> <p>The book was about the relationship between money, life, and the self. It urges people to understand themselves clearly, to recognize the drain of their life energy, and to be fully aware of the flow of money around them. Aside from abstract discussions, it offered plenty of practical advice. Most of these were long term practices that can only unfold over time.</p> <p>Before putting them into long term practice, I took the ideas from the book to the supermarket at my doorstep to buy a few things.</p> <p>When my teeth are fine, I want to buy a bit of everything I see regardless of whether I will actually eat it later. This often results in things rotting in my fridge and cabinets or even growing insect eggs. That always fills me with guilt. But this time was special because my bad teeth severely limited my choices. I walked straight to the juice section and grabbed a bottle of clear apple juice without even glancing at the cloudy orange juice. Then I grabbed a soft piece of bread and immediately turned toward the checkout counter. There was no sweetened iced coffee, no potato chips, and no meat. I only got exactly what I needed to supplement my essential nutrition during this special period.</p> <p>It restricted my capabilities and forcibly narrowed my choices. At the moment of checkout, my eyes and mind became clear and clean. I gained a precious feeling of knowing what I truly needed. Before this, I was surrounded by a dazzling world and I was a fully capable man. This endless array of choices left me confused and dizzy. Although I will eventually recover my healthy teeth, at least this time I have returned with the awareness to identify my true needs.</p> <p>This physical limitation turned out to be a great practice session. It forcibly filtered out unnecessary drains on my life energy and gave me an intuitive sense of where my money was going.</p> <p>This can be considered a benefit of tooth extraction.</p> <p>After resting at home for a few days, I finally felt like going out for a walk. It happened to coincide with a friend's graduation exhibition, so I planned to go support him. I offered coffee and congratulations. We chatted casually in the hallway for a while. Seeing that he still needed to socialize with others, I took the opportunity to check out the other exhibition halls.</p> <p>Although the exhibition formats change, they happen every year and I had been there four or five times before. Stepping into the hall at that moment, I suddenly remembered my past self. I was just like a country bumpkin in the big city. I understood nothing but marveled at everything and took pictures of whatever I saw.</p> <p>Over time, this behavior became a habit that pushed me to marvel at everything I encountered regardless of whether I genuinely liked it or not. Choosing everything is equal to choosing nothing. Wanting to miss nothing also means you do not know what you want. The curse of "I am already here anyway" echoed in my ears. The old me acted like I had obsessive compulsive disorder. I refused to miss a single room or hall and just wandered for the sake of wandering.</p> <p>One way the self expresses itself is through its unique choices.</p> <p>This time I did not cling to the novelty of the exhibition halls nor did I feel regret over missing anything. After strolling through a few rooms and taking two or three pictures of paintings featuring the sky, I briefly said goodbye to my friend and turned to leave.</p> <p>Back home, I continued to heal and thought about what I truly wanted. I thought about going to exercise, eating delicious food, and doing the things I genuinely want to do once my teeth are fully healed.</p> Bloghttps://blog.kaiyikang.com/posts/labs/personal-blog/https://blog.kaiyikang.com/posts/labs/personal-blog/Technical and Feature Overview of This Blog.Tue, 26 Dec 2023 00:00:00 GMT<p>import Callout from "@components/mdx/Callout.astro";</p> <h2>Brown Noise Player (2026.01.26)</h2> <p>I really enjoy listening to noise because it helps me feel calm and focused. I have a particular fondness for brown noise and decided to create a player for it within my own digital space.</p> <p>The project is now complete and you can click the play button at the top of the page to listen. Please rest assured that the sound is very gentle and it will not startle you or disturb your environment.</p> <p>Regarding the implementation I initially considered using audio files but they were too large and would occupy significant storage space. Because of this I chose to generate the noise using JavaScript functions instead.</p> <p>The general principle is to first generate random white noise and I used a dual channel design for this purpose.</p> <pre><code>for (let i = 0; i &lt; bufferSize; i++) { // Fill the left and right channels with independent random numbers // This utilizes binaural decorrelation to make the sound feel incredibly wide instead of being focused in the center of the head leftChannel[i] = Math.random() * 2 - 1; rightChannel[i] = Math.random() * 2 - 1; } </code></pre> <p>Brown noise is characterized by its rich low frequencies so I applied a Lowpass Filter to remove the high frequency components. This approach simulates the auditory experience even though it is not a mathematically perfect brown noise where the power density decreases by .</p> <pre><code>const filter = state.ctx.createBiquadFilter(); filter.type = "lowpass"; filter.frequency.value = 220; // Maintain a deep feeling at 220Hz </code></pre> <p>To prevent any sudden clicking sounds I implemented a linear ramp to create a smooth fade in effect.</p> <pre><code>state.gainNode.gain.setValueAtTime(0, state.ctx.currentTime); state.gainNode.gain.linearRampToValueAtTime(0.3, state.ctx.currentTime + 0.8); </code></pre> <h2>MDX (2025.01.14)</h2> <p>I wanted to provide more flexible and expressive formatting options for articles, so I added support for the <code>mdx</code> format.</p> <p>&lt;Callout type="tip" title="Why MDX?"&gt; MDX allows me to embed React or Astro components directly within articles. I can showcase <strong>interactive charts</strong> and <strong>live code demos</strong>, rather than just dry code blocks. &lt;/Callout&gt;</p> <p>I will have the opportunity to showcase more content, similar to:</p> <p>&lt;Callout type="info" title="New Content"&gt;&lt;/Callout&gt;</p> <p>Instead of</p> <pre><code>&lt;Callout type="info" title="New Content" /&gt; </code></pre> 像猫一样思考https://blog.kaiyikang.com/posts/2017/%E5%83%8F%E7%8C%AB%E4%B8%80%E6%A0%B7%E6%80%9D%E8%80%83/https://blog.kaiyikang.com/posts/2017/%E5%83%8F%E7%8C%AB%E4%B8%80%E6%A0%B7%E6%80%9D%E8%80%83/Sat, 13 May 2017 00:00:00 GMT<p>我曾经问过同学,说,人能不能像猫一样思考。她和我讲,人怎么知道猫是怎么想的?也许我们永远无法知道猫在想什么,但我们还是可以去尝试「感受」它在想什么。这种感受从结果上来说,是大致相同的,但是如何能够获得感受的能力确实因人而异的,有的人可能看到某些语句就可以获得极大的共鸣,而有的人即使是长时间的耳濡目染,也很难获得这样的体会。</p> <p>《当下的力量》这本书,如今已经摆在了心灵励志或者是情感的分类货架上的。那时刚上大学的时候看到了这本书,就已经受到了一些震撼,几年过去了,书中的某些语句,例如「像树木和花草一样思考」,「人的思维大多只是冰山上的那一部分」等等,到了至今还时不时的会回忆起来。</p> <p>那个时候的我,也没有有关的知识,读这些书只是觉得内心很舒坦,而其中的语句也可以让焦躁或者恶劣的心情平静下来。不过至于原理和原因,书中却没有过多的描述,而因为本人是个工科生,本着求实的态度,所以对此还有些耿耿于怀。</p> <p>在《改善情绪的正念疗法》一书中,我倒是发现了许多有实验结论做支撑的理论依据。</p> 「不完美」也是「完美」https://blog.kaiyikang.com/posts/2018/%E4%B8%8D%E5%AE%8C%E7%BE%8E%E4%B9%9F%E6%98%AF%E5%AE%8C%E7%BE%8E/https://blog.kaiyikang.com/posts/2018/%E4%B8%8D%E5%AE%8C%E7%BE%8E%E4%B9%9F%E6%98%AF%E5%AE%8C%E7%BE%8E/Sat, 08 Sep 2018 00:00:00 GMT<p>最近有幸通过网络,我接触到了两种人:看似完美,但有所瑕疵的人;看上去完美的人。在经过了比较与思考后,我有了种感触:不完美其实是一种完美。</p> <p>倘若单纯从语言文字的角度出发,这句话其实是矛盾的。但如果把「完美」看做成一种「和谐」,那么理解起来就更加的顺畅。</p> <p>从数学角度讲,我们可以将零和一,视作完美,但当我们想到黄金比例0.618的时候,也许会显得不知所措,因为这个数相比起零和一,更加和谐与自然。</p> <p>完美,可以说成是「走极端」。而「不完美」,反而有种妥协的含义在里面。</p> <p>艺术领域更是如此。走极端的艺术家有很多,例如「蒙德里安 」的框框画,即只有横平竖直的线条,以及纯粹色块的填充。又比如草间弥生,纯黄的背景,只有密集黑色半点的填充。「杰克森·波洛克」肆意的在帆布上泼洒颜料,将作品「No.5 1948」拍卖出了1.65亿美元。</p> <p>我并不会去说,追求极致和完美是不好的。但如果我们想到文艺复兴时期的作品,就以最熟悉的「蒙娜丽莎」为例。经过精雕细琢的黄金比例,却会让我们感到如此的和谐,以至于成为了数百年的经典。</p> <p>当我在网络上去搜索大众对于不完美的解读时,「侘寂美学(わび・さび)」映入了眼帘。这是我最喜欢的美学风格之一。</p> <p>这种日本式的美学,同样也式日本式的精神向往。「它包含了對於不完美這件事的智慧,以及看見不完美之事物的美感的能耐與態度。 」(来源:pinsoul)</p> <p>因为我们已经洞察到,这个世界无论怎么样都是无法完满的了,即,世界是无法完美的。</p> <p>有能力欣赏完美的事物,是非常简单的。但去欣赏不完美的事物,则需要一种别样的智慧。这也是大多数人会抱怨生活中的不顺利,慨叹人生时运不济的原因。</p> <p>在工程上,完美绝不意味着一台机器满功率负载,以达到最大的输出。完美的意义而是要去寻找一种和谐与稳定。工程师需要在机器性能,使用时长,使用的简便性等等方面,做出各种各样的取舍。过度偏向任何一方,都会导致整个系统的崩溃。这对于设计机器和使用机器的人来说,都是不愿意看到的。</p> <p>当然,倘若我们不去追求完美,并不意味着没有追求。事实上,追求「不完美」反而更难。</p> <p>从我们最常见的自我认知方面讲,我们都愿意和完美的人或事物作伴,有时对于不完美的人或事物是持有否定态度的。发自于心底深处的潜意识,似乎不经意间就可以左右我们的意志。等当我们回过神来,发现早已成为那种偏执的,会因为追求不到完美而感到失落的人了。</p> <p>正如上文所言,如何将不完美看作是完美,如何以追求和谐作为最终目标,都是需要智慧的。</p> <p>回到开头,我所提及的两种人。与不完美的人聊天,令我感到无比的喜悦。与看似完美的人接触,我会有种如坐针毡,小心翼翼的感觉。久而久之,心底变得负面起来,猜忌,妒忌,看不起油然而生。</p> <p>但这种负面的感受,难道不也是一种「追求完美而不得」的情况么。</p> <p>愿我们在获取智慧的道路上越走越远。</p> <p>感谢您的阅读。</p> 查理大桥https://blog.kaiyikang.com/posts/2018/%E6%9F%A5%E7%90%86%E5%A4%A7%E6%A1%A5/https://blog.kaiyikang.com/posts/2018/%E6%9F%A5%E7%90%86%E5%A4%A7%E6%A1%A5/Fri, 07 Sep 2018 00:00:00 GMT<p>查理大桥,横跨伏尔塔瓦河,距今约有650年的历史。</p> <p><img src="@assets/2018/p53772590.jpg" alt="白天的查理大桥" /></p> <p>大桥长约500米,前后盖有尖梯屋顶的桥塔。只需要100克朗,就可以登上桥塔,总览布拉格的风光。</p> <p>大桥两侧的26座巴洛克式雕像是17世纪末和18世纪初建成,而后拓展了4座,最终桥上共有30座雕像。</p> <p>雕像上,某些部分已经被游客摸得闪闪发亮,可见这些雕像都有一段似真似幻的传说或故事在背后。</p> <p><img src="@assets/2018/p53772596.jpg" alt="夜晚的查理大桥 其一" /></p> <p>我在尝试思考用合适的词汇,试图准确描述自己对于走在查理大桥上的感受。</p> <p>但我感到词穷,于是尝试去寻找作家或者诗人的评价。</p> <p>例如卡夫卡就曾经说过:「我的生命和灵感全部来自于伟大的查理大桥。」</p> <p>这句话的确很好,但它似乎并不是很切合我的感受。</p> <p>所以我不断思考,最后想到了一个很平凡的词汇:「恰当」</p> <p>当我走在桥上时,脚下的每一块砖都恰当的组合成我脚下的路。</p> <p>每一寸风都以恰当的角度和力道吹拂着我的皮肤。</p> <p>太阳和云,指挥着所有的光线,令它们在合适的位置得以汇聚。</p> <p>远处的山城,近处的河道,一丝一毫都恰如其分。</p> <p>颇为识趣的是,路边的街头艺人也携带着小提琴,大提琴和手鼓等乐器,又听觉增加了一份趣味。</p> <p>大桥仿佛活了起来。</p> <p>一切都是这么恰当和完美。所以我不愿意离开,只愿意在大桥上不停辗转。</p> <p><img src="@assets/2018/p53772610.jpg" alt="夜晚的查理大桥 其二" /></p> <p>有些地方,需要你去静候它的美。若气象不佳,下雨或大雾,就什么都看不到了。</p> <p>但是布拉格的夜晚不会,不论天象如何变化,它都会将最完美的姿态展现给你。这种豁达和包容,是其它地方不可企及的。</p> <p>我想,用言语是很难去描述这样的感受的,只好引用德国文豪歌德的话,「在那许许多多城市像宝石般镶成的王冠上,布拉格是其中最珍贵的一颗。」</p> 经验包豪斯(一)https://blog.kaiyikang.com/posts/2023/%E7%BB%8F%E9%AA%8C%E5%8C%85%E8%B1%AA%E6%96%AF1/https://blog.kaiyikang.com/posts/2023/%E7%BB%8F%E9%AA%8C%E5%8C%85%E8%B1%AA%E6%96%AF1/Sat, 15 Apr 2023 00:00:00 GMT<p><img src="@assets/2023/p93481129.jpg" alt="题图:Bauhaus 楼" /></p> <h2>理解与享受</h2> <p>让我们来聊聊包豪斯。</p> <p>如果使用德语「Bauhaus」搜索,你将得到的结果是离家最近的建材市场。这个词由「Bau」(建造),和「Haus」(房子)非常干燥地组合而成。它算是一个好词么?若从纯粹艺术性角度讲,并非如此,我们联想更多是建材市场,而不是综合的设计理念。如果放低视角,从更普遍的角度出发,我们却得到了实用的、功能的等更贴近大众生活的感受。</p> <p>我觉得它是个好词,因为它非常成功和彻底的完成了它的使命,即潜入到我们的现代生活,化作一团无形的概念空气,被吸收并理解,悄无声息环绕在我们的周围。</p> <p><img src="@assets/2023/p93481001.jpg" alt="从屋内到屋外,它围绕在我们周围" /></p> <p>我们应该对包豪斯有自然且直观的理解,毕竟我们对此早有经验。而正因为太过自然,就如同看电影被剧透一般,我早在出发前,就能大致地勾勒出游历的轮廓。</p> <p>当然我不会为此泄气,因为就个人经验来说,我忽然意识到,情绪和感情的触发,有时候需要理性的铺垫。浅显的例子是指弹吉他:通过清脆的节奏,温柔和华丽的曲调,人们可以感受到弹奏者在舞台上传递出了非常浓厚的感情,但实际上,当你去问这些弹奏者此刻正在想什么,答案绝对不会是爱情的喜悦,或失恋的悲伤等等充沛且抽象的情感。而是该小节的强弱如何,有什么特别需要注意的转折和准备,等一系列非常严肃的知识。我们也许会因知道真相而感到扫兴,但冰冷的知识确实是一系列情感的前提准备。</p> <p>回到旅行上,如果以过分纯真的心态出发,那一定的概率,会在以纯视觉和听觉的领域兜兜转转。它强调的不是五感上的直接震撼(可以震撼到一百年前的人,但现代人对此几乎无感),而是需要以知识做催化剂,将感受缓慢激发出来。</p> <p>由此,我不得不在旅行前,多阅读些文章,多看看相关纪录片,为的就是获得它在历史时间线上的位置,从而真正去抓住,它对我们的生活到底产生何种深刻的影响。唯有如此,当我们身临其境的时候,那些习以为常的设计与结构,才会闪耀着跳跃出来,激活大脑中的神经,使我们在精神的领域,获得深刻且无与伦比的愉悦。</p> <h2>干燥的介绍</h2> <p>包豪斯诞生于魏玛,本身是一个设计学校。它主张艺术和工艺制造应相互结合。这在当时来说是非常新颖的概念。后因政见等问题迁移至 Dessau,最后迫于 Nazi 压力被迫关闭。存续时间从 1919 到 1933 年,虽然寿命短暂,但正因为受到负面力量的压制,影响力却是极强的,最终得以广泛播散到全世界。</p> <p>从慕尼黑出发,到达 Luther Wittenberg 站,再转乘火车 RB ,就能到达 Dessau 主火车站。它不在高铁的主干线上,同时当地地广人稀,因此自驾过去会是一个更好的选择。</p> <p><img src="@assets/2023/p93481018.jpg" alt="迷你的火车站,没什么商店,也没什么人" /></p> <p>主火车站很袖珍,它的地下通道分成两个方向,一个朝向市中心,一个朝向 Bauhaus 楼。</p> <p>在前往目的地的途中,会途径 Hochschule Anhalt (安哈尔特应用技术大学)校区。这所学校是一个应用类技术学校,虽然以工程学为主,但仍包涵设计专业。这部分由 Bauhaus 负责,算是传承了当年的治学。</p> <p>包豪斯建筑,并不是指这座建筑本身,而是包含了教学楼以及附近『大师之家』(教授租住的样板房)的建筑群。</p> <p>朝着市中心方向走,则能找到四四方方的 Bauhaus 博物馆。为了纪念包豪斯成立 100 周年,它开放于 2019 年。</p> <h2>Bauhausgebäude Dessau</h2> <p><img src="@assets/2023/p93481143.jpg" alt="最经典的立面,正在维修" /></p> <p>我不知道如何用中文称呼这栋教学楼。如果还是直译,即『德绍的包豪斯建筑』,平平无奇,也没有什么特殊的风味。但转念一想,说不定毫无味道,也同样能是『一种味道』。</p> <p>黑白灰构建出的外立面,交叠穿插的方形几何,安静的在那里沉睡。中间一条柏油马路,将它一分为二,中间则靠悬置的通道连通。正门是大红色的,与冷淡的外墙格格不入,冷落在了马路的一侧。正门门檐的右上角,『Bauhaus』的字样告诉我们,这是包豪斯楼。</p> <p><img src="@assets/2023/p93480966.jpg" alt="红色的门,右对齐的字" /></p> <p>它勾起了我很多的对建筑的印象。</p> <p>比如被马路一分为二这点,让我想到了硕士读的慕尼黑工业大学的楼。每次我们要去上课或食堂,都要横穿一到马路,虽然车流不多,但间或停歇的琐碎,令人十分恼火。双推门的主入口,以及中间上行,两侧下行的楼梯,是回忆中高二的教学楼。而大片大片的透明玻璃,是我在咖啡店写作业时,一抬眼就能看到的景色。直至现在,我依旧喜欢类似的元素。</p> <p>我当下的经验是如此理所应当,但这种经验的源泉却来自于前一百多年,每当想到这里,便肃然奇境。它化身成为我对建筑经验的基底和抽象,而这样的化身,也是理念基因能切实被传承和散播的具象化体现。</p> <p><img src="@assets/2023/p93481036.jpg" alt="透明的玻璃和框架,是我喜欢的元素" /></p> <p>如果用一个词形容该体验,我想是『熟悉感』。不同于我们浸润在生活中,它更有力量和侵略性,主动向前与我们的现代生活发生关系。</p> <p>它强大的力量,并通过宗教式的伟大奇观展现,而是以顽固气雾的方式环绕在了生活的周围。它的显现,不在于我们注意到了它,而在于我们忽视了它。就像是我们向上凝望,以为那是苍穹,但从未发现,其实只是一堵墙罢了。</p> <p>这股力量不会是我们萎靡和恐惧,反倒是让我们产生了相当大的依赖。我喜欢以观察生活的细节举例,类似于洗澡后擦干身体的顺序。通常,这个顺序是固定的,并且你会非常信任这种顺序,相信这种手法会完美的擦干身体。它从未被很好的察觉,但我们信赖并应用它。这栋建筑的秩序,给我带来熟悉感与安定感。我不需要查询手册,或是小心翼翼的试探,打从推开红色的那道门开始,我对建筑本体的经验早就已经谙熟于心。</p> <p><img src="@assets/2023/p93481045.jpg" alt="从廊道看去的正门" /></p> <p>类似的现象,哲学和思想界其实更为普遍。我们以为是自己原创的思考,其实大部分早已经在百年或千年以前就已经被提及。通过与父母,老师以及朋友的交流,独立的阅读和学习,洞悉了思想的遗传记忆。这种力量,是我们赖以生存的空气。</p> <p>就如同文章开头所说的,心灵与经验的植入是不容易察觉,只有通过提前理性的学习和思考才能发现它的存在。而一旦察觉,那这股力量必然带来特殊的启示和心灵震颤。</p> <h2>与百年共枕入眠</h2> <p><img src="@assets/2023/p93481179.jpg" alt="性价比的选择" /></p> <p>这栋建筑包含以功能为优先的基础模块。大师通过有序的组合,构建出了整栋教学楼。这样组织的好处是,即便是过了百年,虽然教学楼的功能消失了,但通过激活不同的模块,也可组合出富含其它功能的建筑。</p> <p>原来的商店被改成纪念品和前台,食堂被改建成酒吧和餐厅。而至于原来的工作室,则理所应当的成了作品的展示区。学生宿舍被改建成旅店,一栋兼顾艺术观光和包含食宿的历史建筑,就这样通过重新组合,在现代重新散发出了光彩。</p> <p><img src="@assets/2023/p93481042.jpg" alt="前台的窗外" /></p> <p>checkin 的时间是下午两点。</p> <p>在前台,我获得了一张小册子,里面的介绍骄傲的写着『住在这里是一种非常特别的体验,无法比拟于一般酒店的过夜经历』。从正门出来走到建筑的侧翼,就能看到小小的入口。</p> <p>根据官方的描述,在当时这座学生宿舍是很豪华的。一层大概有 6 间单人房,公用的厨房,浴室与厕所各一间。由于有食堂的关系,位于最里侧的厨房并不大。为了适配整座建筑的水管系统,洗浴间中的淋浴部分被加高,形成了有趣的「房中房」。其中干湿分离部分做的非常合理,值得在未来的家装中做参考。厕所部分则较为简单,通过外露的水管能直观看到,每一层的污水都通过这根纵贯整栋楼的水管汇合,并最终流出。</p> <p>单人间的面积不小,非常方正。其中有一个洗手池,一个经典的红色柜子和桌子,两把椅子,一张大床,以及两盏灯。朝外则是巨大的矩形窗户,左侧有一扇通往外侧阳台的门。阳台很小,也是方正的造型。为了方便排水,伸出去地面的形状是向下卷曲的,令人感觉并不踏实。当然,对百年前的人们来说,这迷你的阳台却是灵感爆发的平台。透过当年的黑白照片可以看到,青年的学生们簇拥着聚会,创作和拍照,丝毫不顾危险。</p> <p><img src="@assets/2023/p93481046.jpg" alt="椅子,鲜红色的桌子" /></p> <p>我在回忆中搜索了一下,什么样的住宿氛围和它类似。拼拼凑凑,综合了一下,它感觉像是 00 年左右的招待所。空间虽然宽敞许多,但里面的一些陈设都有棱有角,别具风格与设计感,却说不出具体的所以然。</p> <p>当然,上述比较,仅是强行拼凑出的匹配结果,落实到具体细节,则有非常大的差距。譬如说那个红色的双开门大衣柜,门非常的厚,里带有收纳的柜子,180 度展开后,可与中间的衣柜整体组合成一个新的组合柜子,非常巧妙。这种家具的设计与细节的把握,一般酒店是肯定没有的。从整体的感受上讲,说它像是招待所,其实是想表露其以实用为优先的思路。比方地面平实光滑,走在上面不会发出老式木板的噪音。墙边以及墙角的过度,简洁且规整,怎么看都不会看腻。</p> <p>依旧,等等一系列的设计,虽然放到现在来看或许有些轻微的时代痕迹,但想到这是百年前的设计,便会由衷的感叹,到底是什么样的思维与灵感,造就了如此超前的创作。</p> <p><img src="@assets/2023/p93481057.jpg" alt="从楼道中仰视" /></p> <p>宿舍楼的空间,像是大师之家的青春版,容量变小了,但背后的设计小心思却一直保留着。楼道内的栏杆是明亮的红色,天花板根据楼层则使用了其它不同的颜色,从独特的角度看过去,像是蒙德里安的学生会创作出的作品。些许杂乱的结构,却充满了青年人的活力。</p> <p>漫游其中,我会幻想那时的学生会如何在这里生活学习与创作。</p> <p>课余时间,众人杂乱地涌进某位同学的小屋中。运气好有座位的,就坐着或躺着,运气一般的,就坐地上或直接站着。大家围成一团,听着某位有点子的同学,声情并茂得讲述自己和教授斗智斗勇的奇妙经历,或是阐述某种全新的概念与点子。等太阳角度合适,和煦的阳光照进房间,靠近窗户的同学打开了通往阳台的门,大家纷纷涌入狭小的阳台,在高处贪婪地享受着春天的激情与活力。</p> <p>兴许我所住的这所房间,就是某大师曾居住过的地方。</p> <p><img src="@assets/2023/p93481041.jpg" alt="阳台与蓝天" /></p> 经验包豪斯(二)https://blog.kaiyikang.com/posts/2023/%E7%BB%8F%E9%AA%8C%E5%8C%85%E8%B1%AA%E6%96%AF2/https://blog.kaiyikang.com/posts/2023/%E7%BB%8F%E9%AA%8C%E5%8C%85%E8%B1%AA%E6%96%AF2/Sun, 16 Apr 2023 00:00:00 GMT<p><img src="@assets/2023/p93482271.jpg" alt="题图:大师之家的门扉" /></p> <h2>大师之家 Meisterhäuser</h2> <p><img src="@assets/2023/p93482283.jpg" alt="建筑外侧" /></p> <p>名字听起来很酷,但实际上就是教授住的别墅楼。它不仅可以用来居住,还可以用作工作室,组织教学活动等等用途,可谓是功能多样。</p> <p>四座独立的别墅位于主教学楼北方,矗立在一片高耸空旷的树林之中。从建筑外面欣赏,颇有种在自然中欣赏雕塑的感觉。</p> <p>别墅的游览体验十分新奇。面朝别墅楼从左往右,第一栋建筑被设置为了售票处,第二栋和第四栋可以参观,第三栋仍被用作艺术家的居所和工作室。因为是私人宅邸,所以入口狭窄,只留了一人进出的宽度。大门默认是关闭的,若想购票或参观,则需要敲门或是按门铃,由里面的工作人员迎接,才能进入建筑内部。</p> <p>建筑的主色调是黑白灰,形状不是规则的方正,而是立体矩形的穿插和组合。根据组合的不同,建筑的外观呈现出不同的状态,但因为是共同的「基本的组件」,由此整体风格和功能是一致的。这种看似矛盾的状态,让原本沉闷的建筑群变得更加深刻。</p> <p><img src="@assets/2023/p93482311.jpg" alt="颜色与落地窗" /></p> <p>不过,这些建筑师的热情远没有就此结束。</p> <p>当你走近建筑,围绕它观察时,纯粹的红黄蓝色彩会忽闪忽闪的出现在你的目光中。纯粹的色彩像是飞鱼,在广袤的海面上,活跃地穿梭和上下起伏。它们常出现在不太起眼的位置,例如窗框内侧,建筑外围的天花板等等。我将它们视为这些大师收敛且克制的激情,旧时代的琐碎点缀仍没有从人们的头脑中脱离。它以深刻的方式遗留在了现代设计的隐匿角落中,既是一种迸发的闪念,又是一种无声的请求,以肃穆规则的姿态邀请你进入这座建筑中,进入这个崭新的时代。正如每一座墙体都是水泥的灰白色那样,你也许会得到沉默的对待,但哪些突然闪现的活泼色彩告诉你,它绝不是冰冷的,抗拒的。大师的思考与灵感弥散在每个细节中,你将从中体会什么是独立、自由与幽默。</p> <p><img src="@assets/2023/p93482282.jpg" alt="Wassily Chair" /></p> <p>当整理照片时,我发现 Bauhaus 的设计有种连贯性,所谓形散神不散。虽然作品的形式千变万化,但就像是从一个核心点牵引出的圆球体。当你从围绕它体验的时候,你不仅看到了球的不同表面,还能从背后看到统一且和谐的内核。这种别样的连贯性与凝聚力,似乎是在其他艺术理念里较为罕见的。</p> <p>当我穿过一道道门,尝试去捕捉里面的建筑内的场景时,康定斯基和蒙德里安的影子一次次出现在我的相机里面。对其中所袒露出的真诚与热忱,我感到惊讶。他们住在建筑中,并非只是去做干巴巴的艺术理念幻想,而是与空间一同思考与呼吸,将空间的肌肤融合进内心,再将思考的硕果,毫无保留地返还给空间与他们笔下的设计作品,从而达成正反馈似的增益。他们与设计物之间,内与外,形成了共识的通道。设计的灵感与热忱流淌其中,正因为这种坦诚,我们才能得以感知,从而成了接受恩惠的那一方。</p> <p><img src="@assets/2023/p93482335.jpg" alt="门扉2" /></p> <p><img src="@assets/2023/p93482345.jpg" alt="无滤镜" /></p> <h2>包豪斯博物馆</h2> <p><img src="@assets/2023/p93482295.jpg" alt="博物馆的下层空间" /></p> <p>博物馆位于市中心,从火车站步行只需十分钟。主街在维修,路上没什么车,也因为放假的关系,街上的人也寥寥无几。安静的空气,充斥着寂寞和荒凉。市中心的小公园内,老年人在遛弯,而年纪小的学生则聚在一起打球。静中取闹,在慕尼黑不常见到。</p> <p>博物馆的建筑,是非常标准的长方体。为了不使它成为无聊的硬疙瘩,深色的透明外墙为其增添不少趣味,仔细观察,可以寻到在里面游憩着的参观者。</p> <p>推门而进,一整层尽收眼底。售票处,咖啡店以及上楼参观的入口均被置于建筑的一侧,而其它的空间则正如其名,真的是「空」间。贩卖纪念品的货架,供人歇脚的桌椅,小孩子玩的大积木等等元素,凌乱地摆放在空间中。</p> <p>宽阔的面积,极高的天花板,衬着这些散乱的模块更加迷你。忽然间,空间作为一个抽象词竟也变得直觉和感性。我站在这边,望向那边,不由自主的开始想象,应该用什么东西填充这片区域,也许是用一些艺术展品?或是让演员们来一场演出?设计师坏得很,特地规划出一片广袤却带有限制的空间,将人们想要填充的欲望给勾引出来。与想象力发生冲突,现实却是空空如也,巨大的玻璃墙幕映衬出空间中的零碎,极限的反差勾勒出了空间的轮廓。我望着玻璃,看着模糊的自己,深刻的感受着多边形的包裹。</p> <p><img src="@assets/2023/p93482286.jpg" alt="巨大透明的材质" /></p> <p>楼上是展厅,囊括了众多和包豪斯有关的作品。与其说作品,不如说是生活方式,小到家具日用品,大到建筑楼宇,具体到锅碗瓢盆的摆放方式,抽象到圆形方形和三角形的理念。其中最大的展厅,是以人物的视角出发展开的,不仅罗列了教授的作品,还拓展性的加入了课程中的学生作品。可以注意到,老师的教学对学生风格的影响是多么的重要。</p> <p><img src="@assets/2023/p93482287.jpg" alt="老师作品" /></p> <p><img src="@assets/2023/p93482288.jpg" alt="学生作品" /></p> <p>包豪斯的每一件作品,似乎都朝着『是其所是』的方向靠拢。</p> <p>以老式的椅子为例。椅子腿,和椅子面的衔接处,再到椅子背,都能找到装饰的痕迹,不论是自然的雕花,神话中的龙凤,还是简单的弧线线条,它们都在向人们呐喊,我们是美,是艺术。而至于椅子本身,则退居其位,成为了美的载体。</p> <p>主人将它放置在屋子里,向每一位客人介绍椅子上精雕细琢的工艺。待介绍完,就请对方坐于其上,该喝茶喝茶,该聊天聊天。刚惊叹过的奇巧技艺,就被一股脑地闲置在屁股底下。</p> <p>包豪斯,有意在剥离这种花园的小径分叉,尝试将椅子最基本的属性暴露出来。创作者们通过沉思,将前者的『是』与后者的『是』(即椅子这个概念本身)直接打通,从而把在花园中迷失的人们,直接带离到了迷宫的出口。</p> <p>对这久违的直率,以及愿意去暴露的勇气,我感到敬畏与感动。</p> <p><img src="@assets/2023/p93482297.jpg" alt="是一种回归" /></p> <p>漫游在名为包豪斯的『家居市场』,我感受到的是秩序和矛盾。</p> <p>秩序,指的是艺术家们为了设计感和工艺感作出的平衡考量。我不会感到它的凌乱,即便不同作者的不同作品,放在一起却也显得十分和谐,例如只使用基础的方形体组合成一套家具。这种根据相同规则和理念下的产物,是秩序感最有力的体现。</p> <p>矛盾,理解起来也没那么复杂。真诚且澄澈的思考,犹如红色火焰一般的热忱,这是艺术家们企图通过最真实且直率的方式,将作品从人类的一般直觉中挖掘出来。我们普通人,即便没有说明书摆放在一边,也可以立即使用它们。天平的另一端,则是冷静且严肃的洞察,犹如蓝色冰晶一样的抽象与简洁。纯净的红黄蓝基色,平直且顺滑的几何曲线,它们来自物理自然,却从未走出过雅典学院,也从未走近人们最切实的生活。</p> <p><img src="@assets/2023/p93482293.jpg" alt="直线秩序" /></p> <p><img src="@assets/2023/p93482332.jpg" alt="靠近与疏离" /></p> <p>冰蓝的抽象实质,通过最热情的红色表达被输送到了当今这个时代,成了人们在现代生活的指南。这样的矛盾,既有人性一面,也有非人性一面。多重多面的复杂性,使之成为了一片丰富的沃土。什么样的果实,只需稍加改造与适应,便可在其中茁壮成长。我想,这便是包豪斯即便是过了一百年,仍在我们的视野中发挥着重要作用的秘密。</p> <p><img src="@assets/2023/p93482300.jpg" alt="前方的秘密" /></p> 里斯本,湿润斑驳的彩虹https://blog.kaiyikang.com/posts/2023/%E9%87%8C%E6%96%AF%E6%9C%AC-%E6%B9%BF%E6%B6%A6%E6%96%91%E9%A9%B3%E7%9A%84%E5%BD%A9%E8%99%B9/https://blog.kaiyikang.com/posts/2023/%E9%87%8C%E6%96%AF%E6%9C%AC-%E6%B9%BF%E6%B6%A6%E6%96%91%E9%A9%B3%E7%9A%84%E5%BD%A9%E8%99%B9/Sun, 22 Jan 2023 00:00:00 GMT<p><img src="@assets/2023/p92475825.jpg" alt="" /></p> <h2>概览</h2> <p>葡萄牙坐落于欧亚大陆的最西端。我的地理直觉很差,每次在地图上选择旅行目的地的时候,都会下意识地忽略这个国家,乃至来到欧洲这么多年后,才想着特地找时间光顾。</p> <p>下了飞机,湿咸温润的空气扑面而来,浸润了鼻腔。憨厚的云块漂浮在宽阔的机场上空,给予视觉强烈的压迫感。我习惯在欧洲内陆旅行,很少见到如此巨大的反差。反差越大,并且经验越离奇,旅行的韵味就会变得更加醇厚。</p> <p><img src="@assets/2023/p92475747.jpg" alt="" /></p> <p><em>在里斯本的机场</em></p> <p>因为靠近海边,清晨的时候,整座城市常被海雾笼罩。即使天空逐渐透亮,但起伏得街道依旧神秘且恍惚。路边的灯泡昏黄,低垂双眼,散发出清晨的倦意,仿佛在低声吟唱着什么,指引着迷途的人们进入一条不知道终点的迷宫。</p> <p>随着海雾气散去,太阳显出了真身。即便是在严酷的冬天,它温润的光线照耀着大地,身子也竟变得暖和起来。里斯本的气温不低,冬季也在 10 度到 16 度左右,但晚上较凉,不过多添一件衣物就足够应付。</p> <p><img src="@assets/2023/p92475749.jpg" alt="" /></p> <p><em>清晨的里斯本</em></p> <p>同是海滨城市的巴塞罗那,它里给我的更多是阳光与大海,充满着黄金的能量。里斯本虽然同样靠近大海,但孕育更多的,是海洋的粗粝和莽撞。</p> <p>从空中俯瞰里斯本,它的街道是曲曲折折的。蜿蜒曲折的小路如同毛细血管一般,随着地势的上下起伏,充满韵律得爬满整座城市。道路两旁的建筑是瘦高的。尽管它们的质感与相貌大相径庭,但在连接处却非常紧密,没有丝毫的空隙。道路曲折,迫使建筑也像蛇形一样排布。近处的墙体会因为曲折的道路,遮盖住了背后的景色。楼宇和墙面交相躲藏,身处其中,无时无刻刺激着我探索的神经,但这仅仅是水平方向上的遮蔽,而将这种快感推向极致的,则是在垂直方向上。</p> <p><img src="@assets/2023/p92475762.jpg" alt="" /></p> <p><em>俯瞰城市</em></p> <p>有别于大部分欧洲的核心城市,里斯本建立在山丘附近,许多老城区的建筑落差极大。走着走着,就突然出现一个上坡。道路延伸到天空,侵袭了全部的视野。我沿着道路爬上山坡,用喘气的频率交换提升的海拔。本想着走到了道路的尽头,但仅仅是抵达了稍微平坦的半山腰。</p> <p>站在珍贵的平地上,我尝试从建筑群中寻找一个合适的罅隙。从狭长的空洞中,我看到更多的民居楼随着山峦,一路向下俯冲,漫灌到远处的海岸,最终起飞,升向灰蓝的天空。</p> <p><img src="@assets/2023/p92475779.jpg" alt="" /></p> <p><em>巨大下坡的俯冲</em></p> <p>里斯本以立体而生动的姿态,诠释了什么是真正的城市立体探险。</p> <p>不过,过分的生机也给旅行造成了不少困扰。我订的旅店坐落于半山腰,虽然临近有公交车站,但都位于坡上或坡下,以至每次疲劳回来,仍需付出些额外的体力,以应对额外的升降。但随着往返次数的增加,我也学会了走捷径,不去依赖导航,而是看准车站,特地选择从山下的车站出发,然后从山上的车站返回。这样一来一回,意味着我永远只需要走下坡路。</p> <p><img src="@assets/2023/p92475793.jpg" alt="" /></p> <p><em>城市花园</em></p> <p>囿于城市街道的狭窄,公共交通工具被设计的非常独特。常见公交车的车厢设计的很长,为的就是能装载更多的行人,因为无法适应狭长的小路,以至于只能在宽阔的主干道看见。而进入了狭窄的小路,只有特殊的矮胖公交车才能穿梭在其中。它比一般公交车要短许多,其形态憨态可掬。有轨电车也类似,但相比前者,后者有着更加悠久的历史。不论是车门的开闭,内部的装修,还是手动去更换的车盘,都更偏向于自然机械的风格。这也因此使其成为里斯本独特的城市名片。</p> <p><img src="@assets/2023/p92475802.jpg" alt="" /></p> <p><em>古老的 12E 路,依旧保持着当年的形态</em></p> <p><img src="@assets/2023/p92475803.jpg" alt="" /></p> <p><em>内部的装饰</em></p> <p>里斯本的建筑在粗粝和精致间来回摇摆。由于海风的缘故,石质建筑表面常会出现黑绿色的污渍。它证明着时间正不断延展,像是代表个性的纹章,使得每个建筑都散发着独特的个性。</p> <p><img src="@assets/2023/p92475810.jpg" alt="" /></p> <p><em>斑驳的建筑</em></p> <p>瓷砖则是里斯本的另外一套独特景观,有时会大片铺满一整座建筑,富丽堂皇,有时却覆盖墙面的一小部分,残缺破败。瓷砖虽然是呆板的方形,但上面的花色却变化万千。运用了数学中的分型技巧,花色既可以独片成型,也可以与周围的瓷砖勾联,形成一片全新且巨大的图案。魔法般的变化,人们永远不会感到厌倦。</p> <p><img src="@assets/2023/p92475812.jpg" alt="" /></p> <p><em>大片的瓷砖</em></p> <p><img src="@assets/2023/p92475819.jpg" alt="" /></p> <p><em>瓷砖博物馆</em></p> <p>类似的美学,除了受到外来文化的影响,也受到了当地植物灵感启发。因为里斯本潮湿和温暖的环境,当地植被异常茂盛。即使是在冬季的 12 月,还能看到成片的亚热带植物群。叶片硕大,树枝高耸,围绕生长在城市的微型公园中。大大小小的植物纹理,也被采纳进了瓷砖以及建筑的元素中。相较于在巴黎或巴塞罗那看到的新艺术风格,它在文化和自然元素的融合更加粗糙和原始,细节的过度远没有前者来得丝滑,虽然笨拙,但却十分可爱,散发着原始地热情。</p> <p><img src="@assets/2023/p92475824.jpg" alt="" /></p> <p><em>贩卖的植物,十分的茂密</em></p> <p>里斯本是海洋精神的辐射器。它不仅包含着海洋的粗犷和野性,而住在这里的人们也受到感召,激发出了探索和热情,使得这座城市即使在波涛之中,也蕴含着无穷的精致宝藏。</p> <p><img src="@assets/2023/p92475832.jpg" alt="" /></p> <p><em>城市雕塑喷泉</em></p> <p><img src="@assets/2023/p92475847.jpg" alt="" /></p> <p><em>码头立柱</em></p> <h2>交通与计划</h2> <p>抵达里斯本机场后,跟随指示牌抵达地铁站,里面有几台售票机器。第一次需要额外购买一张充值卡,花费 0.5 欧元。我自己购买了 6.5 欧元的天票,进闸机之前需要刷卡,第一次刷会激活天票,持续时间 24 小时。充值卡的位置一般在地铁站,如果碰巧卡里没钱还需要坐公交车,上车也可以付费(但比较麻烦,我没有尝试)。</p> <p>地铁、公交车和有轨电车是最常用到的交通工具。其中地铁分成四条线路:蓝色的海鸥,黄色的向日葵,绿色的帆船和红色的东方。其中机场位于红线的终点站,只需要乘坐几站地铁,即可以很快抵达市中心。</p> <p><img src="@assets/2023/p92475822.jpg" alt="" /></p> <p><em>四条地铁线路</em></p> <p>我选择的旅店位于城西的半山腰上,离黄线的终点站 Rato 比较近。</p> <p>我一共在里斯本逗留三天,第一天去了里斯本旁边的城镇 sintra,那里有很多的城堡景观。从 sintra 中心出发,还可以抵达欧陆的最西端,罗卡角。第二天,逛逛里斯本市中心的著名景点:瓷砖博物馆,主教座堂和卡尔莫修道院,以及老城区,乘坐了圣胡斯塔升降机。第三天则去参观了热罗尼莫斯修道院。</p> <h2>在 Sintra 的佩纳宫</h2> <p>从地图上看,葡萄牙呈现出长条形,由东到西的距离并不算很遥远。从里斯本的 Rossio 火车站出发,乘坐一个小时的城郊铁路,即可抵达 sintra 的车站。</p> <p>另外值得说明的是,该车的车票,以及到达城郊以后的公交车车车票都需要额外购买。我的方案是:去 Rossio 车站的人工卖票处,购买一张 Train and Bus Ticket,印象中的价格是 16 欧元左右。这张票包含了从里斯本到 Sintra 的往返车票,以及 sintra 当地的公交车票。当然,你也可以选择单独购买,但我选择了比较偷懒的一种方法。</p> <p><img src="@assets/2023/p92475848.jpg" alt="" /></p> <p><em>清晨的售票处</em></p> <p>大概早晨的七八点钟,我到了人工的售票处,那里几乎没什么人,和售票员简单沟通后,他就给了一张黄色的车票卡片,凭借这张卡片,我就能乘坐在 sintra 任意的公交了。</p> <p>城郊铁路线有些破旧,这不仅是说车辆,而且路上的风景也同样。没有了里斯本城光鲜亮丽外表的环绕,整体呈现出了荒颓感。车里除了旅客外,当地人的肤色各异,各个民族的人都有,而穿着也都十分质朴,可谓是还原了葡萄牙最生活化的姿态。</p> <p><img src="@assets/2023/p92475876.jpg" alt="" /></p> <p><em>城郊铁路</em></p> <p>Sintra 的中央火车站不大,「中央」反倒是种过分的夸耀。矮小的平房,塞着迷你的服务办公室,里面只能挤得下几个员工。反倒车站建筑的内部的彩色瓷砖却十分夺目,甚至有些格格不入。</p> <p><img src="@assets/2023/p92475884.jpg" alt="" /></p> <p><em>火车站内的墙壁</em></p> <p><img src="@assets/2023/p92475881.jpg" alt="" /></p> <p><em>Sintra 线路图</em></p> <p>公交车站靠在火车站旁边。公交车是游览各处名胜的主要工具,虽然可以选择自驾,但崎岖狭窄的山路却是对驾驶技术的极大挑战,不如放心把颠簸交给轻车熟路公交车大爷,也算是旅途中的一种趣味。公交车线路是环形的,上车和下车的位置相同,不用担心找不到车站。</p> <p><img src="@assets/2023/p92475862.jpg" alt="" /></p> <p><em>山上的植被茂密</em></p> <p>佩纳宫坐落在城郊地区最高的山上,天气好的时候,登上佩纳宫可以俯瞰整座里斯本。</p> <p><img src="@assets/2023/p92475854.jpg" alt="" /></p> <p><em>云雾缭绕的城市</em></p> <p>可以选择网上购票,或是在现场通过自动售票机器购买。我当时去的比较早,售票机附近没什么人,而等到了中午,排队的人就非常多了。购票的时候,需要选择进入城堡的时间,如果去得太早,即便能够进入园区,但仍需要在城堡面前排队,等到了预定时间,才可以进入。进入城堡后,还有两到三次的检票,因此记得随时保留票据。</p> <p><img src="@assets/2023/p92475861.jpg" alt="" /></p> <p><em>中午的检票处,排满了人</em></p> <p>我很难评价这座宫殿建筑是什么风格。不同颜色和形状的立体几何形相互拼接在一起,但门框以及墙壁上的雕塑装饰却十分细腻,仿佛是在极力去掩盖拼接所带来的不和谐感。</p> <p><img src="@assets/2023/p92475874.jpg" alt="" /></p> <p><em>宫殿的外墙</em></p> <p><img src="@assets/2023/p92475880.jpg" alt="" /></p> <p><em>不同颜色的混搭</em></p> <p><img src="@assets/2023/p92475882.jpg" alt="" /></p> <p><em>红色的塔楼</em></p> <p>浪漫主义也能被分成很多种类型,有完美理想的,也有琐碎直觉的。不太清楚是不是因为和德国的设计师有关系,但它以很直观的视角,淋漓尽致地展现了什么是「外人眼中的它」。这种外来文化对本地文化的解读和阐述,也是文化领域中一个非常重要的主题。这座城堡仿佛丢勒眼中的犀牛,美学已经被放置在了次位,重要的是瑰丽的想象力与创造力。</p> <p>因为行程规划的关系,逛完城堡就匆忙下山了。至于剩下的花园以及其余有趣的城堡,等下次有空再去。</p> <h2>教堂与修道院</h2> <p>我并不热衷于欧洲各地的宗教文化。对时常出现的宗教建筑,也仅仅是走马观花,看个热闹罢了。</p> <p><img src="@assets/2023/p92475885.jpg" alt="" /></p> <p><em>建筑上的雕塑</em></p> <p>在里斯本除了主座教堂,还有两座很有名的修道院,一座在市中心的边上,名为卡尔莫修道院,因为早年地震的关系,失去了天顶,只留下了墙壁和飞檐。另外一座在城市沿海的西边,名为热罗尼莫斯修道院。</p> <p><img src="@assets/2023/p92475904.jpg" alt="" /></p> <p><em>卡尔莫修道院的断壁残垣</em></p> <p><img src="@assets/2023/p92475906.jpg" alt="" /></p> <p><em>卡尔莫修道院的雕塑</em></p> <p>粗野和狂放是主旋律,即便是能够带来宗教神圣感的精致,也被十分直观的雕刻在了建筑的外立面。主体如同一座憨厚巨大的蓝鲸,进入体内可以看到如同骨头的建筑架构穿插在天顶。它似乎仅仅是为了符合物理学的规律,从而就不加修饰的放置在了那里。当然其中也并不是没有巧思,但更多的是「思」,而非「巧」。人类思想中的那股原始力量,毫无遁形的被暴露出来,化身成了建筑中的纹饰。</p> <p><img src="@assets/2023/p92475886.jpg" alt="" /></p> <p><em>卡尔莫修道院的内部</em></p> <p>海洋之心不断影响着这些建筑,它所感染出的人类想象力是实现这些建筑的灵感源泉,而为了抵抗大自然的强大力量,建筑中的每一块砖石也不可避免的变得笨重和粗犷。旷日持久,仿佛是一场无声的战争,一边建筑,一边毁灭,从墙砖的斑驳和参差的缝隙中,我们看到了这场声势浩大拉锯战中的点点滴滴。</p> <p><img src="@assets/2023/p92475892.jpg" alt="" /></p> <p><em>热罗尼莫斯修道院的塔楼</em></p> <p><img src="@assets/2023/p92475908.jpg" alt="" /></p> <p><em>热罗尼莫斯修道院的内部</em></p> <p>如今,这些修道院已经成了历史,成了抽象的知识和象征。它里面要么是空空如也,要么被改造成了博物馆,被用来放置历史文物。商业化的改动,以至丢失了很多当时人们的生活细节。太多游客的堆积,让这些靠金钱、血泪和信仰凝固成的建筑损失了很多味道,只有仔细品味,才能从中咂摸出一丝丝滋味。</p> <h2>夜幕</h2> <p>里斯本的太阳落入海平面之下,夜幕低垂。人们丝毫不会留恋太阳的退场,反倒翘首以盼,期待黑夜的光临。</p> <p><img src="@assets/2023/p92475943.jpg" alt="" /></p> <p><em>夜幕下的城市</em></p> <p>有两种灯挂在头顶,一种是常见的路灯,整齐得矗立在大路两旁,还有一种是明黄色的灯泡,挂在小路边的高墙上,惹得眼睛恍惚。里面还内置的传感器,人走在下面,灯光突然闪耀,仿佛有神明飘荡在身后,屁颠屁颠地帮忙。</p> <p><img src="@assets/2023/p92475944.jpg" alt="" /></p> <p><em>灯柱和电车</em></p> <p>五彩缤纷的黑夜还在持续鼓动,大路和小路则是两种风景,得分开说。</p> <p>大路上,林立的店铺关了门,取而代之的是酒吧和餐馆。格调通常都很高,是奢侈的成年人经常光顾的地方。囿于建筑的狭窄,餐厅常是长方形的,嵌入在建筑中,一侧是出入口,一侧是透明玻璃围成的厨房。外面看进去,里面很深,像大海一样,激活味蕾上的探索欲。</p> <p><img src="@assets/2023/p92475956.jpg" alt="" /></p> <p><em>狭长的餐厅</em></p> <p>蜿蜒曲折的弄堂,体现着里斯本夜晚的真正魅力。</p> <p>与大道上的西装革履不同,小道上的舞蹈是年轻且动感的。从进入的那一刻开始,就立即被感召。街道极窄,左右两侧的居民楼十分高耸,最终只留下了能够仰视天际的罅隙。如小孩巴掌大的砖石,铺成的道路迸发出细碎且轻盈的鼓点,咚咚哒哒得随着山峦的起伏上上下下。躁动的节奏没有停歇,反倒是和两边墙壁的瓷砖产生了共振。灯光混杂着月光,瓷砖时刻变化着颜色,在楼宇间来回跳跃穿梭,时而变大,时而变小,时而密集,时而稀疏。白天里洋溢着色彩,夜晚却变得诡谲多变,难以琢磨。</p> <p><img src="@assets/2023/p92475967.jpg" alt="" /></p> <p><em>里斯本的小巷</em></p> <p><img src="@assets/2023/p92475958.jpg" alt="" /></p> <p><em>坐在外面喝酒</em></p> <p>嘈杂的节奏充盈着深夜宁静的小道,不断鼓动着人们向深处探索。酒吧与餐厅像是各种味道混杂的糖豆儿,包装袋没扶稳,手一抖,不规律的洒落在各处。大人的刻板规则,到这里就失去了效用。香烟的迷雾,欢唱的笑声,被大门遮掩着的舞台女郎,异国的风情…悠长的小巷子,像是个神奇口袋,迸出各类新奇好玩儿的元素,拨弄着年轻人炽热的好奇心。</p> <p><img src="@assets/2023/p92475965.jpg" alt="" /></p> <p><em>站在外面喝酒</em></p> <p><img src="@assets/2023/p92475961.jpg" alt="" /></p> <p><em>熊猫食堂</em></p> <p>转过街道,迷你的花园则是另外一副天地。分散的道路汇聚到中间的喷泉,剩下的空间被绿植填满。硕大的叶片,茂密的树丛,仿佛一阵迷幻药,让我无法分清是冬天还是春天。开阔的视野和幽暗的灯光,使得无论多么高大的植物,仿佛都退化为了墙角的青苔,变得闭塞和内敛。此刻,忽得回忆起了在中国南方的童年景象,白色墙壁虽然高大,遮盖天空,但也有其不堪得一面。靠近地面的墙根,因为潮湿的关系变得发黑发腻。青苔见缝插针的生长,足够野蛮,看久了也习惯了,最后沉淀到了记忆里,竟生出了一丝久违的安心和舒适感。</p> <p><img src="@assets/2023/p92475960.jpg" alt="" /></p> <p><em>凌晨的花园</em></p> <p>迷你花园一旁的小亭子,散射出如同天堂一般的白色荧光,贩卖的不是香烟或报纸,而是被我称作是精神与灵感的源泉——酒精和点心。虽然酒的品类不多,但也完全足够慰藉不同的灵魂。里面的小哥带着微笑,悠然自得的满足每个人的需求,那一晚,他成了救世主。</p> <p><img src="@assets/2023/p92475962.jpg" alt="" /></p> <p><em>灵魂补给师</em></p> <p><img src="@assets/2023/p92475970.jpg" alt="" /></p> <p><em>灵魂补给站</em></p> <p>有人单独,也有人三两成群,点上一杯葡萄酒,围坐在花园中一角,伴随着五彩斑斓的黑夜侃侃而谈,生活也因此变得无比醇厚起来。</p> 陆止于此,海始于斯https://blog.kaiyikang.com/posts/2023/%E9%99%86%E6%AD%A2%E4%BA%8E%E6%AD%A4-%E6%B5%B7%E5%A7%8B%E4%BA%8E%E6%96%AF/https://blog.kaiyikang.com/posts/2023/%E9%99%86%E6%AD%A2%E4%BA%8E%E6%AD%A4-%E6%B5%B7%E5%A7%8B%E4%BA%8E%E6%96%AF/Mon, 23 Jan 2023 00:00:00 GMT<p><img src="@assets/2023/p92602999.jpg" alt="" /></p> <p>在丧失理智,完全落入情绪深坑前,我想告诉你的是,从里斯本旁边的sintra火车站出发,乘坐403公交车,一个小时左右就能到达。</p> <p>我觉得自己像个浪人,外套很脏,袖口的和下摆都开了花。贴身的衬衫也粘哒哒的,不舒服。车沿着山路颠簸,车上总共不到十个人,生疏和分散的坐在窗户边上,时不时还会上来几位没有眼力价儿的当地人,破坏这种好不容易建立起来的尴尬平衡。我困倦,手机要省电,靠着前面座椅的塑料靠背睡眠。不知道过了多久,到了,细腻微小的兴奋渗了出来,蔓延到手臂,脖颈,脸颊与头皮上。</p> <p>这是一种属于思想上的极限,而并非身体物理上的极限探索。所谓它的极限,在于它位于欧陆的最西端。而我的极限在于,我被它召唤,大脑觉得它很极限。于是,我将此种行为赋予一个素雅的称呼,名为安全的极限。</p> <p><img src="@assets/2023/p92603017.jpg" alt="" /></p> <p>伴随着失焦的视觉,我选择配合来自英国sea power乐队的《From the Sea to the Land Beyond》专辑一起下咽。呜呜咽咽的噪音与古典,还有隐约的海浪与海鸥声,可谓天作之合。</p> <p>那里是思绪的流浪终点,能满足我们想要的一切。回头看是漫山遍野的绿色植被和小黄花,再回头看是无尽的海洋与天空,侧面有一座小小的红色灯塔,是最完美的点缀。到达这里,任何感情都被束缚住了,不是因为空间太过狭窄,而是太过无垠,乃至无法寻求到终点,无法落地,由此被束缚住了。</p> <p>我想吟诗,但我不会吟诗。我想拍照,但相机容纳不了远方。我想思考,大脑却因海风的不断吹拂,而停止了无休止的漫游与歌唱。能够打断我的,唯有旁边时不时路过的游客,有些说着我能听懂的语言,主题多半是一些繁杂且无聊的日常,不论海风多么强烈,都无法吹散腐臭和令人窒息的味道。我沿着安全的栅栏,小步行走,远离他们,持续踱步到自我精神的彼岸。</p> <p><img src="@assets/2023/p92603036.jpg" alt="" /></p> <p>石块被海风侵蚀成尖锐的模样,黄色的小花却是十分的柔和精致。他们整齐划一,朝着一致的方向看去,也许是向内朝着无尽的大陆,也许是向外朝着无尽的海洋,亦或只是朝着那座可爱的灯塔,以至于觉得自己也是一副十分可爱的模样。</p> <p><img src="@assets/2023/p92603037.jpg" alt="" /></p> <p>海风持续吹拂,掀起了海雾。轻微的海雾弥散开来,连接着天空,远处的地平线,近处的波涛,海岸与悬崖,全都被勾联在了一起。拍照大概很难带来良好的视觉观感,就如同我面所说的,当相机的镜头朝着地平线伸展过去,获得只是完全的失焦。类似于盲人,所有抵达这一刻的人,双眼都跟随自然磁力的牵引,瞬间失灵。正如同面对广袤的海洋与天空,我们终于变得平等而又脆弱。</p> <p><img src="@assets/2023/p92603043.jpg" alt="" /></p> <p>失去视觉的代价是什么呢?我觉得是想象力。我想我在此刻已经丢失了想象力,无法再去运用任何想象到的元素拼凑出一个合理的世界。每一块砖,每一块玻璃,都被吸入无尽的地平线。颜色,还有什么颜色,眼前除了蓝色什么都没有。我感到愤怒,但却无处发泄。愤怒在急促的流逝,悄无声息的也离我而去,而后欣喜钻出了出来,但没持续很久,也很快的消失。</p> <p>我被世界放了鸽子,所有的感官情绪全部落了空,以至于我成了一个空壳,一个结构。自然塑造了这个结构,而这个结构所生成的所有结果都漂流回了自然。于是,我们成就了一体,也许我不该如此的自信,将我与它称为我们,客气的说法是,他们,我成了他们,我将不再是我。</p> <p>渐渐的我意识到,我不能再迷失下去,否则会永远融化在无尽的海洋与幻想之中。遂尝试控制住了理智,并开始背离这个迷茫精彩的地方。</p> <p>我回到旁边的公交车站。等车的时候,在备忘录里写下了这样一段话:左顾右盼,一边是感性海岸,一边是生活公交。最终不论那里多么辽阔,我们终将需要生活。</p> <p><img src="@assets/2023/p92603044.jpg" alt="" /></p> 博客https://blog.kaiyikang.com/posts/labs/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/https://blog.kaiyikang.com/posts/labs/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/关于本博客的技术和特性介绍Tue, 26 Dec 2023 00:00:00 GMT<p>import Callout from "@components/mdx/Callout.astro";</p> <h2>Brown Noise Player (2026.01.26)</h2> <p>我很喜欢听噪音,尤其是棕色的噪音,会让我安静下来,所以想做一个在自己的空间中。</p> <p>现在已经做好了,你可以点击网页顶部的播放按钮倾听,放心,声音很柔和,不会惊吓到你和周围的环境。</p> <p>关于如何制作,我本来想使用音频文件,但是它太大了,而且会占用存储空间,因此我想到了使用 JS 函数来生成噪音。</p> <p>大致的原理是首先生成随机的白噪音,这里我设计了双声道。</p> <pre><code>for (let i = 0; i &lt; bufferSize; i++) { // 左右分别填充独立的随机数 // 这利用了"双耳去相关性",让声音听起来极其宽广,不聚焦在脑中 leftChannel[i] = Math.random() * 2 - 1; rightChannel[i] = Math.random() * 2 - 1; } </code></pre> <p>棕色噪音的特性是充满低频,因此这里使用了Lowpass Filter(低通滤波器)来滤除高音部分。 当然,这只是模拟了听感,而不是实际的棕色噪音(即频谱每翻一倍,能量下降一半的平方:1/f^2):</p> <pre><code>const filter = state.ctx.createBiquadFilter(); filter.type = "lowpass"; filter.frequency.value = 220; // 保持 220Hz 的深沉感 </code></pre> <p>为了避免突然的咔哒声音,可以用渐强:</p> <pre><code>state.gainNode.gain.setValueAtTime(0, state.ctx.currentTime); state.gainNode.gain.linearRampToValueAtTime(0.3, state.ctx.currentTime + 0.8); </code></pre> <h2>MDX (2026.01.14)</h2> <p>我希望让一篇文章能够拥有更多且灵活的表现形式,因此加入了 <code>mdx</code> 格式。</p> <p>&lt;Callout type="tip" title="为什么是 MDX?"&gt; MDX 让我能够在文章中直接嵌入 React 或 Astro组件。 我可以在其中展示<strong>交互式图表</strong>、<strong>实时代码演示</strong>,而不仅仅是枯燥的代码块。 &lt;/Callout&gt;</p> <p>我将有机会展示更多的内容,类似于:</p> <p>&lt;Callout type="info" title="新的内容"&gt;&lt;/Callout&gt;</p> <p>而不是</p> <pre><code>&lt;Callout type="info" title="新的内容" /&gt; </code></pre> 2024 写作https://blog.kaiyikang.com/posts/2024/2024%E5%86%99%E4%BD%9C/https://blog.kaiyikang.com/posts/2024/2024%E5%86%99%E4%BD%9C/Sun, 14 Jul 2024 00:00:00 GMT<p>标题的数字没有任何含义,不代表 2024 年有什么特别之处,单纯是个作为区分的版本标签。关键想聊的是当下的写作。</p> <p>说话的我和写作的我,是两个不同的我。在说话的时候,我明显感觉到,思维像是一个沙包,一颗颗豆子被缝在小布兜里,不规则无定形。说出来的话,仿佛小孩子将沙包丢到天空中,到处飞来飞去。写作可大不一样了。豆子还是那些豆子,但我有足够的时间,坐在电脑面前将它们按压碾碎,过筛烹煮,最终把口感绵密的豆沙馅呈现给对此感兴趣的读者。</p> <p>在创造方面,我是形式感很重的人,这意味着,我的创造应该遵循特定的韵律,文字要有明确的标题和格式,其内容要足够规整和易读。照片要稍微精致一些,并且发布的日期需要定点定时。我想,我没法为这些这种形式感寻找出合适的理由,它混杂了片面的最佳实践,还有个人的性格与审美。也许是做为程序员所带来的职业病,什么创作似乎都脱离不了写代码的影子。虽然代码是严谨的,但面对同一个功能块,不同的人会有不同的实现方式,这也同样包含着创作者的性格,从该角度讲,我将其称为一种艺术。</p> <p>对形式感的感触是薛定谔式的,看着整齐划一的历史文章和图片,有时觉得充满着秩序的美感,但有时也会觉得有些许的反人性。当我想创造或发布一个作品的时候,脑子里蹦出的第一个问题不是关于作品本身,而是它是否符合先前的各种形式,如果符合了,无事发生,如果不符合,那么也许连创作行为本身都无从谈起。让创作之外的东西影响其本身,我对此实在欢喜不起来。</p> <p>我时常认为写作是严谨的,要顾及逻辑的同时,更不能胡说八道。但细细想来,这也得看自身和环境,要是它本就不是什么技术论述,单纯是个私人感受的分享,那感受和思维会变的过分拘谨,得不到其应有的释放。而环境的影响也不小,例如有无评论功能,即使是一篇胡言乱语的技术分享文章,下面根本没有评论功能,读者找不到渠道去评论或纠正文章中的错误,这样的胡说八道似乎也招惹不来太多的是非,也仅是落下个江湖骗子的名分。不太重视脸面的作者,也就无所谓要不要严谨了。</p> <p>我在想是不是自己太过君子,从没有过流氓的念头,或有过类似的念头,但不过是被心中强烈的道德卫兵缠在树上吊起来打。有时候斯文败类就是比纯斯文的要更有魅力,前者不仅多了俩字,同时还消弱了后者所透露出的学究和规训气质。</p> <p>我想变得有趣,严谨只是一个工具,但最终还是想要有趣。</p> <hr /> <p>我从去年的每星期一篇,再到每月一篇,最后到了几个月一篇,写作频率是越来越低。专心投入到生活之中是原因之一,而平台则是另外的原因。</p> <p>比方说微信的公众号。在发布公众号文章前,需要首先在另外的平台排版插图,然后再同步。经过一系列复杂的点选和设置后,还要打开手机扫描二维码,最终才能发布出去。而且文章一经发布,几乎没有修改的可能性。从腾讯的角度角度讲,这逻辑很合理,在如此严苛的环境下,要是真的放开了,让人们撒开了手脚放肆的书写和发布,微信真有可能变得乌烟瘴气。诚然,即便是增加了如此多的束缚,现在的公众号仍活成了我们不喜欢的样子。说回来自己,我不管它是如何考量的,但就我自己的真实体验而言,发布一个公众号就是复杂的,刻板的,极端严肃的。</p> <p>我不喜欢。</p> <p>其他的平台也有可能,但着实好不到哪里去。比如豆瓣,丝毫不考虑我作为作者分享的强烈欲望。我辛苦拍的照片,写的文字,就是为了让所有人早早看到,而不是闷在那里,等着管理员蹲在小黑屋里细细筛选完每个字以后,再被无罪释放。我的分享应该是属于所有人的,所有人都应该有同样的资格第一时间阅读到它。(当然,我最亲近的人应该排在所有人之前:D)</p> <p>切换平台成了重要的事情,因此私人的博客就孕育而生。这个平台,除了底层的技术用到框架技术之外,完完全全都是在我控制范围内的。从域名链接,再到背景颜色,再到每一个字的间距和大小,这些都是可以根据个人喜好定制,而不必再遵循任意平台的限制。</p> <p>当黑盒子被打开成为白盒子的时候,一切运行的机理将更为透明且可理解。当然,伴随着的阵痛必然是信息过载,有太多不明白的问题亟待解决,以及更多的东西要学习,但幸亏学习的过程是愉悦的,不断的刺激和正面的反馈正在推动着我去不断的思考当前的平台,如何改善它?如何增加新的功能?如何让阅读的体验更加舒服?我相信对我自身成长也是有裨益的。</p> <hr /> <p>有了态度,有了传递出的平台,最重要的还应该回归内容。内容的形式,长短,该有什么不该有什么,我想了很久,也想了很多。</p> <p>有天,我曾和母亲提起过,说是如果文章太短会不会显得琐碎,从而没人阅读。她的反馈是,短点儿好,短了才有人来读,毕竟现在社会的节奏这么快。我心想说有道理,于是这才放下对长短的戒备,在此之前,我都会把极短的内容堆积在一起,拖了很久才发布。现在想想,短了,于他人于自己都是有利的。</p> <p>有另一个我不太确定的好处是,短了反倒更容易让我写的变长。短了怎么又变长了,这不是矛盾么?其实情况是,我写作不光是情绪很严肃,环境和时间更是要遵循一个特定的路径。从频率角度讲,产量最高永远会发生在周日的下午,家旁边的咖啡馆。道理很简单,为了平静的迎接周一的工作日,周日的下午自然不会安排什么太过上火的活动,爬山社会,胡吃海塞都会统统避免。而咖啡馆则是老习惯,这点我记得在几年前的文章还提到过,记得那时还奉星巴克为圭臬,现在已然变成最糟糕的选项了。</p> <p>现在生活节奏发生了变化,以前的路径也不再合适,创作长文的方式也应该得到调整。虽然时间比较细碎,但我可以利用这个细碎的时间去专注书写些较短的段落,然后通过对这些段落的拼接和延续,逐步积累成一篇长文。相较于之前的「要么全部,要么没有」,短段落更加现实,实现了先把内容铺开来再说的策略。</p> <hr /> <p>还有是内容的选择。</p> <p>有了搜索引擎,甚至是大预言模型之后,我发现技术、理论或和方法论相关的内容,都已经屡见不鲜了。很多内容都被整理的很好,很具体。我之前还曾想把自己遇到问题的解决答案总结成博文,分享出来,但发了几篇之后,发现还不如网上搜索到的总结好,看似内容都是自己的东西,实则都是复制粘贴的杂合体。尝试几次后,就完全没了兴致。</p> <p>在实际中为了解决问题而存在的内容,我愿称其为笔记,更应该随性的为自己记录,保存在本地,至于是否公开,那就得看每个人的意愿。就我自己而言,我在想如何让自己的博客变得更自我一点,不是说从中展现太大的 ego,而更多的是自身性格和对同一片天地的认识。这种偏向于让物种多样性增加的方向,是在这个内容生成时代弥足珍贵的东西。就算大预言模型能够精准产出像我这种风格的写作内容,那我也希望这个循规蹈矩的自己,而不是概率模型。</p> <hr /> <p>最后想用一句简短的话来总结这篇文章:我希望在自己的平台上,用有趣,简练和带有自我的方式做表达。</p> 什么是同步锁?https://blog.kaiyikang.com/posts/2024/java_%E5%90%8C%E6%AD%A5%E9%94%81/https://blog.kaiyikang.com/posts/2024/java_%E5%90%8C%E6%AD%A5%E9%94%81/Fri, 04 Oct 2024 00:00:00 GMT<h2>同步锁和线程</h2> <p>Synchronization Lock,同步锁,或称监视锁,互斥锁。</p> <p>它确保不同线程,安全访问共享资源。</p> <p>一个线程,想执行以下代码时,需获取该对象的锁。该获取过程,由 JVM 自动控制:</p> <pre><code>public synchronized void method() { // 方法体 } // OR synchronized(object) { // do something } </code></pre> <p>多个进程,想同时执行下面的代码,即尝试获取同一个对象的锁,叫线程竞争。</p> <p>竞争会导致 Blocked 阻塞状态([[Java多线程的故事#线程的状态]])。线程 B 已经持有了锁,线程 A 想尝试获得,但失败。JVM 会将它置于锁池 Lock Pool 中,并改变 A 状态为阻塞。</p> <p>Lock Pool 中存放着,所有试图获取锁,但没有如愿的线程。</p> <p>线程 A 在池中,会等待锁的释放,一旦释放,便恢复运行。该过程称为等待机制。</p> <p>与之相对,持有锁的线程,执行完代码以后会释放锁,JVM 会唤醒等待的线程。称之为唤醒机制。</p> <h3>例子</h3> <pre><code>class Scratch { public static void main(String[] args) { SharedResource reource = new SharedResource(); Thread threadA = new Thread(() -&gt; { reource.doSomething("Thread A"); }); Thread threadB = new Thread(() -&gt; { reource.doSomething("Thread B"); }); threadA.start(); threadB.start(); } } class SharedResource { public synchronized void doSomething(String threadName) { System.out.println(threadName + " has entered the synchronized method"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadName + " has exited the synchronized method"); } } </code></pre> <p>输出:</p> <pre><code>Thread A has entered the synchronized method Thread A has exited the synchronized method Thread B has entered the synchronized method Thread B has exited the synchronized method </code></pre> <h2>关键字 volatile</h2> <p><code>synchronized</code> 锁的是 object,定义是 is created using a class is said to be an instance of that class。</p> <p><code>volatile</code> 锁的是 Variables,更轻量。开销小,性能好。</p> <p>直接的例子:</p> <pre><code>public class SynchronizedExample { private boolean flag = false; public synchronized void setFlag() { flag = true; } public synchronized boolean isFlag() { return flag; } } </code></pre> <pre><code>public class VolatileExample { private volatile boolean flag = false; public void setFlag() { flag = true; } public boolean isFlag() { return flag; } } </code></pre> <p>特性对比:</p> <table> <thead> <tr> <th>属性</th> <th>volatile</th> <th>synchronized</th> </tr> </thead> <tbody> <tr> <td>可见性 Visibility</td> <td>yes</td> <td>yes</td> </tr> <tr> <td>有序性 Ordering</td> <td>yes</td> <td>yes</td> </tr> <tr> <td>原子性 Atomicity</td> <td><strong>no</strong></td> <td>yes</td> </tr> </tbody> </table> <p><strong>可见性 Visibility</strong></p> <ul> <li>定义:指当一个线程修改了共享变量的值,其他线程能立即看到。</li> <li>原因:多核 CPU 和各自缓存之间出现的一致性问题。</li> <li>例子:线程 A 修改了变量 x 的值,但线程 B 可能仍然看到的是 x 的旧值。</li> </ul> <p><strong>有序性 Ordering</strong></p> <ul> <li>定义:程序执行的顺序按照代码的先后顺序执行。</li> <li>原因:为提高性能,编译器和 CPU 会对指令进行重排,导致执行与书写顺序不同。</li> <li>例子:代码中,语句 1 在语句 2 之前,但实际执行时,颠倒顺序。</li> </ul> <p><strong>原子性 Atomicity</strong></p> <ul> <li>定义:指一个操作是不可中断。即使在多线程中,操作一旦开始,会让整个操作完成,而不被其他线程干扰。</li> <li>问题:在多线程环境下,非原子操作可能会导致数据不一致。</li> <li>例子:i++ 看似是一个操作,实际上包含了「读取 i」、「增加 1」、「写回内存」三个步骤,因此不是原子操作。</li> </ul> <h3>什么时候用 volatile?</h3> <p>适合用在一个线程写,多个线程读的场景,并且写入是简单赋值,不依赖于变量当前值。</p> <p>适合的例子:</p> <pre><code>public class FlagHolder { private volatile boolean flag = false; public void setFlag() { flag = true; // 简单赋值,不依赖当前值 } public boolean isFlag() { return flag; } } </code></pre> <p>不适合的例子:</p> <pre><code>public class Counter { private volatile int count = 0; public void increment() { count++; // 不适合,因为这是复合操作,依赖当前值 } } </code></pre> 2024 十月阅读https://blog.kaiyikang.com/posts/2024/2024%E5%8D%81%E6%9C%88%E9%98%85%E8%AF%BB/https://blog.kaiyikang.com/posts/2024/2024%E5%8D%81%E6%9C%88%E9%98%85%E8%AF%BB/Sun, 27 Oct 2024 00:00:00 GMT<h2>正文</h2> <p>最近 mindset 一词出现频率很高。</p> <p>例如 Joshua Comeau 不断尝试从编程逻辑的视角去理解 CSS。在他的技术演讲中,内容不仅精彩,还拓展了自己的心路历程,并在其中强调了自己在学习中填补 mindset gap 的重要性。</p> <p>我没有想到中文合适对应的词。mind 有头脑和精神之意,而 set 则是集合。它不单是「思维模式」或「思维心态」,也许是知道编程的缘故,对我而言,它更有些模块的含义。gap 更加活灵活现,也符合对应的气质。人们摆弄着不同的模块,然后把它们以合适的顺序连接起来,从而得以理解一个事物。</p> <p>我很喜欢这个概念,这和《Soft Skills》中曾强调过的「构建属于自己的 Tutorial」不谋而合。通过学习与实践,把理解一个事物的思维链条打通,使其足够直观且容易理解,这是我想一直践行的。</p> <p>同时,打通逻辑链也应符合从简的原则。一切应从最符合直觉的简单语句出发,例如动宾短语等,然后逐渐深入,就像用放大镜观察,一层层捕捉其中没有连接上的断点,等到断点被弥合了,再深入,直至功能最终实现。在这个递归流程中,我们能够感觉并理解为什么人们当前所流行的解决方案非常复杂的原因,因为它们存在的目的正是弥补从直观角度上考虑所忽略的事情。那些敲脑袋想出来的解决方案,但在实际中其实并没有办法实现。如果我们时间和精力不足,那么只考虑该模块的输入和输出,而不深入内部构造,也足够能实现想要完成的任务。</p> <p>面对如此规模的知识爆炸,快速理解并将其引入到当前结构的能力是十分必要的。制造轮子在深入了解某个主题是必要的,但倘若时间有限,拿来就用也是不错的选择。</p> <hr /> <p>最近几个月都在集中的阅读 Cal Newport 的书,改写版的《Digital Minimalism》,中译本《深度工作》以及原版《Slow Productivity》。</p> <p>在聊这些书的内容之前,我似乎稍微摸到了畅销效率书的门道,开头先来一个例子:出现了问题,然后在一阵反思,顺着展示出承诺或解决方案,最后再持续循环。至于上面提到的作者,比类似的模版稍微好一些,除了例行公事外还会讲一些自己信奉的主义和哲学,算是给读者喂了「所推崇的方法论有效」的定心丸。</p> <p>自主性的理念贯穿他所有的著作。《Digital Minimalism》不会排斥社交媒体和最新的技术,而提倡在使用这些技术前,我们要明确的认知,知道我们自己到底想要获得什么。iPhone 的确能运行很多 App,但这不是因此贪嗜的理由,甚至乔布斯在当年最初设计的时候,排斥将应用商店加入手机功能中。此刻的我们,正在使用着别人硬塞给我们的东西,从不去想自己真正想要获得什么。</p> <p>至于《深度工作》则强调注意力在现代的重要性。他们不仅希望我们可以掏钱,还进一步希望剥夺我们的注意力,从而将大部分的时间投入其中。因此,重新获得专注力,并将它投入到自己想要发展的领域,在当下是尤为重要的。</p> <p>最后的《Slow Productivity》是 2024 年最新的作品,基本上将上面两本书做了个结合。他总结出的原则是,在快节奏的工作中,我们应该以自然的工作节奏,做更少的事情,并且执着于质量。这本书就像我的认知唤醒器一样,不断的轻轻敲打我应该如何更加主动的做好一件事情。</p> <p>在讲述执着于质量的章节中,作者提到了审美的重要性,让我印象深刻。以往对于审美的谈论很大程度聚集在个人本身,例如提升修养品味,让精神升华一类的。他提到的却是和所做工作的质量联系在一起的。对这点我感同身受,当我欣赏过精致简约的架构和代码之后,我就再也无法忍受自己写出质量糟糕的代码,并且看到由他人写的,质量并非很好的代码,也会变得无比嫌弃。这里其实假定了,人都是有朝着更好方向迈进的动机的。把握了这层心里,一旦人有了品味和审美以后,再让他退回到之前的状态,是无比困难的。因此,好的品味不光还影响人本身,还会影响人手下的创造之物。</p> <hr /> <p>另外一本读到的是台版翻译的《Die With Zero》。</p> <p>作者偏爱风险,同时很富有,因此对书中的内容,我无法完全认同,照单全收,但有些点子确实有启发性,尤其是在于个人塑造方面。</p> <p>他强调了一个观念是,人的经历和金钱一样重要,会随着时间的变化产生收益。不同点是,钱可以在任何时间挣取出来,但经历会随时间和年龄的变化而无法再来。作者假定了有一些经历是和年龄匹配的,过了这个年龄,再经历味道就不对了。因此如何让对应的年龄,做对应的事情,从而让它的「收益」最大化,这是他在书中要讨论的事情。</p> <p>我们所知道的绝大部分人都是在生前省吃俭用,死后还留有一大笔未曾使用的财产,而这部分财产则包含着,这个人为了辛苦工作,而丧失掉了享受人生的那部分时间。人的时间大多固定,只要用在了这里,那么就没办法用在那里,正是所谓的「机会成本」。</p> <p>把握自己真正想要的,从而让自己在有精力的时候去经历这些,才是让生命获得最大价值的方法。以上论述都来自于书中,而现实状况更多是复杂且有出入的,因此我才说不能照单全收。但作者也还是强调了「主动做自己想做的」,这点也和上一节作者所提及的不谋而合。</p> <p>从中我读到的比较新颖的观点是,人在死后留财产是「不负责的」,只有那些不知道自己如何享受生活和支配金钱的人,才会将选择权交给死亡。相反的那类人,会在生前就处置并分配好所有的财产,不论分配是多少,自己知道自己要花多少,以清醒和理智的态度直面死亡。</p> <p>我个人觉得有一定的道理,但仍太过绝对。也许等我真的哪天能挣出那些钱,有所谓的资产了,才大概能更深刻地明白作者想聊的理论吧。</p> <h2>小总结</h2> <p>不论多少工具,AI 算法的出现,最后还是落在了要专注做事上。用主动且积极的态度去反思自己的日常行为,然后再用主动的心态,花些时间和精力泡在事情中,从简单开始再逐步拓展到复杂。</p> <p>对自己好些,不要太吝啬,好好享受当下,好好做事。</p> <hr /> <h2>谵语</h2> <p>写作有些像梦境,不知道从哪里开头,也不知道在哪里结尾。一行行或一段段的文字流于荧幕上,滚入到眼球,再滚出。回归到现实后,写作以不间断的形式一直在飘荡,从人的出生开始从不间断,直到死亡。</p> <p>梦和生命也很相似。生命也没有开头。从观者的角度看,生命降生到世界上是有迹可循的。性爱,怀孕,然后在产房生产。观者像读者,实践并感受新生命的降生。读者进入书店,买了本书,然后再拆封,开始阅读书中第一行的道理一样。但从生命自身角度讲,一切非常莫名其妙。自己如何被降生,自己又如何度过了最初在襁褓中的几年,是失去记忆的梦,没人会记得。稀里糊涂的开始上学,然后工作。</p> <p>结束了写作,结束了阅读,生命死亡了。它们都会让人感到惋惜,记忆随着时间逐渐模糊,渗入了神经中。观者在生活时,部分神经被激活,才会回忆起那时发生的事情。</p> <p>真实属于他人,只有自己在做梦。这点并不矛盾,不是说你我类似,于是你我都必须是真实的,或都在做梦。它们不是定义,而是描述。真实和做梦的阐述不来源于其自身,而来自你我,是我的经历和对你的观看,共同塑造了这个看似矛盾的架构。反过来,一切都是调和的了。</p> Java多线程的故事https://blog.kaiyikang.com/posts/2024/java_%E5%A4%9A%E7%BA%BF%E7%A8%8B/https://blog.kaiyikang.com/posts/2024/java_%E5%A4%9A%E7%BA%BF%E7%A8%8B/Mon, 30 Sep 2024 00:00:00 GMT<h2>线程和进程</h2> <p>Process 进程是 System 级单位,有独立运行空间,切换会有较大开销。</p> <p>Thread 线程是处理器 CPU 调度单位,同一类共享代码和数据空间,比 Process 轻量,切换的开销小。</p> <p>一个 Process 是一个 Java 程序的运行,包含至少一个或 N 个 Thread。</p> <h2>创建一个线程</h2> <p>创建一个 Thread,可以直接使用 Thread Class。或者使用以下三种方法自定义:</p> <ul> <li>继承 Thread 类:简单,无法继承。重写 run 方法。</li> <li>实现 Runnable 接口:复杂,可以继承。重写 run 方法。</li> <li>实现 Callable 接口:同 Runnable。重写 call 方法。</li> </ul> <p>其中,继承 Thread 类和实现 Runnable 接口,无法获得执行结果,无法处理执行异常。</p> <p>但是位于 <code>java.util.concurrnet</code> 中的 <code>Callable</code> 解决了该问题,call 方法是 run 方法增强版,可以在实现时,通过传入泛型的方式,定义返回值。</p> <p><strong>例子:继承 Thread 类</strong></p> <pre><code>class Scratch extends Thread{ @Override public void run() { System.out.println("Thread is Created"); } public static void main(String[] args) { Scratch thread = new Scratch(); thread.start(); System.out.println(Thread.currentThread().getName()); } } </code></pre> <p><strong>例子:实现 Runnable 接口</strong></p> <pre><code>class MyTask implements Runnable { public void run() { System.out.println("Task is running"); } } public class Main { public static void main(String[] args) { MyTask task = new MyTask(); Thread thread = new Thread(task); thread.start(); } } </code></pre> <p><code>MyTask</code> 类实现了 <code>Runnable</code> 接口,只定义了可执行的任务,但本身不是线程。<code>Thread</code> 类提供了机制,将任务与线程相互关联。</p> <p><strong>例子和解读:实现 Callable 接口</strong></p> <p>接口的定义是:</p> <pre><code>@FunctionalInterface public interface Callable&lt;V&gt; { V call() throws Exception; } </code></pre> <blockquote> <p>补充知识:<code>@FunctionalInterface</code> 是函数式接口,可以用 Lambda 表达实现:<code>ClassWithFunctionalInterface func = () =&gt; System.out.println("ok");</code></p> </blockquote> <p>Callable 是 Runnable 的补丁,<code>V</code> 定义了返回值,<code>Exception</code> 定义了异常。</p> <p>因为最终我们使用 Thread 实现多线程,而它只接受 Runnable,因此需要一个机制,连接 Callable 和 Thread。</p> <p>解决方案是 <code>FutureTask</code> 类。</p> <p>该类提供两个构造方法:</p> <ul> <li><code>FutureTask(Callable callable)</code>,可以和 Callable 关联。</li> <li><code>FutureTask(Runnable runnable, V result)</code>,也支持 Runnable。</li> </ul> <p>该类是 <code>RunnableFuture</code> 接口的实现,源码是:</p> <pre><code>public interface RunnableFuture&lt;V&gt; extends Runnable, Future&lt;V&gt; { void run(); } </code></pre> <p><code>Runnable</code> 负责 Thread,<code>Future</code> 负责得到来自 <code>Callable</code> 的返回值。</p> <p>总结一下逻辑关系是:</p> <p>Thread -<code>Runnable</code> + Callable - <code>Future</code> -&gt; <code>RunnableFuture</code> -&gt; <code>FutureTask</code></p> <p>用代码逻辑表示是:</p> <pre><code>// Callable Scratch scratch = new Scratch(); // To FutureTask FutureTask&lt;String&gt; future = new FutureTask&lt;&gt;(scratch); // To Thread Thread thread = new Thread(future); </code></pre> <p>完整的 <code>FutureTask</code> 使用例子:</p> <pre><code>class Scratch implements Callable&lt;String&gt; { @Override public String call() throws Exception { System.out.println("Scratch"); return "OK"; } public static void main(String[] args) { // Callable Scratch scratch = new Scratch(); // To FutureTask FutureTask&lt;String&gt; future = new FutureTask&lt;&gt;(scratch); // To Thread Thread thread = new Thread(future); thread.start(); try { // Get result from FutureTask String result = future.get(5, TimeUnit.SECONDS); System.out.println(result); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException | ExecutionException | TimeoutException e) { e.printStackTrace(); } } } </code></pre> <h2>线程的不同状态</h2> <p>通过 <code>new</code>,我们创建了一个 Thread 对象。在从生到死的完整生命周期中,它包括六个状态:new,runnable,blocked,waiting,time_waiting,terminated。</p> <p><img src="http://www.itheima.com/images/newslistPIC/1615964642254_%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E5%9B%BE.png" alt="" /></p> <p>具体如下:</p> <ol> <li><strong>New 新建</strong>:初识状态,不能运行。JVM 为它分配了内存,没有线程生命特征,和其它 Java 对象一样。</li> <li><strong>Runnable 可运行</strong>:New 状态,调用 <code>start()</code> 进入。它细分成两个状态: <ol> <li><strong>Ready 就绪</strong>:它没运行,只是做好准备,等待 JVM 调度。</li> <li><strong>Running 运行</strong>:真正运行。JVM 或 CPU 开始调度。</li> </ol> </li> <li><strong>Blocked 阻塞</strong>:因某些原因,失去 CPU 执行权,即 CPU 不执行它。一般会因两种情况进入阻塞: <ol> <li>[[同步锁]] 被其它线程截胡。</li> <li>线程运行时,发出 IO 请求。</li> </ol> </li> <li><strong>Waiting 等待</strong>:Running 中的线程,调用了 <code>wait()</code> 或 <code>join()</code> 方法,进入该状态。等待中的线程,必须等其他线程执行<em>特定操作</em>后,才有机会再次争夺 CPU 使用权,然后转为 Running。例如: <ul> <li><code>wait()</code>:等待其他线程调用 <code>notify()</code> 或 <code>notifyAll()</code> 唤醒</li> <li><code>join()</code>:等待其他加入的线程终止。</li> </ul> </li> <li><strong>Timed_waiting 定时等待</strong>:Running 中的线程,调用了 <code>sleep(long millis)</code>、<code>wait(long timeout)</code>、<code>join(long millis)</code> 等方法进入。类似 Waiting 状态。</li> <li><strong>Terminated 终止</strong>:<code>run()</code>、<code>call()</code> 执行完或者未捕获的 Exception、错误 Error,线程进入终止状态,生命周期结束。</li> </ol> <h2>切换不同状态</h2> <h3>New -&gt; Runnable</h3> <p>调用 <code>start()</code> 方法,启动线程,然后会自动调用 <code>run()</code>。</p> <p>单纯调用 <code>run()</code>,无法启动线程,参考下面的例子:</p> <pre><code>public class StartVsRunExample { public static void main(String[] args) { // 创建一个实现了 Runnable 接口的匿名类 Runnable task = new Runnable() { @Override public void run() { for (int i = 0; i &lt; 5; i ++) { System.out.println(Thread.currentThread().getName() + ": Count " + i); try { Thread.sleep(500); // 睡眠500毫秒 } catch (InterruptedException e) { e.printStackTrace(); } } } } Thread thread1 = new Thread(task, "Thread-1"); thread1.start(); Thread thread2 = new Thread(task, "Thread-2"); thread2.run(); // 主线程继续执行 for (int i = 0; i &lt; 5; i++) { System.out.println(Thread.currentThread().getName() + ": Main Count " + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } </code></pre> <p>输出:</p> <pre><code>main: Count 0 Thread-1: Count 0 Thread-1: Count 1 main: Count 1 main: Count 2 Thread-1: Count 2 main: Count 3 Thread-1: Count 3 Thread-1: Count 4 main: Count 4 main: Main Count 0 main: Main Count 1 main: Main Count 2 main: Main Count 3 main: Main Count 4 </code></pre> <p>其中,<code>main: Count 0</code> 在主线程中运行,不在线程 <code>thread2</code> 中。</p> <h3>Runnable -&gt; Block</h3> <p>例子:</p> <p>两个线程,抢通过定义 Object 为 <code>lock</code>,:</p> <pre><code>class BlockThread extends Thread { private String name; private Object lock; public BlockThread(String name, Object lock) { this.name = name; this.lock = lock; } @Override public void run() { System.out.println("Thread " + name + " State is "+Thread.currentThread().getState()); synchronized (lock) { System.out.println("Thread " + name + " hold the lock."); try { System.out.println("Thread " + name + " State is "+Thread.currentThread().getState()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread " + name + " released the lock."); } } } public class Scratch { public static void main(String[] args) throws Exception{ Object lock = new Object(); BlockThread t1 = new BlockThread("Thread1", lock); BlockThread t2 = new BlockThread("Thread2", lock); t1.start(); t2.start(); Thread.sleep(100); System.out.println("T1 state is "+t1.getState()); System.out.println("T2 state is "+t2.getState()); } } </code></pre> <p>输出:</p> <pre><code>Thread Thread2 State is RUNNABLE Thread Thread1 State is RUNNABLE Thread Thread2 hold the lock. Thread Thread2 State is RUNNABLE T1 state is BLOCKED T2 state is TIMED_WAITING Thread Thread2 released the lock. Thread Thread1 hold the lock. Thread Thread1 State is RUNNABLE Thread Thread1 released the lock. </code></pre> <h3>Runnable -&gt; Waiting</h3> <p><code>join()</code>:让其它线程进入自身,使用 <code>wait()</code> 机制实现。</p> <p>例如,当 Thread A 执行 threadB.join() 时,线程 A 会进入 WAITING 状态,等待 ThreadB 结束后,再继续。</p> <p>例子:</p> <pre><code>public class JoinExample { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -&gt; { System.out.println("Thread started"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread finished"); }); thread.start(); System.out.println("Main thread waiting for thread to finish"); thread.join(); // thread join main System.out.println("Main thread continues"); } } </code></pre> <p><code>wait()</code>:Object 类方法,每个对象都有,必须在 synchronized block 中使用 ([[同步锁]])。使进程进入等待,直到其他线程,调用同一对象 <code>notify()</code> 或 <code>notifyAll()</code>。</p> <p>使用方法:</p> <pre><code>synchronized (object) { while (条件不满足) { object.wait(); } // 条件满足,继续执行 } </code></pre> <p>它区别 <code>sleep()</code>:</p> <ul> <li><code>sleep()</code> 不释放锁,<code>wait()</code> 释放锁。</li> <li><code>sleep()</code> 暂停执行,<code>wait()</code> 用于进程通信</li> </ul> <p>例子:</p> <pre><code>class Scratch { public static void main(String[] args) { SharedResource resource = new SharedResource(); Thread t1 = new Thread(() -&gt; { try { resource.use(); } catch (Exception e) { e.printStackTrace(); } }); Thread t2 = new Thread(() -&gt; { try { Thread.sleep(2000); resource.prepare(); } catch (Exception e) { e.printStackTrace(); } }); // not ready, waiting... t1.start(); // ready, notify! t2.start(); } } class SharedResource { private boolean isReady = false; public synchronized void prepare() { isReady = true; notify(); } public synchronized void use() throws InterruptedException{ while (!isReady) { wait(); } System.out.println("I am ready"); isReady = false; } } </code></pre> <h3>Waiting -&gt; Runnable</h3> <p><code>notify()</code>:通知 <code>wait()</code>。</p> <p>例子见上一子章节。</p> <h2>参考</h2> <ul> <li><a href="https://blog.csdn.net/qq_38721537/article/details/124581201">Java中Thread详解(一篇就够了)_java thread-CSDN博客</a></li> <li><a href="https://github.com/cosen1024/Java-Interview/blob/main/Java%E5%B9%B6%E5%8F%91/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%80%BB%E7%BB%93%E7%89%88.md">Java-Interview/Java并发/Java多线程总结版.md at main <code>cosen1024/Java-Interview</code> GitHub</a></li> <li><a href="https://www.itheima.com/news/20221107/102713.html">Java多线程:线程的生命周期的六种状态</a></li> </ul> 不协调https://blog.kaiyikang.com/posts/2024/%E4%B8%8D%E5%8D%8F%E8%B0%83/https://blog.kaiyikang.com/posts/2024/%E4%B8%8D%E5%8D%8F%E8%B0%83/Sun, 23 Jun 2024 00:00:00 GMT<h2>人多的好处</h2> <p>身处大城市中,人多常常暗示着拥挤和等待。</p> <p>不过这种状况也并非完全。当你在匆忙赶地铁的时候,倘若看到在你要出发的站台上看到了很多人站着等待,这传递出了非常好的信号——车辆即将抵达,你将可以马上离开。</p> <p>同理,如果你看到站台上等待的人很少,意味着车刚才已经驶离,你不得不在站台上等待一个完整的周期,并看着人们逐渐的在身边聚集。此时此刻,「人多」所传递出的,不再是快速的离开,而是拥挤与等待。</p> <h2>衣领子</h2> <p>某天我在服装店里闲逛,忽然看到假人模特身上,套着一件领子格外别致的衣服。由于领口没有缝合线,尖尖的领子从脖子位置平整地延展上去,自然且松弛地翻垂下来。我对它的德语名称产生了好奇,通过手机戳戳点点,查到领子被称为「Kragen」。为了记住这个词,我在脑子里不停地重复它。</p> <p>当天是德国的自豪日,我刚从店里推门出来,一股汹涌的人潮就向我的面门奔涌而来。人们着装各异,有遮盖乳房,尝试露出上半身的「男人」;有性感穿搭,穿着吊带小裙的「女人」。各类要素随意排列组合,毫无章法地倾泻了出来。</p> <p>我看着形形色色的人,又想到「Kragen」,心中不由得去对比这两个概念,遂而隐约察觉到,人的个体作为概念的渺小与无力,以及「Kragen」相对更加「永恒」一些。</p> <p>比方说,我的名字是 YK(用缩写举例)。假设它不重名,那么 YK 作为一个概念,是有能力指代并且描述我,「YK 是怎样的」或「这个有 YK 的感觉」等说法也都成立。把我作为个体抽象为 YK 与「Kragen」比较,无论站在什么视角,都无法与其竞争。从时间上,领子的德文早在 1600 年就出现了。从地理上,它广泛传播在大德语区,每当你同当地人讲「Kragen」的时候,对方都能明白,这代表着领口的那片布。</p> <p>我作为 YK 的缔造者和诠释者之一,颇有些无力与挫败感。原以为,人可以更强大些,但其实作为普通人,名声都比不过衣领子。从另外一个角度出发,有些人的名声却能流芳百世,比方说「Transformation de Fourier」。</p> <p>即便说了这些,一般人也无太大必要始终和抽象概念过意不去。毕竟衣领子只能被摆在塑料假人身上,没办法像人那样,吃穿住行,并感受着欢喜和悲伤。</p> <h2>时间的格式</h2> <p>我始终没有在苹果手表上寻找到带有合适功能的表盘。</p> <p>我的需求很简单,数字钟作为主体,并附有四个附加功能,日期,天气,气温和倒计时。我尝试换了不同的表盘,要么表盘不显示数字,要么附加功能不完全。</p> <p>时常,我就不得不在智能手表上使用传统机械时钟的表达方式,去作为日常赶车的参考。经常用地图或软件查车的人都知道,所有时间都会精确到阿拉伯数字,而现代公交的管理系统也是根据根据的具体的数字时间。因此这就产生了冲突,模糊的我,匹配不上清晰的它。</p> <p>机械时间指针给我带来的是模糊的质感。有时虽然我欣赏并享受时间呈现的闪烁其词,但每当它是这样的时刻,我都不需要知道精确的数字,因为在那时,我更关心檀香的长度或天空色彩的变化。反过来,如果我需要精确数字的时刻,例如赶车,但它没有及时出现时,模糊性只会放大内心中的不踏实和不安的感觉。</p> <p>这是种皮笑皆非的错位感。发车的时间和智能手表,本应通过精确的数字对齐。但智能手表就是要模拟出表盘时间,就是要通过确凿但复杂的算法,把本就精确的数字模拟成粗大的像素点,从而在人工干预的情况下,给我们的使用造成障碍。</p> <p>通过电子模拟出来的浪漫和怀旧,还请在首先实现基础功能的情况下进行,不然总会显得过分谄媚。</p> 关于涟漪的联想https://blog.kaiyikang.com/posts/2024/%E5%85%B3%E4%BA%8E%E6%B6%9F%E6%BC%AA%E7%9A%84%E8%81%94%E6%83%B3/https://blog.kaiyikang.com/posts/2024/%E5%85%B3%E4%BA%8E%E6%B6%9F%E6%BC%AA%E7%9A%84%E8%81%94%E6%83%B3/Sat, 11 May 2024 00:00:00 GMT<p>人如果是一片水池或一个空间,人的思维,发出的任何声响还是动作,都会让这片水塘泛起涟漪,也会在空间中增加物体。涟漪的复杂,空间中物体的多寡,都会随着人思维和运动的频率不停变化。如果思维太多,太剧烈,空间里的东西会变得细碎,容易扬起沙尘,而涟漪的波浪则会破碎,成为细小的水花。不论是沙尘还是水花,容易随着感受或思维的震荡变得无法保持稳定,升起并在空间中飞扬,生命力与创造力在此刻达到顶峰。水波停止并不在摆动,扬尘被清扫,空间变得重新洁净起来。这看起来像是一种违背活着的状态,活着,意味着运动与创造,只有动起来才会在外部世界留下痕迹,他人也能因此将你识别为活着的状态。不过,他人的话语也仅仅是一家之言,我们能感受自己空间的存在和震颤才更加的重要。顶着要活着的正流,尝试不要去「活」,说是一种矛盾,也同样是一种张力,而这种冲突和对立的力量,正是能够被自己察觉,从而从自身的角度感受到自己在活着,即活的涟漪能够被活自身所识别。于是,人就需要外物的确认,任何的扬尘和涟漪都只能存在于自我的空间中,只是知晓了这一点,那么沙尘和水雾也就成了冗余的事物。</p> <p>当人意识到矛盾发生的时候,统一也随之来临。自我的内部包裹着自我的外部,然后再次循环起来。人变得整体起来,知道什么是活,也知道了什么是彻底的宁静,从而不再惧怕死亡,因为那是连续的,圆融的,安静且祥和的。</p> Kaş - 初入卡什https://blog.kaiyikang.com/posts/2024/%E5%8D%A1%E4%BB%80_%E5%88%9D%E8%AF%86%E5%8D%A1%E4%BB%80/https://blog.kaiyikang.com/posts/2024/%E5%8D%A1%E4%BB%80_%E5%88%9D%E8%AF%86%E5%8D%A1%E4%BB%80/Wed, 04 Sep 2024 00:00:00 GMT<p>凌晨一点,我们趁夜色刚降临之际出发,为了追赶最早五点的航班。夜很静,以为最后一班的公交车空空如也,结果上车才发现乘客还真不少。看上去,大家都专注在自己世界中,车里比车外似乎还要安静。</p> <p>到了换乘车站,站在站台等车,看到铁轨的另外一侧还有工人在做焊接工作,不禁想起德国到处长期停滞的工地,感概道,原来真的还有工人在工作啊。有人在创造秩序,自然也有人在破坏秩序。夜幕下,喝醉的人也不少。不远处就有个人,身着浅色的背带裤,提着瓶酒蹲在站台边上,等了许久才站起身来,摇摇晃晃背着向远处走去。对方的姿态从未变化过,只从屁股缝隙处多出了些棕色的脏污垢。不想去大胆猜测,但又不得不猜测。</p> <p><img src="@assets/2024/IMG_1434.JPG" alt="夜晚的站台" /></p> <p>到了机场,人稀稀疏疏的,没有想象中的那么空旷和寂寥。登着check-in之前,我们找了一处椅子坐下。机场是全天候开放的,因此也吸引了不少流浪汉,为了防止躺在椅子上,就在座椅中间「特地」加入了栏杆。不锈钢质地的栏杆,防了流浪汉,也防了我们在机场休息的打算。半梦半醒,环顾四周,有人已经睡着,男士们则在看游戏视频,打发时间。</p> <p>约莫四点,前台的人终于上班了。等待行李托运完,我们随着人流上楼,等待安检开门。左等右等,一个多小时都不见开门的踪影,这时前排的人逮着路过的工作人员问了问,这才知道,大部分人都排错了队伍。我们在D,实际要去B。D口因为没有早航班,没开门,B口早开了。于是,太阳还没亮,一大帮人火急火燎的又冲到B口。</p> <p>半梦半醒的时候,人的注意力和逻辑是断片的。比如,过边检的时候,我把CH错认成了CN,还以为自己的护照也能走快捷通道,结果门口的阿姨立马把我拦住,叫我去远处的窗口。我这才意识到自己眼花,低下头快步朝着另外一个通道走去。</p> <p>六点,我们上了飞机。因为订的廉价航空,所以即使要确定座位也要额外加钱。但这里有一个额外小贴士是,如果留意订票过程可以发现,完全可以省略订座的流程,在check-in的时候自动分配座位,这样就是完全免费的。实际上了飞机,后半部分几乎没有什么人,完全可以躺下来睡觉。由此可以看出,廉价航空引诱你花钱的策略还是非常多的。</p> <p><img src="@assets/2024/IMG_1449.JPG" alt="朝阳" /></p> <hr /> <p>下了飞机,空气很热,但有些许微风吹过,因此并不闷。</p> <p>沿着下飞机的通道走,右侧是中庭,透过玻璃,能看到楼下的迷你儿童乐园。乐园中的玩具泛着塑料的旧光,颜色虽然浓郁,但部分油漆已经脱落,颇有种国内三四线乃至乡镇大商场的乡土气息。</p> <p>边检的通道一字排开,坐落在大厅中央,乘客自由地朝着边检口涌去。通常印象中的边检,都是庄严肃穆的警察局窗口,到了这里,却成了火车票卖票口,丝毫没有章法可言。我们特地打印出了签证,为的是防止太严格,不给过关,打印出来的纸递了出去,结果对方都没折开,在护照上盖了戳子,就直接给放行了。</p> <p>等待行李的大厅也维持了同样格调。等行李期间,我想着去厕所一趟,短暂行个方便。厕所不出所料的很臭,原因自然很简单,每个包间内部都有个巨大的废纸篓,什么样的纸张都在里面堆积着。还记得之前在哪里看见过,说的是一些地方的基建落后,厕所下水管道比较窄,放厕所纸容易堵,因此不得不使用废纸篓。另外还有个发现,是在另外一个隔间有幸发现了蹲厕。这种在欧洲极为罕见,但在亚洲很常见的如厕方式,竟然存在于中亚的交汇之处。正想着要感叹之余,眼票见旁边有根水管子,却也不见得类似的垃圾桶,不由又一阵细思极恐。</p> <p>拿到行李出了海关,转角是个换汇的地方,欧元对里拉,1比29。这个数看起来很大,熟不知到了市中心,汇率立刻成了1:36。我可算明白机场的心思了,明明可以用抢的,却还额外附加换汇服务,真是用心良苦。相比那些挣来的多余的钱,都用来修缮店面了,难怪硕大的机场,就属这个小窗口最精致现代。</p> <p>完全站在外面,即使身处阴凉下,不出片刻背后就已经湿透。这让常年呆在寒冷德国的我不得不要额外适应一番,忍住燥热挑拨出的躁动,尝试冷静思考下一步的动作。根据网友信息,出门应该有个轻轨站,但我们当前站的位置,门前是大片的停车场,怎么也不像是有轻轨的地方。于是我们尝试顺着人流逐步摸索,旅行社的宣传亭子首先不需要碰的,再来出租车也是碰不得,毕竟土耳其的出租车在中英文世界似乎都得不到待见。新世界加上高温度的双重夹击下,我们的脑子缩成了单线程,最终采用了最原始朴素的方案,问路。</p> <p>这一问,就问了一条街。因为很多当地人并不懂英语,相互理解起来非常吃力,所以相同内容,需要反复问不同的人。有门口的保安,铁柱子下休息的工人,还有年轻的小情侣,虽然人各有不同,但都笑憨憨的,也没有什么不耐烦的样子。再经过多同他人多次确认之后,我们得出了结论,只有先乘坐公交大巴或出租车,从2号航站楼乘车到1号航站楼,然后才能坐上大巴。大巴公交车属于市政交通,需要买全票上车,换句话说,机场本身不提供任何中间接驳服务。</p> <p>在接受了这个事实后,立刻又要接受另外一个事实是,如此重要的车站站点,竟然就孤零地站在大门的一侧,没有提示牌,没有时刻表,仅有的也不过是陈破的广告。所谓大隐隐于市,却没想到以这种方式。看到已经有个小哥坐在了那里,我们才勉强确认这姑且是个在运营的车站。等待之余,我们和小哥闲聊,彼此互报家门,知道他来自俄罗斯,独自过来旅游。聊着聊着听他说,他在这里已经等了一个多小时,我们的心一下子就疲倦了起来。也许是上天喜欢戏耍旅人,我们嘴边的话还没升温,车缓缓的开过来了,刚铺盖卷儿的心,又随着车里空调忽然又重新蹦起来了。</p> <p>拿信用卡刷了车票,等待片刻,车启动了。车一路跑,我一路盯着手机里的导航看,生怕上错了车,跑到了什么犄角旮旯里去。等屏幕中的小箭头与规划的路线重合了,我悬着的心终于才放下,安安心心朝着下个目的地前进。</p> <p><img src="@assets/2024/IMG_1459.JPG" alt="车站" /></p> <hr /> <p>城轨车站不算难找,就在航站楼门口不远处。上了电梯,进站口延续了之前车站的乡土气,弱不禁风的闸机,还有孤零零的售票机器。</p> <p>一位女士站在售票机前忙碌的操作,看到我们过来,仿佛等到了救兵,用着简单的英文向我们求助。这是典型的问道于盲,我们凑过去看屏幕,尝试切换成英文戳戳点点,但遗憾的是,即便每个词都认识,但真的不知道这个票应该如何购买。系统没有额外的提示信息,也没有具体的站名可供选择,只提到说什么5次或11次Usage之类的云云,全然的莫名其妙。</p> <p>我们几个面面相觑,眼神也往返了几个回合,但买票的事情却一点进展都没有转机,直到一对情侣路人的出现。</p> <p>这对路人看着年轻,似乎是本地人,它们看了看机器,却也太说不明白买票的操作逻辑,再加上语言沟通不畅,那个男的想不出更好的办法,干脆一不做二不休,拿着自己的交通卡给我们所有人买票,加上他共五个人。印象中,一张票20里拉,虽然总价不贵,但这股热忱却十分珍贵。我本想支付他现金,但现在钱包中一张里拉钞票都没有,见对方也没有想收的打算,只好作罢,嘴里连忙说着感谢。</p> <p>沿着轨道走一小段,车就在前面等着我们。由于这里是始发站,且线路简单,所以也不必担心做错方向。车厢内人不多,空旷且安静,我们找了个座位坐下,空调开的很足,心和思维也稍微镇定了下来。</p> <p><img src="@assets/2024/IMG_1460.JPG" alt="很现代的轻轨车厢" /></p> <hr /> <p>长途汽车站坐落在城郊偏西北的方向,周围的建筑稀疏,比不上城中的热闹。我们顺着指示牌走到门口,太阳依旧灼热。车站是个巨大凉亭样子,亭顶带着橙红色的锯齿状,稀疏的钢制框架布满了锈迹和污渍。或棕色或透明的玻璃围绕起来成了大厅,上面有很多胶粘和碎纸的痕迹,想必一些告示已经被反反复复贴了好几回。哪里漏水,哪里地板塌陷,就拿个红白相间的警戒线围住,做个松松垮垮的补丁。</p> <p>过了如同充话费赠送的安检门,大厅里坐落着不用大巴车公司的柜台和办公室,似乎来的不是时候,里面一半都空无一人。我们要去 Kaş,于是在每个柜台的信息告示处浏览,没一会儿的功夫就找到了。</p> <p>「Kaş?」,「Kaş!」这几乎就是交易完成所需的全部对话了。</p> <p>15分钟后离开,前台的大爷和我们说。我们一听时间似乎有些紧张,毕竟身上携带了很多行李的同时,还要简单休整,所以也没货比三家,当即拍板决定了。一切准备妥当后,我们顺着大爷招呼的方向沿着背后的大门走去,那里还有另外的大爷在做接应。我同他确认了目的地后,把拖箱搬进内行李舱。</p> <p>车厢内前排已经坐了人,大多是土耳其人,游客反倒不算多,我们顺着找了位置坐下。座椅是暗淡的红色,破旧的华丽感。</p> <p><img src="@assets/2024/IMG_1463.JPG" alt="大巴车内" /></p> <p>嘴上说着15分钟离开,但等了20分钟也不见动静。我们坐在那里观察司机,发现了规律。网上都说土耳其的长途车不准时,也找不到固定的时刻表,原因全在于人。虽然他们说的是土耳其语,但我都可以脑补出来「里面有大座儿啊,去Kaş,还有十分钟出发了,过时不候咯!」。一切的准则都是以填满座位为第一优先,只有等待填得差不多了,才会出发。在我小的时候就已经领略过了类似的氛围,如今在他国的土地上再次经历,颇有一种考古的韵味,而且非常的「国际化」。</p> <p>司机师傅,不出所料还是一位大爷,是长途大巴另外的一个鲜明标志。白头发,黑眉毛,方正的面孔,虽然因为年龄,脸上带着些皱纹,但眼眉中透露着精神气。他穿着白色的Polo衫,黑色西裤皮带和小皮鞋。褶皱却锃亮的衬衫用料并不高级,里面的白色跨栏背心全都能透出来。</p> <p>这样的穿着和与作派,无疑都在诉说着这样的特征,老练与专业。像这样的人去开大巴车,速度多快你都不会慌张的。我都能想象到一连串的画面,司机大爷游走在崎岖的山间车道,开了十几年的路像是在后花园遛弯一样,如同打太极般游转着手下的大方向盘,路在颠簸,乘客在遥移,但大爷内心的沉着冷静却是永恒不变的真理。</p> <p>对这样的角色,我从不会吝惜赞美之词。</p> <p>大巴车终于开动了,但不能高兴得太早,因为这里要提及大巴车从不准时的第二个理由,是司机中途会拉人。大概开出不到几公里,能感受到车缓缓地停下,似乎交涉了一阵,上来了个人。他看到座位已经满了(对,此时已经满了),司机会从架子上面抽出来个垫子,放在驾驶座旁边,过道一头的位置,像极了被罚学生坐在黑板边上的 VIP 位。人做好,司机就位,车这才继续行驶。</p> <p>像这样的临时停靠,印象中十个手指都数过来。因此在印象中,我们的旅程变得非常冗长与沉闷,如同车上的空调一般,靠近摸上去在出冷风,但其实一点冷气也感受不到。幸亏我们需要大量的睡眠,睡眼惺忪后,最终抵达了 Kaş。</p> <hr /> <p>又经历了些反复询问和小的波折后,我们终于在傍晚抵达了在 Kaş 的住处。</p> <p>虽然仅过了不到一天,从清晨的出发,到傍晚的到达,但却身体和精神却仿佛经历了许多天。新的环境与信息扑面而来,大脑与思维在不停的认识它们,并且解决突如其来的问题。真刺激,也真的足够让我们印象深刻。</p> <p><img src="@assets/2024/IMG_1485.JPG" alt="傍晚抵达卡什" /></p> Kaş - 浮潜https://blog.kaiyikang.com/posts/2024/%E5%8D%A1%E4%BB%80_%E6%B5%AE%E6%BD%9C/https://blog.kaiyikang.com/posts/2024/%E5%8D%A1%E4%BB%80_%E6%B5%AE%E6%BD%9C/Sun, 08 Sep 2024 00:00:00 GMT<p><img src="@assets/2024/R0000002.JPG" alt="石头滩" /></p> <p>当脚踏着鹅卵石,朝着地势更低走过去,海水会逐渐的浸没足,小腿,膝盖,最后是大腿乃至全身。太阳能量很强烈,乃至皮肤都能感受到纤细的灼热,海水却因为吸收了能量,变得很温和,没有任何寒冷的刺激感。而被水浸过的皮肤,像是糊了一层盔甲,一扫炎热的灼烧感。</p> <p>身体逐步适应了海水,进一步地,脸乃至整个头部都要扎进海水里。那些与水十分熟悉的人,会丝毫不犹豫地扎进水里,一个转身的功夫已经找不到人影了。与其相反的人,例如我,会将身体泡在水里后,面部朝下,对着海面试探,发呆。澄澈的海水已经足够让我见到水下的地面与岩石,但是否继续朝着它靠近,我不确定,大概是因为需要心理准备和一些胆量。</p> <p><img src="@assets/2024/R0000003.JPG" alt="飘进海里" /></p> <p>轻微调整了一下游泳面罩,脸部慢慢浸入水中。开始是鼻子与嘴巴,再来是脸颊,然后是耳朵,最后是头。细微体验,会发现皮肤和五官的触感是联动的,仅仅是一小片肌肤进入水中,体验却像是所有的五官都完全进去了,但当然这仅仅是个错觉。不能贸然被这种错觉所吓倒,然后错误的马上把头抬上水面,而要继续向水下挺进,直到所有的五官全部被封闭。</p> <p>水的温热并不会牵动五官,让我觉得过度慌张。但除了温度与触感的改变外,最大当属听觉上的变化。人类的吵闹声会瞬间被清空,一切变得安静的同时,水浪声会在耳畔徘徊,时不时还能听见微小的气泡声。如果此时戴了呼吸装置,例如浮潜的管子,或是水肺的呼吸管,从身体内部传来的浓厚呼吸声,则是所有声音中最突出的那一轨道。呼吸,还是呼吸。呼吸被尘封在身体盒子中许久,这一刻被海水打开了,逃逸了出来。我的身体退居到了舞台后面,成为搬运气息的帮工,一切灯光都聚集在了呼吸上。</p> <p>我仍然没有完全适应被重新计算过权重的身体,不清楚如何调整,只好大大地睁开眼睛,盯着水底的石头和沙土,任由呼吸声在四周飘摇。</p> <p>腿小幅度向前咕涌,头深入水中,直至完全淹没。等到眼前的景色看的差不多了,就得一鼓作气,将双脚向后撤,借用海水的浮力把身体完全抬起。抬起后的身体并非笔直,而是有些摇摆,将四肢弯折呈现出游泳的姿势,开始准备遨游浅水下的世界。</p> <p><img src="@assets/2024/IMG_1931.JPG" alt="海下的石头和脚" /></p> <p>随着地势和视野的关系,水下的风光十分飘忽,从不同的位置进入,看到的也大相径庭。</p> <p>从海滩处浮潜,水草不多,多是沉底的沙粒和石子,随着离陆地越来越远,地面越来越深,陆续出现了巨大的礁石。肉眼可见的,光线逐渐被打散开,远处黑黑的,礁石也黑黑的,躲藏在深海的背后。我漂浮在海面,看着愈行愈远的水底产生了错觉,大地仍旧在延续,而我却飞升了起来。远方的巨大黑暗峡谷变大了,恐惧的心也升了起来。我很小,用了很多力量向前游动,肉眼看到的位移却小到可以忽略,能量都被黑暗的深海吞没了。于是我决定停止继续,转身回去爬山,爬回到高地,爬回到沙滩与岸边。</p> <p><img src="@assets/2024/IMG_1806.JPG" alt="海草" /></p> <p>相比于海滩,从 Boat Trip 途径的海湾处开始浮潜,能看到的景象有些许不同。定是经过船长导游们精心挑选过的地方,海里的元素丰富许多,除了平整的沙地外,还有许多稀疏的水草。礁石的形状更加丰富,除了自然的,还有经过人工雕琢过的,那是些已经沉入水中的遗迹。有处遗迹围城了一个矩形,像是一座水下的泳池,不清楚它的用途,但表面已经崎岖不平。脚踩在上面需要注意,不小心就会被划伤。</p> <p>也许是位置远离人烟的原因,海中的鱼类变多了。虽然不像在海洋馆看到的,或在宣传册中看到的鱼群那样丰富多彩,大概是滤镜和现实的差别,但有幸见到还是令人愉快的。窸窸窣窣的小鱼穿梭在岩石缝中,如果用手尝试去够,它们会立刻察觉,然后飞速逃开。鱼群是少见的,但我仍见到了一次。似乎是物理中的磁场或电场线。每一条鱼都十分纤细和小巧,身体会随着海洋漂浮并反射出光线。它们是粒子探针,聚集在一起,有节律的闪烁着光线,巧妙地刻画着海洋的流动轨迹。近看颇为精巧且壮观。</p> <hr /> <p>从海中回到陆地,我立刻怀念起不久前的感觉。</p> <p>那个世界很宁静,只有海浪和自己的呼吸声,仿佛和自己变得更亲近了,当然和自然也同样是。四肢和躯体可以肆意扭动,旋转,浮力战胜了重力。站在陆地上,身体虽然踏实了,但有些异样沉重的幻觉,像是灵魂的自由没了。耳畔除了海浪与风声,再来就是人类的喧闹声,这定不会让我感到愉快。</p> <p>大陆不断在延伸,我却要背着它走去,重新过我的生活,骑着小摩托车,吃我的馕饼,我会想念它们的。</p> <p><img src="@assets/2024/R0000001.JPG" alt="海边" /></p> Kaş - 陌生的交易https://blog.kaiyikang.com/posts/2024/%E5%8D%A1%E4%BB%80_%E9%99%8C%E7%94%9F%E7%9A%84%E4%BA%A4%E6%98%93/https://blog.kaiyikang.com/posts/2024/%E5%8D%A1%E4%BB%80_%E9%99%8C%E7%94%9F%E7%9A%84%E4%BA%A4%E6%98%93/Fri, 06 Sep 2024 00:00:00 GMT<p><img src="@assets/2024/R0002207.JPG" alt="租车" /></p> <p>卡什镇子不大,就有一条主街道,但汽车却不少,而街边剩下的位置,都被小摩托车占据了。在我心目中,小摩托车是个非常适宜的交通工具。它不大,可以穿梭在房子中间;它有动力,能应对上下的山势;它续航足,去偏僻的街区或海边很方便。在这个小镇常见摩托车的身影,我们不打算租汽车,但为了增加活动半径和灵活度,决定试试小摩托车。</p> <p>在地图上,我们找到了第一家租车店。傍晚散步过去,只见得店门大开,门口稀稀疏疏停着摩托,张望了半天,却也找不到店主的身影。我们踏进门,尝试叫了几声,却也没有回应。因此,我们无功而返,计划着晚饭过后再回来看看。</p> <p>晚饭过后,我们回来的时候,就见到了店主大爷带着孩子在旁边吃东西。他看到有客人上门,憨憨地跑到跟前向我们介绍。他英文很好,说车是 400 里拉一天,没有押金。如果有事故且是对方的责任,那么对方保险负责,如果是我们的责任,那我们该赔多少就多少。<em>听上去是相当详细的介绍,但爱人的提醒,即时拉住了我这匹即将脱缰的野马。</em></p> <p>我听见没押金,感觉价格大致能接受,脸上浮现出了便宜到惊讶,想要快速成交的轻松神情,但爱人之前调查过,见网上说是有 150 一天,因此谨慎不少。统一口径后,我们回复大爷,说是再回去想想,如果决定了就第二天来租。</p> <p>土耳其的交易,多看是不是本地人,看是不是说的本地话。是,则友情价,不是,那就可以靠着绝对的信息差坐地起价。因此作为外国游人,做任何交易的时候,还真应该留个心眼。我反思,像我这种喜形于色的,就是个反面典型,最容易在其中被宰的。</p> <p>第二天,我们在地图附近标记了三四家,打算一家家挨着过去问。</p> <p>第一家离港口最近,门前也最为吵闹。店主在忙着和另外的顾客谈事情,见我们过来,就招呼另外一个靠近门口的年轻小哥和我们聊。他不说话,沉默良久,从兜里掏出手机,在翻译软件中颇为熟练地输入土耳其语,翻译过来是你们想要租什么。我们用英文作答,说想要租小摩托车。他回复要只需要 150 里拉,很棒的价格,但条件是需要有经验。本着诚信原则,我们诚实地告诉他说我们没有经验,还能不能租。反反复复「对话」了几轮,结果都是不能。最终我们只好作罢,道谢过后前往下一家。走在途中,有个问题萦绕在我脑中,既然他能听懂英文,为什么还需用翻译软件呢?着实令人百思不得其解。</p> <p>第二家在不远处,从远处就能看到梳着别致油头的年轻人坐在空调房里,一边闲聊一边玩手机。见我们过来,店主马上放下手机向我们打招呼。开了玻璃门,为了节约时间,我们确认了 150 里拉的价格,开门见山地问没有经验行不行,得到的回答和刚才一致。店主还特别嘱咐了一句,要有非常熟练的驾驶技巧才可以。随即转过话头,马上推荐让我门租车驾驶。<em>听到这句话,爱人露出了怀疑的表情。持有德国的驾照本来可以不论有经验与否在土耳其驾驶小摩托车,这会不会是商家的一种为了引导顾客去租利润更高的小汽车的一种营销手段??</em> 我们依旧无法,因此只好扫兴得离开。</p> <p>烈日炎炎,我们被高温和连续的拒绝搞得晕头转向,遂决定,如果最后一家也不行,那就转回头问第一家,贵也就贵点儿吧。朝着山坡走不远,我们就来了最后的租车店。这家店虽然评分高,但实际只有几个人评论,我心里有些将信将疑。店面没有豪华的配置,应该说是对比之中最朴素的,昏暗的房间没有空调,破旧的木质桌子和椅子,墙上和玻璃上贴着陈旧的日历和海报,门口似乎也没有几辆像样的摩托车。</p> <p>进了店,靠在桌子一侧的人和我们打了招呼。同样的话术,我们又强调了我们有允许开小摩托的德国驾照,只不过没有相关经验,并询问能不能教我们开小摩托车。听闻我们的询问,对方报价 400 里拉,也确认没经验没关系,愿意教我们。被两家店折腾之后,我们的心灵仿佛如沐清凉的空调,看到了自由奔驰的希望。简单合计,我们就这样决定了。因为现金不够,我们打算用欧元付,他说没问题,只不过现在手头没有里拉现金,打算之后还车了以后还多出的钱,写字条为证。</p> <blockquote> <p>经过市场调研以后,我们也摸清了市场行情,即租小摩托车必须要有当地认可的驾照,例如持有德国B驾照。如果是中国驾照,则需要翻译件或国际驾照。其次,价格分乘有经验和没经验两档,有丰富驾驶经验 150 里拉,没有驾驶经验 400 里拉。最后,虽然小摩托车没有押金,但是也没有任何保险。</p> </blockquote> <p>成交之后,他没着急出门带我们看车,而是用土耳其语打了一通电话。我们不解其意,他说等等,车马上就来。我们呆站在门口等了会儿,只见得一个骑着黑色小摩托车的大爷飞驰而来,停稳到店门口,下了车,伸手给我们指,示意这就是我们要的车了。</p> <p><img src="@assets/2024/IMG_1734.JPG" alt="就是这辆" /></p> <p>车不大,非常破旧,但感觉饱经风霜,看着很皮实。我们交了钱,店主从店里拿出了自带复写纸的表格。他们两人用车椅子当桌子,对着我的驾照和证件一通记录,其间嘴里还说了很多听不懂的土耳其语,感觉,大爷咖位是比店主要大上不少,一通指指点点。等单子填完,他们就指导我开始如何点火,按油门,停车等等操作。可能是因为上两次碰壁的关系,我生怕他们会因为我技艺不佳,导致看了我尝试操作之后,收回租车,所以我用尽浑身解数尝试记忆,并尽可能在下一步的实验环节中复现刚才的操作。结果大概是差强人意的,起码没有在起步的时候就把车挂着碰着,试着在院子里转了个踉跄的小圈圈之后,我拍着摩托车的椅子说,没问题,很好骑!</p> <p>试验结束,我心想也该结束,然后付钱了,但很可惜没有,店主说,我需要跟着他去去另外一个地方,交钱,然后我在独自把车开回来才行。我懵懂的满口答应,随即坐在了副驾驶。大爷一个箭步就上了主驾,指指我的头盔,示意我戴好,然后熟练地启动了摩托车。</p> <p>路不平,一路是非常陡峭的上坡,我坐在副驾,身体不自主的向后倒。为了平稳,我自然要扶住大爷的肩膀,Polo 衫和白色跨栏背心,看起来是一套,碰上去的感觉就是另外一套了。我本来想要用双手轻轻扶肩膀,但因为天气炎热,他出了不少汗。为了不捂着大爷,也为了防止怪异的手感,我从一整只手掌,改成只用几个手指做支撑。虽然面对颠簸的路途不算是很牢固,但起码没有飞过去。在经过了十几个弯路过后,我们终于抵达了另外一家店。看到店牌子,我才知道这个大爷是另外一个修车的店主。</p> <p>他完全不会英文,单纯用手势邀请我坐下。我面对陌生的环境,显得很乖巧,总之顺着对方的意思准没错。之后,我把欧元交给他。他用浏览器查了查当日汇率,并用计算器算了算 400 乘三天的总价,和我确认完数字后,找给了我余下的费用。具体过程的我忘记了,总之非常艰辛,除了最基础的软件翻译,我们用了几乎所有的能表达含义是的方式,嗯嗯啊啊,手势,数字等等,最后还是把这单小生意给敲定下来了。</p> <p>等我骑车离开前,他还帮我选了顶头盔,是被埋在众多的巨大摩托车头盔之中,最小最秀气的那个。我不知怎么表达感谢,只好连连用大拇指的手势,表示对他的最真挚和热烈的感谢与赞赏。</p> <p>同他告别后,我骑着小摩托,一边估摸着来时的方向,一边回忆街道标志的含义,开始时颤颤巍巍,等过了几个道口,深觉熟练不少,遂朝着原来的方向,一路自由得奔驰而去。</p> 吃鸡肉https://blog.kaiyikang.com/posts/2024/%E5%90%83%E9%B8%A1%E8%82%89/https://blog.kaiyikang.com/posts/2024/%E5%90%83%E9%B8%A1%E8%82%89/Thu, 15 Aug 2024 00:00:00 GMT<p>家门口没走几步路就有一个广场。每逢周四就会有各式各样的摊贩,开着特别改装过的移动商店去贩卖新鲜的东西。最常见的是水果蔬菜鲜花,还是肉铺奶酪一类的。但要数最吸引我的,还得是买烤肠和鸡腿的摊位。他们每次出现的位置都大差不差,烤肠在中间,鸡腿则靠近商铺一侧。每次路过都会传来阵阵香气,味道连着鼻子,鼻子连着肚子,而肚子连着钱包。倘若一次性全都买下,一来吃不掉容易撑着,二来挑费太高,钱包会抱怨,但总要选一个,因此不得不在其中来回踱步。</p> <p>这天我来的早,中午到了广场,只有鸡腿店开张了。车门掀起,鸡的各类部位架在上面被炙烤,散发阵阵肉香,场面颇为诱人。看没什么人,我提了半只鸡,欣喜地往家里走。</p> <p>即使是半只鸡,配合一些其他的吃食,也足够让我能吃上两顿。要么说生活的重点在于不停地选择,刚刚逃过选香肠还是鸡肉的一劫,现在又不得不对眼前鸡的部位犯了难。鸡有半只,共含了鸡胸和鸡腿两块部位。这两个区域看似都有肉,实则却大相径庭,鸡腿肉带骨头,香而嫩,鸡胸肉虽然多,但又柴又干。这一顿我只能吃其中一个区域,要么香中午柴晚上,叫先甜后苦,要么柴中午香晚上,叫先苦后甜。</p> <p>要是按照我原有的习惯,那必然是先苦后甜,但转念一想,无论如何调换顺序,似乎总会在香香柴柴之间来回打转。不过是因为有了先后时间顺序,导致感官有了错觉,仿佛它们是有所不同的,这才在隐隐之中让我做出选择犯了难。想到这点以后,面对香喷喷的鸡肉,我继续发出疑惑,该如何调节感官和心灵,并让品味发挥最大的效果。</p> <p>后来我想清楚了:对于好吃的鸡腿肉,我要关闭外界任何噪音的影响,凝神静气,贪婪地同时,细细品味腿部的精华。色泽香味口感,所有的感官必须要全力以赴,不可以有丝毫的懈怠,但凡有哪个感官溜了号,那都是对最好吃部位的亵渎。而对于口味一般的鸡胸肉,我打开了电子榨菜,本着填饱肚子的打算,囫囵吞枣的将剩下的部位吃干抹净。由于注意力的游移,干柴那部分对感官而言也变得不再重要,这样我既饱餐了一顿,鸡胸肉也得以成功的完成了其使命。</p> <p>最终,我完整了品味了这半只鸡,即享受到了精华,也体验了平凡。就综合的整体而言,我能够以实现最高意志的方式享受了它,在这一刻,我的灵魂得到了升华,同时鸡也得到了某种意义上的救赎。我们成功规避掉了时间和两难取舍对于人性的考验,并最终获得了究极的体验。</p> 圆筒与礼https://blog.kaiyikang.com/posts/2024/%E5%9C%86%E7%AD%92%E4%B8%8E%E7%A4%BC/https://blog.kaiyikang.com/posts/2024/%E5%9C%86%E7%AD%92%E4%B8%8E%E7%A4%BC/Wed, 27 Mar 2024 00:00:00 GMT<h2>圆筒</h2> <p>去往站台的路上,我想到些关于人的比喻,似乎这比喻非常形象的描摹了人的形态。我对此窃喜,但因为没什么价值,也只好写出来聊以自慰。</p> <p>人,不论是在肉体还是精神上,其实就是一个中空的筒子,大的类似中空的水泥柱子,小的类似厕纸用完的纸芯。其实但从肉体角度讲,这个比喻并不夸张,嘴巴是上面的开口,屁股是下面的开口。食物从上面进去,废物从下面出来,这不就是完美的筒状么?至于真实的人,不过是在这个基础上附加了很多精致的结构:脸,胳膊,腿等等。而正由于这些特殊的结构的存在,让原本的筒也有了非常多额外的性能。</p> <p>如果将一块巧克力从厕纸芯的前端丢进去,你将会立刻看到它会从末端掉出来。巧克力没有变成营养被吸收,也没有变成带有异味的奇怪物质。同时,除了纸芯壁上会蹭一些巧克力的残渣以外,并没有太多显著的变化,没有变胖或是变瘦。这点正是厕纸芯和人的不同之处,前者缺乏了性能,无法吸收外界的物质并作用于自身,而这却是后者与生俱来的能力。</p> <p>将这个概念推广开,其实人的精神也应该是一个具备吸收变化功能的圆筒。学校里学的,书本上读的知识,或是在参与社会获得的经验,这些都作为食物,被吸收到精神中,即使精神本身它并没有太多意识,就像人会品尝味道,但不会在意下肚以后的事情。这些知识或经历,过了一段时间成了精神的养料。那些因为吸收而不断长大的东西,我想可以称作是灵魂或是智慧。</p> <p>饮食完毕,如果不运动不做功,营养化为了膘,对身体丝毫没有益处。吸收的知识也是同理,不诉说不写作不实践,堆积起来的污泥堵塞了精神的通道。也许人会因此变得偏执和忧郁,并持续自甘堕落下去,恶性的循环就此形成。</p> <p>写作或是同他人聊天,没想到竟然也有活血化淤的功效。</p> <h2>礼或仪式</h2> <p>礼或仪式有种束缚感,最直观的感受来自穿上白衬衫和西裤的瞬间。白衬衫要平整,但上臂幅度较大就会有褶子,甚至需要 skirt slay 等工具的辅助。而西裤要足够贴合腰部,为了美观,有时甚至更紧。作为男性,我不清楚女性穿着裙子的感受,但相信既美观又舒适的裙子是存在的。不过这种穿着也要看场合,即使在身体物理方面没有束缚,不过穿着的场合和条件却有些许严苛。</p> <p>从外星人的视角看来,仪式有些时候像是整个人类社会的 SM 活动。人们的身体被束缚,但精神却无比的愉悦。直率的轻松快乐不好么?我们需要为此再过度包装一层么?对此类问题我想到的合适比喻是在炎炎夏日,人们会在闭紧门窗的屋内开足空调冷气,同时还会在身上裹上厚厚的被子,为的了获得一丝温暖,以抵御人造的「严寒」。小的时候,我有类似的经历,享受是有,但更多的还是困惑与不解。</p> <p>从自动化的角度讲,这又像是一种反馈调节机制,通过不断的折腾人们的身体和动作,从而达到内心的喜悦。其实人人都是系统控制的大师,不过其机制并不被人人所察觉。</p> <p>通过外界的仪式,以达到获得改善内心的效果,从古至今似乎均是如此。不过礼也在逐渐变化,这种变化不是形式上的,而是从客观逐步向主观过度。换句话说,同一套仪式,也许上一辈的人会认可,但这一辈的人会排斥。它被打散,变得十分细碎,以至于无法成为统一的社会共识,只能被有能力辨别的人所接纳。因此,有些礼虽然你看着像礼,但其实质已经变成了戴着面具的舞台演出。它不是生活和人心本身,而是旧有通行货币的遗风,以一种陈列的方式被放置在博物馆中。</p> <p>也许有不少人会因此幻想和迷恋,但我其实怀疑他们并不知道自己在迷恋什么。他们的感动,更多是来自于「我们一起经历了件大事」,而不是仪式本身的神圣性和特殊性。这种遮蔽会让仪式的存在更加脆弱,不再那么神圣化了,就像是可替换的非原厂配件,只要能运行起来就足够了。因此就我个人而言,不会对社会上的泛用仪式特别感兴趣,而更加短促和直接的获得其背后实质。这种实质可以很多样,可以是诚挚的话语,也可以是真情的流露。当然,我没有完全一棒子打死它的缘故,也是在于仪式可以像导管一般,能以快捷通道的方式,将人们最直面内心的感情给迅速抽出来,并呈现在所有人的面前。虽然过程复杂了些,但所幸结果是好的。</p> <p>经此一役,人们或能更深刻的理解外界,或能更真诚的对待对方,应该没有什么比这更令人开心的结果了。</p> 我爱的是人,不是效率https://blog.kaiyikang.com/posts/2024/%E6%88%91%E7%88%B1%E7%9A%84%E6%98%AF%E4%BA%BA%E4%B8%8D%E6%98%AF%E6%95%88%E7%8E%87/https://blog.kaiyikang.com/posts/2024/%E6%88%91%E7%88%B1%E7%9A%84%E6%98%AF%E4%BA%BA%E4%B8%8D%E6%98%AF%E6%95%88%E7%8E%87/Thu, 14 Nov 2024 00:00:00 GMT<p>我熟悉以下类似的场景,人们进入虚拟会议,主持人浏览一圈与会人的头像,见都是德国同事,于是提议会议要不要切成德语。但同时,也有人注意到了我的头像,会特别向我征询意见问,是否要切换语言。而我的回答也几乎相同,请继续使用德语。</p> <p>后来我反思,自己的回答和选择到底是出于什么目的或动机。其实把视角放宽,时间拉长,我也不总是机械的回复,让大家使用德语。一旦出现了重要的事情,或需要我深度参与或了解的任务,我就会主动请求将语言换成英文。对此,我认为了我选择的标准就是为了效率。如果一场会议,我几乎不需要参与,而主要参与人的母语都是德语,那我坚持德语一定就是为了让这场会议更高效,哪怕提升微乎其微。</p> <p>我相信,工作场中的交谈都是以效率优先,毕竟人们的时间都有限。当双方同时都可以兼容英文时,那使用英文就是最好的方式。</p> <p>于此相对,另外一个极端场景是喝酒。</p> <p>我喝过酒,也见过会英文的同事喝酒。事实上,在酒过三巡之后,没人会在乎对方说的是人话还是鸟话,即使用肢体语言似乎也可以表述世界上的一切事情和感受。亲身体验,酒精会让我获得更多的勇气,尝试使用不擅长的德语交流。这样的交流笨拙且效率低下,但谁在乎呢,大家这会儿可都泡在酒精里面。</p> <p>有种浅显的矛盾是,常在社会中混,看似没有个正形的人,德语交流往往非常好。非德裔的大爷大妈口音虽然含混且不太正宗,但总能和其他人聊得有来有回。你自然可以说他们英文可能不太好,但同时,我也不会觉得他们之间的闲聊就是为了追求效率。很多时候,他们的对话就像是喝完酒精后的发言,很多时候就是鬼扯,扯到天南地北毫无边界。而在工作场中的人,往往不会怎么不着边际的胡扯,虽然也有些午休时候闲聊,但更多还是在会议中讨论具体的业务和事情。</p> <p>此前我还会尝试在工作中练习德语的机会,后来察觉到「德语-效率」之间的矛盾后,也就不再强求自己,转而去寻找其它更合适的途径。</p> <p>看到街边的大爷大妈,我会觉得他们彼此是在和人聊天,或换种说法,在他们聊天中,「人」的浓度非常高。而反观会议中的人们,「效率」的浓度绝对是爆表的。因此在最初我所描述的情景中,我会坚持用德语,绝不是为了那些与会人,而就是单纯为了效率。</p> <p>我爱的是效率,而不是那些人。如果就如此写下定论,我会很沮丧,所以我想要加一个定语,「只有在稍显冰冷的工作中」,这条定论才会成立。</p> <p>脱下工作以后的外衣后,我更爱的是人,而不是效率。</p> Kaş - 骑行在半岛https://blog.kaiyikang.com/posts/2024/%E5%8D%A1%E4%BB%80_%E9%AA%91%E8%A1%8C%E5%9C%A8%E5%8D%8A%E5%B2%9B/https://blog.kaiyikang.com/posts/2024/%E5%8D%A1%E4%BB%80_%E9%AA%91%E8%A1%8C%E5%9C%A8%E5%8D%8A%E5%B2%9B/Sat, 07 Sep 2024 00:00:00 GMT<p><img src="@assets/2024/R0002196.JPG" alt="" /></p> <p>kaş 西侧有个半岛,形状犹如手挤出来面团剂子,头窄身宽。从繁华的城中心向北直行,拐过一个分叉口就是入口。一条巨大的环形路环绕着整座半岛,一路向前无需指引,车不多路况很好。</p> <p>阳光明艳,即便是我戴上了墨镜,给山海蒙上了一层透黑的滤镜,一切事物依旧被照射着无所遁行。我骑着昨日租来的小摩托车,在路上驰骋。路旁时不时的会出现粉红的花,衬着灰黄的草木更显艳丽。左侧是山,临近山脚有酒店公寓。右侧是海,海边是熙熙攘攘的人与遮阳伞。前方的路很是俏皮,弯弯绕绕,时不时的被山所遮挡。</p> <p><img src="@assets/2024/R0002213.JPG" alt="" /></p> <p>描绘景色不如看照片来得直观,但骑着小摩托在山海之间疾驰的感觉却是拍不出来的。</p> <p>左手轻靠刹车,右手轻扭油门,眼睛时不时的跳动在前方道路和仪表盘之间,速度约莫在 30 到 40 之间,随着油门起起伏伏。引擎的声音轰鸣,开着开着也逐渐习惯了,直到停车休息时,所有一切都安静了下来,才乎的意识到方才的巨大轰鸣。风是另外一个指向器,风声鲁莽且尖锐的时候,暗示着速度正逐步加快。当风逐渐从皮肤上褪去,阳光射线重新溅射时,速度变得缓慢,乃至彻底静止。</p> <p>每当我看到一处好风景,都会找个安全的位置,停车并熄灭引擎,刚才的暴躁一下变得乖巧,仔细听听,车尾的排气管还有轻微的震颤声音,不清楚是机械问题,还就是个单纯的物理规律。我面朝大海,拍了几张照,转过头去就能看到它孤零零地矗立在街边,左右既无车,也无人,仿佛身处荒郊野岭。一时间,我忽然感到有些轻微恐慌,担心车子坏掉,被抛弃在灼热的太阳之下,于是便生出些想法,想要变成公路侠客,带着全套的修理装备,掌握不系统却十分实用的修车知识。即便是身边的老伙计出现意外状况,无需他人相助,仅凭自己的本事就能叮咣五四地利索搞定问题。随着一次次地尝试打火,引擎重新轰鸣,旅程又能继续下去了。</p> <p><img src="@assets/2024/R0002142.JPG" alt="" /></p> <p>还有种异样的感觉。我下半身稳坐在车座上,脚牢固地踩着车,头和上半身却感受着大风在急速的呼啸,暗示着自己在以超乎肉体的能力在向前奔跑。这确实很怪,而上下半身的速度体验不一致也同样很怪。有时,这样的怪异感在不断的刺激我的神经,每当手中的握把稍微懈怠的时候,它总会提醒我,为我纠偏。</p> <p>即便海风笼罩,我仍然难以躲避太阳的灼烧。草木大多低矮,围绕着山脚,路旁虽然有零星的树,勉强高于人的身高,但它们的影子很难被说成是避难所。地大多是荒的,平的,想要站在上面俯瞰大海与岛屿,就不得不被天空笼罩,与阳光做正面的接触。拍了几处,我暂且还能忍受,但到后来,汗流不止,皮肤刺痛得仿佛在尖声抗议。因此我只好匆忙的结束环岛之旅,狼狈的跑到沙滩边,寻求海洋的慰藉。</p> <p><img src="@assets/2024/R0002237.JPG" alt="" /></p> 缩小比例尺https://blog.kaiyikang.com/posts/2024/%E6%89%A9%E5%A4%A7%E6%AF%94%E4%BE%8B%E5%B0%BA/https://blog.kaiyikang.com/posts/2024/%E6%89%A9%E5%A4%A7%E6%AF%94%E4%BE%8B%E5%B0%BA/Sun, 01 Dec 2024 00:00:00 GMT<p>不久之前,meine Liebe des Lebens 拜访了一户人家后,向我用惊奇的口吻,描述了这段颇为新鲜且魔幻的经历:</p> <p>这户人家住山上,没有便利的公交,只能开车前往。门铃是个平板,可触摸的选项玲琅满目,菜单藏得很深。进入大门,建筑外面是一片巨型花园,一不小心就容易迷路。与其说建筑是房子,不如说是城堡,里面的设施应有尽有,车库,影院,健身房不一而足。管家有两人,其中一位还是医生。男主人从事法律事务,同时还经营着房地产等投资生意。女主人参与经营的同时,也还有自己的工作。他们两人还有一个专业的赛车车队,算是娱乐也算是投资。</p> <p>据她描述,她所看到的一切,仿佛只在电影或是纪录片里出现过。我饶有兴致的听着这段短暂的旅程,虽然她只经过了一个下午的时光,但感觉却是到了异域的大草原。所到之处,所见之物,无不是些珍奇和新鲜的玩意儿。</p> <p>尽管不太重要,但不可否认的是,我们与他人相处时,都会在无意识间和他人比较,或是出身背景,或是成绩,或是财富。但在我听她的讲述时,我忽然意识到,由于被比较的两者差距实在过于巨大,所以失去了任何比较的意义。霎时我产生了一种,见识到「不同物种」之间的错愕感。他们是少数生物,而我们像木头,尽管木头之间也不尽相同,但总归太普通,太常见。</p> <p>这种比较不仅存在于他人与我们自身,还存在于我们见识到的每个人,换句话说,把每一个遇到的人放在统一的坐标系内。这户人家的特别之处在于,他们已经超越了认知坐标系,远远落在了坐标系之外。他们不仅刷新了系统的 scale 的同时,带来另一个影响是,原先散落在这个坐标系中的坐标全部被「集中」,或说「拍扁」了。当参考被大幅度更新后,原先与身边人的巨大差距,相较之下也变得微不足道了。先前也许我们会觉得,认识的人坐拥一两套房子,实现了财富自由,这已经代表了「富有」。但面对超乎想象的富有,先前的认知就完全不成立了。</p> <p>关于财富,关于人与人之间的差距,并非是我在这里想聊的。我认为重要的,是他们为我们带来的不同视野和认知。</p> <p>首先是教育教育方面。这户人家有孩子,而他们对孩子的教育关键词是「礼仪」、「语言」、「实践探索」和「专注力」:</p> <ul> <li>礼仪,我觉得不必赘述。我猜测,他们经常会出现在非常正式的场合,因此对应的礼仪是必不可少的。</li> <li>语言,需要掌握多门,并且是母语的水准。在这里,我再延伸一下,不仅仅是语言本身,表达和社交能力也是必不可少的。</li> <li>实践探索。孩子的教育并非全来自外部,而更多是源于自己的热爱的和探索,因此男女主人并没有把重心放在精英教育,或说「卷」上,而让孩子自由发挥。比如想尝试钢琴,就买一架钢琴;如果对赛车感兴趣,就直接让孩子学罢了。</li> <li>专注力。我个人感觉这是现代社会最稀缺的资源,很佩服他们也把握住了这一点。他们让小孩练习绘画,通过长时间将注意力集中在创作事件,从而培养孩子的专注力。</li> </ul> <p>以上就是他们所认为重要的部分,随之而来的问题是,他们所关注的和我们有什么关系?虽然在前文中,我有些夸大了观奇的体验,但不能忽略的相似点是,我们本质上都是人类,而只要是人类都会和周围的环境产生互动,不同环境产生的互动大相径庭。于是我们有机会能观察到一个非常难得的现象,即在无限的物质和金钱存在的条件下,人们会想些什么,会做些什么。</p> <p>首先时间会变得无比重要。对他们而言,单位时间内产生的收益极高,因此将更多的时间投入到自己的事业和生活中是最佳的选择,而不是被一些不重要的琐事纠缠。他们会相对不计成本的投入那些能够让他们不会操心的事情中,例如雇佣管家,或选择时间最近的头等舱等等。</p> <p>由于物质上的试错成本极低,因此只要是想要的,就可以立刻投入精力和金钱去实践。如果能成,就是一桩美事,如果成不了,也没什么太多损失。相比之下,由于普通人都受到了有限的条件,试错成本非常高,在行动选择上就会更多参考多数人的路径。虽然这条路径没有什么创意,但好在四平八稳,不会出错。基于此,他们的想象力就比普通人更加宽阔,就比如在车队上做广告,也能够称为一种投资。</p> <p>在听完了她所讲述的见闻后,我也感觉十分新奇。我想到更多的不是财富差距,而是另外一种别样的且可能的生活方式。这里说的「可能」,并不是说赚多少钱,而表示的是已经存在了的生活方式,就如同艺术家,旅行家,政治家等等。基于这样的角色,就会冒出对应方式的思维和看问题的角度,而别样的角度,对我来说才是最重要的,也是最有趣的。</p> 拯救厨子https://blog.kaiyikang.com/posts/2024/%E6%8B%AF%E6%95%91%E5%8E%A8%E5%AD%90/https://blog.kaiyikang.com/posts/2024/%E6%8B%AF%E6%95%91%E5%8E%A8%E5%AD%90/Sun, 08 Dec 2024 00:00:00 GMT<p>有时,做后端开发和维护也会遇到奇妙的事情。时间退回到几周前,当时有个任务是要修改测试环境中的数据库数据,我看是测试环境,并且就修改一个值,感觉并不麻烦,于是就接了下来。受限于业务的熟悉程度,还有访问测试工具的权限,我就叫上了另外一个组的同事 J 一起来处理这个需求。</p> <p>因为我们想跳过复杂的行政流程,因此就采取了一个讨巧的方法去修改。虽然花了一些时间,解决步骤也比较讨巧,但从中我受益良多。等任务解决了,我就欣然的和对方汇报,说修改完成了。我想,也是因为回复的太过迅速,以至于给对方留下了一个「解决类似问题专业人」的印象,即便并非是我本人直接解决的。</p> <p>时间继续推进,就在我快把这个事情忘的差不多了,类似的请求又发生了,而且是在最忙碌的时刻。通常,项目以两个星期为一个小周期,多个小周期凑成一个大周期。在大周期的末尾,所有的人都会集中起来,花上两三天的时间制定下个大周期的计划。此时是最忙的时候,因为要不停的开会,听其他项目的同时,制定自己项目的计划。</p> <p>变更的需求发生在清晨。我刚坐到办公室,打开笔记本,就收到了消息。首先是需求方 K 的消息,再来是一个不太熟识的领导 P 远在印度向我打招呼,然后是组内安排计划的同事 B 和我私聊了这个事情,最后自然免不了上次帮忙的同事 J 的询问。</p> <p>有这么着急么?我心里念叨着。为打消心里的困惑,还特地向同事 B 确认了一番,问是不是需要忽略近两天忙碌的会议,而去解决今早临时出现的问题。同事 B 和我说,她已经和另外组的同事 J 联系过了,毕竟他们组负责的内容,应由他们来处理。我说好,然后静静等待进展。</p> <p>随着同事 J 不断的在和我联系,我大概确定了,这事情是非常重要的,看来要今天我是铁定跑不了了。于是我一边开着视频会议,一边打开了文档编辑器,准备数据库变更操作的 SQL 命令。这次的需求,看着描述类似,实则大相径庭:一来是涉及的车辆变得很多,二来是在实际的生产环境,而不是测试环境。前者,意味着我要准备一份长长的,包含许多车辆识别码的脚本,逻辑虽然不复杂,但架不住脚本太长,因此编辑时要格外留神。后者,实际的生产环境意味着任何的改动都需要提前报备,符合正确的流程,然后在选择正确的时间执行。一般来说,需要提前一两个星期的准备时间。(顺带一提,如果真的按照时间表操作,执行时间应该是在明年)我不担心技术层面的执行,但会很在意流程的合规,而这恰恰是程序员无法控制的。</p> <p>倒是和我一起解决问题的同事 J 还挺淡定的,说让需求方去沟通吧,毕竟对方得给出合适的理由。我连声答应。</p> <p>从上午到下午,我一边开着会议,一边准备脚本,一边盯着记录流程的网页,看看需求方是否有留言,状态是否更新了。过了一会儿,专门负责流程的组(另外一家公司)倒是不紧不慢的在里面留了言,询问是否有测试环境的变更记录,理由是否已经被描述,等等。</p> <p>因为需求方也要和我们开会,因此回复也不算及时。又过了一阵子,同事 J 发消息提醒,说是流程已经被许可了,等下开完会,可以一起执行操作。我说好,同时瞟了一眼留言,只见里面赫然出现了一串红色加粗的大字。</p> <blockquote> <p><strong>这个变更需要被立刻执行,因为其中有一辆是苹果 CEO Tim Cook 的车!</strong></p> </blockquote> <p>太乐呵了,这是我第一反应。第二反应是有些紧张,毕竟问题涉及到的人身份比较特殊,这倒是也解释了他们会这么着急的缘故。</p> <p>会议还在开,我的脑子就开始飘乎:「厨子(Cook)的车,他竟然会开这种车,竟然不是其它品牌的车?这是他的第几量车?是他的,还是他管家的?我这个操作看来是意义非凡呀。哈哈哈,厨子竟然也有今天,还需要远在大洋彼岸的我来帮忙。就让你感受到来自德国的神秘力量吧…」</p> <p>一种奇怪的使命感在我心中升起,仿佛是在解决一件了不起的大事件。还没等会议完全结束,我就找了个由头提前离开,立刻和同事 J 取得了联系。简单商量了一下,因为我缺少权限,也没有额外的显示器,所以具体操作还得是由他来执行,我则负责检查和归档。实际操作分两步,第一步更改,第二部刷新。第一步进行得很顺利,但第二步出现了些问题,和我们的预期有些出入。</p> <p>此时已经过了下班时间,我着急忙慌地给不同的同事留言,说需要额外的帮助。时间一分一秒过去,虽然仅仅过去了几分钟,但感觉却超乎想象的长。最后终于有一位资深的同事还在线,我立刻就将他拽入会议。经过一番理论讲解,以及执行过程中突入其来的小报错,波折后总算是正确的触发了刷新,变更请求也得到了正确的解决。</p> <p>我将执行的状态和结果全部归档,并提醒请求方检查结果是否正确。我反复思索任务是否全部得到了妥善处理,等全部确认后,悬着的心才算落到地上。</p> <p>结束以后,精神很疲倦,但脑中回忆起来,整段经历却散发着别样的有趣光彩。从中我也学到了不少,比方说技术与知识的可靠性,这是 junior 和 senior 之间的巨大鸿沟,同时也是当前很难依赖 AI 补足的。技术上,比如纯熟的 database 的应用,或是 psql 命令行的使用。知识上,有对业务系统的理解,也有对技术术语的理解。在紧急关头,不依赖搜索或 AI,而单纯使用一颗大脑去能去迅速定位并且解决问题,这是高手。</p> <p>我希望成为可靠的人,这样就能「拯救更多的厨子」了。</p> 树与表达https://blog.kaiyikang.com/posts/2024/%E6%A0%91%E4%B8%8E%E8%A1%A8%E8%BE%BE/https://blog.kaiyikang.com/posts/2024/%E6%A0%91%E4%B8%8E%E8%A1%A8%E8%BE%BE/Mon, 01 Jul 2024 00:00:00 GMT<p>最近我和同事一直在聊数据结构,其中有种结构叫树(Tree)。就如同我们平日里能见到的树木一样,它由两个要素组成:枝(Branch)和节点(Node)。</p> <p>根据不同的位置和关系,根有不同的名字。树木的根就叫做根(Root),由此伸展出的可以加父节点(ParentNode),以及它的下属节点,子节点(ChildNode)。</p> <p>技术内容的铺垫到此结束,现在我想提及与之相关的糗事。</p> <p>在和同事聊起时,我虽然知道树结构和节点的概念,但却仅限于中文的语境。在用英文沟通,且无法及时查阅英文文档的状况下,我错误的将中文的「父节点」直译成了英文的「FatherNode」,而不是「父母节点」(ParentNode)。直到会议结束,我浏览文档时,才忽然察觉出之前的错误。</p> <p>无意中,我感到有些冒犯,说出这些仿佛我是一个极端以男性为主导的人,但事实并非如此,因为这单纯只是翻译上的或是文化上不匹配的错误,而非本人的想法。</p> <p>这个事情虽然不大,但我触动很深。文化或语言已经以先入为主的态度贯穿了我们的精神,同时我们还无法选择,只得根据环境去习得并适应。直到我们长大了,接触了更多的人和事情,习得了全新的表达方式后,先入为主才会被揭露出来,被我们所察觉。</p> <p>这让我想到了在社交媒体上看到的例子,大意是说两人同时开始学习语言,一个日语,一个法语。前几个单元,学日语接触到的单词是加班、通勤和上司,而法语则是旅游、度假和约会。当法语已经学到调情上床了,日语的词汇表才刚到置办房地产。</p> <p>上述例子也同样一种显露。我觉得大部分人,包括我,都仍对此没有太多察觉,而只有当真正投身于其中,甚至是露出洋相后,才甚至其背后巨大的差异和对我们的影响。</p> 浅水消息https://blog.kaiyikang.com/posts/2024/%E6%B5%85%E6%B0%B4%E6%B6%88%E6%81%AF/https://blog.kaiyikang.com/posts/2024/%E6%B5%85%E6%B0%B4%E6%B6%88%E6%81%AF/Sun, 07 Apr 2024 00:00:00 GMT<p>对话有诸多形式,或面对面,或在聊天软件上。后者占据着大部分我们的日常生活,凭我个人的观察,通过软件聊天的时候,我们的说话方式会发生变化,会变得更直接和简练。比方说「我对你描述的故事感觉十分有趣」直接会简化成「哈哈哈」。</p> <p>内容上的简化,随之而来的是单条消息的信息密度并不太高。一旦想表达的内容变多,势必会被分割成数十个简单的对话气泡,手机的提示音也会弹个不停。以前我一直比较遵循类似的模式,认为信息简洁明了是种环保的美德,久而久之,我便发现单条发送的方式,也影响到了我的思维方式,常常只有发送了第一条消息后,脑子里才会想到,还需发送第二条消息作为补充。下句本是上句内容的补充,它们本可以被囊括在同一个对话泡泡中,但随着割裂的发生,下句无法自然而然的在脑中浮现,而是需要上句被「叮」地发送了,才会想到,原来自己还想再补充一句。</p> <p>由于信息发送变得廉价了,而沟通的载体是信息,因此沟通本身也就变得廉价起来。</p> <p>我想到在初中校外补课的经历,那时我使用的还是个诺基亚手机,虽然已经有流量的概念,但通讯软件远不如现在的发达,发送任何消息都需要通过一条条短信,而每条短信,其实都需要一定费用。还记得是个北京夏天的午后,气温很高,脑子昏昏沉沉,教室的墙壁斑驳,课桌和黑板十分老旧,头顶的电扇随着惯性旋转,假装自己还能吹出些风来。我觉得乏味,想要和同伴寒暄,于是偷偷把手机放在椅子上,夹在两腿之间,手指戳在迷你的九宫格键盘上,打出了些话,向大家问好。据事后收到消息的同学们反馈,消息虽然发送成功,可是字却打错了,错成了一个非常生僻的字,因此大家也面面相觑,不知道作何回复。</p> <p>传递消息像是递水,如果每次传输都需付费,那我自然是希望能够多蓄些水,然后一次性传递得多些。如果传输的费用可以忽略不计,那么蓄水就变得没那么吸引力。一旦脑子里多出些东西,马上就能发送出去,这样的时效性确实是种优点,但并不能说,蓄水变得没有意义。人的大脑虽然会像泉眼一般,不断的流出思维和感受,但只要冒出一点就送出去,冒一点就送出去,积蓄便是无从发生的。而量变会产生质变,浅显的思维经过积蓄和沉淀,势必会孕育出新的思维与灵感,人们还会从中发现各种各样的属于自己的模式与路径,这些也同样是弥足珍贵的东西。</p> <p>当然,我写下这些,并非也意味着我赞成只要发送消息,就要发送一大坨的内容。对那些本就没有什么逻辑或情感连续性的内容,逐条简单的发送其实是非常有效的,也能节约彼此大量的时间,而至于那些连贯的内容,我倒是更愿意积累在一起阅读,或更愿意在有条件的时候面对面交流。</p> <p>不要被技术的爆炸所迷惑,我们必须要认识到,信息的传递在自然界本就不是一件容易的事情。同时,更要警惕,不能让消息传递的廉价化,误导我们觉得沟通消息本身就是廉价的。我们大脑和内心是一切事情发生的起点,这则是最为无价的。</p> 湖边与状态转移https://blog.kaiyikang.com/posts/2024/%E6%B9%96%E8%BE%B9%E4%B8%8E%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB/https://blog.kaiyikang.com/posts/2024/%E6%B9%96%E8%BE%B9%E4%B8%8E%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB/Sun, 19 May 2024 00:00:00 GMT<p>水面在风的吹拂下抖动,反射着银光,刺动着双眼。能见度很好,山峦带着积雪,在湖遥远的另一侧,徐徐展开。湖水冰冷,尖锐的刺激着皮肤。水中的沙石随机排布,借着重力的影响,对着足底猛烈进攻。我赤着脚尝试踏进湖水,结果痛的滋哇乱叫,仿佛健康出了故障。</p> <p>爱人则灵活多了,等着阳光将湖水烘热,不紧不慢地在湖岸边游泳,钻入码头下面,又从中穿出,好不自在。</p> <p>我坐在岸边,身体一半在阳光下,一半在阴凉里,透露着一丝犹豫。爱人问我,是不是不喜欢阳光。我无法笃定地回答是或不是。我不抗拒太阳与它散发的光芒,但因为我对它太生疏,也不了解,缺乏了打交道的常识(比如涂抹防晒),所以无法得出一个论断。</p> <p>太阳如此强烈,占据着所有曝露着的地方。在这个空间中呆上许久,皮肤就像被带细刺的毛刷拂过,从温暖变得刺囊,忍不住地要挪动为止。而钻入阴影中,又很快会被寒冷的湖水侵蚀,于是不得不再次回到阳光之中。阳光,湖水,阴凉,它们好像占山的大王,各自守护着自己的领地,不断驱赶着无法适应环境的人。</p> <p>我躺在树荫下,腿脚晒着太阳。树叶的影子在头顶窸窸窣窣的。树叶随着树枝,由里到外生长,根部密集,外侧稀疏,从远处看,就是一团绿丛丛。阳光一照,显出了原形,外面的较薄,闪耀着嫩绿色,内部的较厚,呈现出难以辨析的暗绿色。树叶的形状是锯齿的,从光线到阴影的过度十分锐利且不自然,但因为每个叶丛都遵循着类似的长势,所以整体的光线异常和谐。</p> <p>透过树叶,云在背后闪躲。爱人喜欢看云,也能辨云,甚至能说得上不同云形状的拉丁文。她同我说了许多术语,冷凝,结晶,温度层等等。我看着云在天空中飘动,实在难以和精确的学术术语结合起来,于是也将这些词打包送上的空中,成为云朵,随着风逐渐散开。</p> <p>她说的大多数虽然已经忘记,但仍对「状态」一词印象颇深。如果我没理解错误,云是状态集合的体现,这个状态集合包含温度,湿度,气压等等。当这些指标达到了一定的标准,它们就以肉眼可见的形式呈现了出来。我想象,天空是一片巨大的空间,每个位置都十分独特,且被许许多多的状态所统治,当状态合适时,就显露出白色,当状态不合适时,则又变成了透明。坐标点像是个传送轴,定点滚动,而云则是包裹,不断地被传送到远方。</p> <p>从天空出发,拓展到地面,乃至穹宇。空间中的每个点都有着基本的状态,当它们都具备了,那么在同一个位置,也许显现出的是个人,也许是一只猫,而因为人想回家,猫想觅食,于是这个坐标又变得空无一物起来。</p> <p>一切的一切都是静止的,但也同样都是在运动的。</p> 社会内外https://blog.kaiyikang.com/posts/2024/%E7%A4%BE%E4%BC%9A%E5%86%85%E5%A4%96/https://blog.kaiyikang.com/posts/2024/%E7%A4%BE%E4%BC%9A%E5%86%85%E5%A4%96/Sun, 11 Aug 2024 00:00:00 GMT<h2>内外</h2> <p>我发现,当我们讨论 MBTI 的时候,代表内向和外向(Introversion and extroversion)的第一个属性常常背离一贯对他人的了解和认识,经常以惊叹的形式呈现出来,「不可能,你怎么可能是个 I 人呢?」。出现了一次是意外,但次次出现大概就能感觉到背后似乎出现了理解上的偏差。</p> <p>我在自己是 I 和 E 的判断上出现过分歧,几年前写过篇黑历史,还看似以颇为严谨的态度剖析自己为什么是 E 人而不是 I 人,现在看来也太过滑稽。当然,现在看过去显得幼稚和滑稽并不是坏事,这表示人在进步,而不是逐渐再萎缩。</p> <p>说回到内外。前几日,我和他人在闲聊,主题各种各样,有工作也有生活,但随着每个人都在对同一主题发表意见和想法的时候,我忽然察觉到了内外泾渭分明的分界线。依照同一个主题,有些人会不由自主地从社会历史或阶级等角度出发,而另外一些人会从自己内心的感受出发,虽然视角会来回穿插,但通过对话能非常明显的判断出这个人内外视角所占的面积有多大,遂能显露出其背后的偏好。</p> <p>内或外,也可以说是主观和客观。仅仅从日常聊天来判断人的归属是非常武断的,譬如我会在亲人或爱人面前展露内心的感受,但也会客观的与同事交流对项目和产业的看法。从前者看我是个 I 人,后者看我就是个 E 人,虽然人人都是两者兼有的,但如果都这么混沌的去判断的话,似乎也就没有判断的必要了。</p> <p>对外的性格犹如面具,大抵是可以通过训练等方式人工塑造出来的,但从本心出发的偏好和无意识间占据思维的比重可就没办法被塑造出来了,毕竟没必要自己骗自己玩儿。我斗胆猜测,面对同样的外部世界,人们也都说着同样的话,「面对这种情况,我需要…」。E 人的重音落在前脚的「这种情况」,I 人则落在「我」上,根据不同的重点,思维和感受也由此发生了偏离,并走上了不同的方向。</p> <p>综上,性格的内向和外向并不是一个很好的判断依据,外向的人可以沉默装高手,内向的人也能大胆开麦当社牛,能够影响的因素太多也太复杂。但面对自己真实的思维和感受却无法装出来,也不需要去装,所以算是一个较为明晰的判断标准吧。</p> <h2>社会是这样的</h2> <p>德国汽车行业不景气是人尽皆知的事实,如果没有亲身参与进去,那无非就是从报道的文字中获知了一个不太相关的信息,但作为边缘的相关者,那么它就会影响着我每天的三分之一时间。</p> <p>我工作不久,但坦诚的讲,虽然常听人说社会无情险恶,但也从未经历过太大的震动。不过最近的公司裁员的确是对我有所触动。</p> <p>一开始我是那种,即使相处了几个月的同事因为自己状况变了而离开公司,也会忍不住感慨一番的人,直到到后来人员变动太大,来来走走太过频繁,因此纤细敏感的突触也逐渐钝化。可能是社会这个大哥觉得我还是太嫩,想要再拿我开涮,于是降下来裁员这么一遭事情,也许是为了让我有所警惕并恢复危机意识,我在此先谢谢大哥了。上周开大会,老板宣布行业不景气要大家做好准备,这周就为了节省成本开始大刀阔斧的裁员。这一刀不要紧,也砍到了我们刚组建的队伍上。组里负责项目沟通的人不幸落难,遗憾与我们道别,然后就火速下线。</p> <p>虽然德国是永久工作合同,但特殊情况下为了保这艘大船,该裁还是得裁。于是我便在一星期内经历了极速的库伯勒·罗丝过式山车:「不是吧?不应该呀!能救么?救不了啊…那行吧」。做完了过山车之后,我还和剩下的同事总结了一下自己幸存的缘由,大概是因为太便宜且姑且算个劳动力吧。</p> <p>闲聊之余仔细去想,人的感情和社会的运行方式很多时候都是冲突的。一方面我们唾弃公司不能如此对人无情,另一方面也不得不承认这是种算是普遍的解决问题的方案,事实就被如此塑造了出来。我不知道对其他人的想法,但就个人而言,尽人事听天命,努力学习提升自己,并以最大的友善祝福他人,也许就是最陈词滥调,但也最有效的应对方式了。</p> 里半生https://blog.kaiyikang.com/posts/2024/%E9%87%8C%E5%8D%8A%E7%94%9F/https://blog.kaiyikang.com/posts/2024/%E9%87%8C%E5%8D%8A%E7%94%9F/Thu, 13 Jun 2024 00:00:00 GMT<p>我有听过「上半生」,「下半生」,「前半生」或「后半生」等之类的说法。</p> <p>它们的共性在于,前半部分是个描述方位空间的形容词,后者则充满了时间意味。用空间描述时间?仿佛时间是个凝固的对象,通过上下前后去描述本应流动的事物。充满活动色彩的时间,被空间的形容词感染,从而变迟钝了。</p> <p>以传染性来说,描述时间的传染性似乎强于描述空间的。例如「短暂的空间」和「狭窄的时间」。虽然前者被描述的对象是空间,但我仍旧会联想到时间的角度上。这里,我感觉有抽象与具象是造成传染性不同的原因之一。相比抽象,人天生对具象敏感一些,因此,在面对能被「瞬间」可视化的空间来说,对时间的感知,更像是后知后觉的结果。</p> <p>同时,上下或前后,似乎也潜藏着一些不平衡的色彩,前者如朝阳,后者则垂暮。至于说我为什么这样感觉,是因为均衡的形容词很难被用在「生」上,例如你从未听过「左半生」或「里半生」等说法。从这个角度看,左右或里外,则要更平衡一些。</p> <p>当然,以上的论述并不包含任何严肃的语义学讨论,只是单纯和粗浅的感知,属于一耳朵的声音和拍脑门儿的结论。</p> <p>尽管如此,「我的内半生」或是「我的外半生」等的说法倒是蛮另类的,我喜欢这个表达,听上去就像是那种对人生个性游刃有余的那种人,不把自己交付给他人(例如,「我的下半生就和你一起度过了」等说辞)。一会儿可以当个外生人,一会儿又能钻进内心做个内省者。时间对所有人都是平等的,不与其较劲,而是把自己的这副身子骨和精神气给管理妥帖与明白,想必也是一种不小的成就。</p> 从批处理到精细化任务 - 如何重构错误追踪机制https://blog.kaiyikang.com/posts/2026/%E4%BB%8E%E6%89%B9%E5%A4%84%E7%90%86%E5%88%B0%E7%B2%BE%E7%BB%86%E5%8C%96%E4%BB%BB%E5%8A%A1---%E5%A6%82%E4%BD%95%E9%87%8D%E6%9E%84%E9%94%99%E8%AF%AF%E8%BF%BD%E8%B8%AA%E6%9C%BA%E5%88%B6/https://blog.kaiyikang.com/posts/2026/%E4%BB%8E%E6%89%B9%E5%A4%84%E7%90%86%E5%88%B0%E7%B2%BE%E7%BB%86%E5%8C%96%E4%BB%BB%E5%8A%A1---%E5%A6%82%E4%BD%95%E9%87%8D%E6%9E%84%E9%94%99%E8%AF%AF%E8%BF%BD%E8%B8%AA%E6%9C%BA%E5%88%B6/Fri, 06 Mar 2026 00:00:00 GMT<p>有一种经典的场景是,为了提升系统的可维护性和可观测性,我们需要将系统从基于批次的状态(Tasks Batch Status)转向至单个任务(Single Task)的精细化错误追踪。</p> <p>为了实现这个目标,我们可以让 Data Object 自己携带目标,而不是通过方法的参数来统一传递状态,从而使代码的变动最小,并保持优雅。</p> <h2>当前的情况</h2> <ol> <li>获取任务:业务逻辑层(use case layer)指挥服务层(service layer),按批次(Batch)从数据库中拉取待处理的任务。</li> <li>获取数据和调用:基础设施层(infra)发送请求,每个请求会携带批量信息。</li> <li>状态转换:服务层根据外部服务的返回结果或抛出的异常,将结果统一包装成一个自定义的 <code>BatchStatus</code>(比如 <code>SUCCESS</code>, <code>PARTIAL_SUCCESS</code>, <code>FAILED</code>)</li> <li>批量更新:业务逻辑层根据 <code>BatchStatus</code>,对这一批任务进行分类,并循环调用 Repository 层的方法更新数据库状态。</li> </ol> <h2>核心的目标</h2> <p>我们希望提升服务在批量任务处理中的可观测性与精细化排障能力。</p> <p>当底层服务调用失败的时候,系统不仅能够为任务标记一个笼统的 <code>FAILED</code> 或 <code>RETRY</code> 状态,还能够将底层错误的原生信息,例如 HTTP 状态码、具体的 Client Error Code、Service Response Message 等等,精准地穿透并传递,并持久化到每一个具体任务的数据库记录中。</p> <h2>遇到的问题</h2> <p>在当前的机制下,服务有严重的错误上下文丢失(Context Dropping)问题:</p> <ul> <li>信息断层:当底层的基础设施捕获了错误码和错误信息,但这些 debug 信息仅仅被打印在了日志中,随后就被丢弃了。</li> <li>状态扁平:服务层只包装了一个干瘪的 <code>BatchStatus</code> 和受到影响的任务 ID 集合。</li> <li>Debug 成本高:数据库中,所有的失败任务看起来千篇一律。开发人员如果想要知道某个具体任务的原因时,必须去拿着任务的 ID 去日志系统中搜索。</li> </ul> <p><img src="@assets/2026/202603_refactoring_error_tracking_01.webp" alt="" /></p> <h2>代码示例</h2> <p>我们需要从持久层中获取 Task,然后请求外部 Client 并获得结果,并将包含着结果或错误的 Result 传递回 Task,最后更新持久层。</p> <h3>第一步:让 <code>BatchProcessResult</code> 具备携带错误的能力</h3> <p>假设当前有一个 <code>BatchProcessResult</code>,里面包含了请求外部 Client 后的统一结果 <code>BatchStatus</code>,里面包含了多个 <code>ItemId</code>。</p> <p>以前只能返回一个简单的 <code>BatchStatus</code>,现在我们希望记录每一个 <code>item</code> 的具体细节。为此,我们引入一个新的 <code>ErrorInfo</code> 记录,并在 Result 中建立项目 Id 和错误信息的映射。</p> <pre><code>// 1. 新增 record 来封装对于每个 item 的错误细节 public record ErrorInfo( String failedReason, String errorLabel, String errorDetails ) {} // 2. 改造批处理返回结果类 public class BatchProcessResult { // 原来只有Batch的结果 private BatchStatus batchStatus; private final Set&lt;String&gt; failedItemIds = new HashSet&lt;&gt;(); // 【新增】用于存储哪个 Item 发生了什么错误 private final Map&lt;String, ErrorInfo&gt; errorInfoByItemId = new HashMap&lt;&gt;(); // ... 省略 getter 和 setter ... public void addFailedItemWithError(String itemId, ErrorInfo errorInfo) { failedItemIds.add(itemId); errorInfoByItemId.put(itemId, errorInfo); } public ErrorInfo getErrorInfo(String itemId) { return errorInfoByItemId.getOrDefault(itemId, null); } } </code></pre> <h3>第二步:在底层捕获异常</h3> <p>在发起网络调用的位置(infra/service layer),我们要改变过去「只打印日志,返回模糊 FAILED」的做法。我们需要捕获 HTTP 状态码,三方系统返回的 Message 等等信息,包装进 <code>ErrorInfo</code> 中,并向上传递。</p> <pre><code>// 服务层,与client交互并获得 result public BatchProcessResult processBatch(String clientId, Set&lt;String&gt; itemIds) { RemoteClient client = ClientRegistry.get(clientId); if (client == null) { //【新增】如果 client 错误,则该 batch 中所有的 items 都会有统一的错误 ErrorInfo errorInfo = new ErrorInfo( "CLIENT_CONFIG_ERROR", "NO_CLIENT", "more details" ); return createBatchResultWithError(BatchStatus.FAILED, itemIds, errorInfo); } try { Response response = client.sendItems(itemIds); if (response == null) { return createBatchResult(BatchStatus.SUCCESS); } //【新增】可以返回包含不同 item 错误的 BatchProcessResult return createBatchResultForPartialSuccess(itemIds, response); } catch (ClientRequestException e) { // 进一步的丰富 client 的错误返回信息 ErrorInfo errorInfo = new ErrorInfo( "CLIENT_REQUEST_ERROR", e.getErrorCode(), e.getMessage() ); return createBatchResultWithError(BatchStatus.RETRY, itemIds, errorInfo); } catch(Exception e) { ErrorInfo errorInfo = new ErrorInfo( "SYSTEM_ERROR", "UNKNOWN_EXCEPTION", e.getMessage() ); return createBatchResultWithError(BatchStatus.UNKNOWN, itemIds, errorInfo); } } </code></pre> <p><img src="@assets/2026/202603_refactoring_error_tracking_02.webp" alt="" /></p> <h3>第三步:业务逻辑层调度组织,持久化前缝合数据</h3> <p>这是整合,或对齐 <code>task</code> 和 <code>result</code> 的步骤。在业务逻辑层,拿到了底层返回的 <code>BatchProcessResult</code>,我们不直接丢给 <code>Repository</code>,而是根据 <code>result</code> 丰富 <code>task</code> 信息:将底层传上来的错误细节,根据 <code>itemId</code> 赋给 <code>Task</code> 实体(<code>enrichTasksWithErrorInfo</code>)。</p> <pre><code>// 业务逻辑层,负责编排服务 public void processBatchTasks(String clientId) { // 获取 task (或说 Entity) List&lt;Task&gt; tasks = taskService.getNextTasks(); if (tasks.isEmpty()) return; Set&lt;String&gt; itemIds = extractItemIds(tasks); BatchProcessResult result = batchProcessor.processBatch(clientId, itemIds); // 由于 batch 成功,没有错误,则不需要绑定到 task if (result.getBatchStatus() == BatchStatus.SUCCESS) { taskService.updateTasks(tasks, TaskStatus.SUCCESS); return; } // 【新增】 在持久化前,把 result 的错误细节缝合到 task 上 enrichTasksWithErrorInfo(tasks, result); // 基于 BatchStatus 分发 TaskStatus switch (result.getBatchStatus()) { case PARTIAL_SUCCESS -&gt; handlePartialSuccess(tasks, result); // 需要对 failed item 做额外的处理 case RETRY -&gt; taskService.updateTasks(tasks, TaskStatus.RETRY); default -&gt; taskService.updateTasks(tasks, TaskStatus.FAILED); } } // 缝合 task 和 result 的方法 private void enrichTasksWithErrorInfo(List&lt;Task&gt; tasks, BatchProcessResult result) { for (Task task : tasks) { ErrorInfo errorInfo = result.getErrorInfo(task.getItemId()); if (errorInfo != null) { task.setFailedReason(errorInfo.failedReason()); task.setErrorLabel(errorInfo.errorLabel()); task.setErrorDetails(errorInfo.errorDetails()); } } } </code></pre> <h3>第四步:干净的服务层,去做持久化</h3> <p>该服务层可以专注于和 <code>task</code> 有关的信息处理,而不会掺杂 <code>result</code> 里面的 <code>failedReason</code> 之类的信息。</p> <pre><code>// 1. 现在的 Service 层更新方法变得极其干净 public void updateTasks(List&lt;Task&gt; tasks, TaskStatus status) { // tasks 里面已经包含了 errorLabel 和 errorDetails, Repository 执行 update 时会自然而然地把它们刷进数据库 // 这里根据 TaskStatus 做最后的处理 switch (status) { case SUCCESS -&gt; taskRepository.deleteTasks(tasks); case FAILED -&gt; handleFailed(tasks); default -&gt; handleRetry(tasks); } } // 2. Task 实体类准备接收这些数据 @Entity @Table(name = "tasks") public class Task { @Column(name = "error_label", length = 255) private String errorLabel; @Column(name = "error_details", length = 255) private String errorDetails; // ... } </code></pre> <h2>总结</h2> <p>通过引入了额外的 Context 载体 <code>ErrorInfo</code>,我们解耦了「捕获异常」与「持久化异常」的生命周期。底层只需要如实记录,服务层和业务逻辑层只管按图索骥赋值。</p> <p>这保证了方法和模块的单一职责,又极大地提升了系统在线上环境的可观测性。</p> <blockquote> <p>免责声明: 代码示例已简化并通用化,仅用于教学目的,侧重于架构模式而非具体项目实现。</p> </blockquote> 向上攀爬入寂静https://blog.kaiyikang.com/posts/2026/%E5%90%91%E4%B8%8A%E6%94%80%E7%88%AC%E5%85%A5%E5%AF%82%E9%9D%99/https://blog.kaiyikang.com/posts/2026/%E5%90%91%E4%B8%8A%E6%94%80%E7%88%AC%E5%85%A5%E5%AF%82%E9%9D%99/Thu, 08 Jan 2026 00:00:00 GMT<p><img src="@assets/2026/202601_dahab_climbing_01.webp" alt="" /></p> <p>不出半小时,乘着皮卡车就能进入峡谷。这里是贝都因人的地盘,几乎不可能自驾进入,让本地向导带着你进山几乎是唯一的选择。也许是 12 月份的缘故,或仅仅是峡谷两旁的山太高了,大部分时候,阳光只能照在山尖,却透不进山脚。空气干燥且清爽,气温异常舒适。</p> <p>由灰黄的花岗岩堆砌成的山巨大,遮天蔽日,站在跟前甚至让人眩晕。岩石外部各异的结构和缝隙,在人们眼中都是绝佳的攀岩手点和脚点,这里是著名攀岩胜地。</p> <p><img src="@assets/2026/202601_dahab_climbing_02.webp" alt="" /></p> <p>岩壁上预置着一排排金属挂片,采用先锋攀登(Lead Climbing)的方式需要攀爬者带着绳子从底部出发,每经过一个挂片就扣入快挂并挂入绳索。如果体力不支,不慎脱落,身体会被最近的快挂制动。即便单个挂点出现意外,多个保护点的存在也让系统整体失效的可能性微乎其微。因此攀岩虽然看着吓人,实际安全系数却非常高。当先锋攀爬者到达顶部并架设好保护站后,下方的保护员便可收紧绳索帮助其下降。初学者随后便能利用这根已设置好的绳索,进行最基础的顶绳攀岩(Top Roping)。</p> <p><img src="@assets/2026/202601_dahab_climbing_03.webp" alt="" /></p> <p>另一种更为原始的形式被称为无保护独攀(Free Solo),即不使用任何绳索或器械保护,单纯依赖肢体接触岩壁向上攀登。本地向导 Mohamed 有时会选择这种方式攀爬至岩壁顶部,为初级水平的爱好者架设好顶绳保护系统。尽管他通常会选择难度较低的路线行进,但单就这种不带任何保护并且赤脚的攀爬方式本身,就足够令人心惊胆战。</p> <p>真正的岩石触感很棒,虽然外表有些滑,但相比岩馆中的人造石头更加坚硬且结实,里面是坚固的实心,摸起来令我踏实。结构变化多端,大小的凸起肆意组合。</p> <p>穿戴好装备,靠近岩壁,直到准备开始攀爬的时候才穿上攀岩鞋子。虽然岩石不会摧毁鞋子,但是细小的沙石会。它们坚硬且尖锐,很快会把鞋底的胶划出斑驳的纹路。系好 Mo 刚从上面放下的绳子,彼此检查一下攀岩者和保护者的安全设置是否正确,终于可以开始攀爬了。</p> <p><img src="@assets/2026/202601_dahab_climbing_04.webp" alt="" /></p> <p>不一样,感觉与岩馆的攀爬完全不一样。没有了固定的颜色与特定的线路,道路像是进入了迷雾,或是被沙尘暴袭击,很难根据肉眼识别。于是身体的所有感受器都要打开,手臂和手指像雷达一般开始摸索岩石,尝试这里做平衡失败了,不妨再试试那里,那么多变化,总有一款适合你。摸一摸,再转转身体,这种尝试也许会持续很久,但岩壁永远会充满耐心地等待你。然后突然在某个瞬间,手指寻到了一个特殊的凸起,握起来大小正合适,挂住它,全身似乎都充满了平衡与力量。Aha 时刻来临了,身体仿佛受到了某种召唤,忽然就能有力地向上抬升,随即用脚尖踩住牢固的岩点。新的稳定来了,又能再一次探索和扫描了。每次神秘的接触都会让我喜悦,忍不住大喊「好爽」。</p> <p>当然,爽并不能一直持续的,绝大部分时间还是困惑与紧张的。肉眼可见的范畴内,大半天都找不见一个可用的点,腿和胳膊的力气在一点点耗尽,随着遭遇临界值,以 F 开头的字不禁脱口而出。此刻,只听得 Mo 会在下面忽忽悠悠地说上一句:「Do not Fxxx」。这种随意感还体现在他指挥的用词,其中有句令我们记忆犹新的是「Stand Up」。多么朴素的指令,但到了岩壁上却成了个非常难以实现的行动,要么角度不够,要么力量不足,让我想到一个词,大道至简,背后却是多少用心良苦。</p> <p><img src="@assets/2026/202601_dahab_climbing_05.webp" alt="" /></p> <p>攀登到了顶,经历了肌肉的酸痛和辛劳之后,得到的不是房顶的天花板,而是宽阔与高耸的山峦和天空。这是攀岩最好的奖励。</p> <p><img src="@assets/2026/202601_dahab_climbing_06.webp" alt="" /></p> <hr /> <p>时间流逝,太阳逐渐滑到了山谷的背后,天空也变得暗了下来。</p> <p>人们乘车逐渐离开,山谷变得安静了些。光线变暗,映照着山谷的颜色也从黄变成灰黑色。巨大的岩石冷峻了不少,连接着天空给人不少压迫感。</p> <p><img src="@assets/2026/202601_dahab_climbing_07.webp" alt="" /></p> <p>在完全暗下来之前,我们尝试再多攀爬几条线,以节约这点宝贵的时间。趁着这段时间,背靠着岩壁,Mo 已经升起了篝火。篝火旁放置着山谷里捡来的柴火,铺着巨大的阿拉伯地毯。他告诉我们这是最传统的贝都因风俗。</p> <p>他与助手一同从皮卡的后斗揭出来一道道晚餐,有土豆和洋葱做的炖菜,有简单的沙拉,还有烤鸡。我们问是谁做的,他说是他的妈妈。虽然听着朴素,但是味道却出奇的美味。配合着山谷的篝火还有攀岩过后的劳累,我觉得那是整趟旅途中最好的晚餐。我感到胃像是无底洞一般,不断吸食着食物,却一点也不觉得有撑得难受的感觉。我们想着为 Mo 和他的助手留出一些食物,但他谦和地告诉我们:这是专门为你们准备的,请尽情享受。我嘴上说着感谢,叉子上忽然又多出了一块鸡肉。</p> <p>天黑了。这次没有了明亮的满月,星星变得清晰不少。火焰在闪动,木头在噼啪作响。吃饱后,人们围坐在一起,有人躺在地毯上,有人背靠着岩壁,但大家的双眼都飘忽在火焰和星辰之间。人与人的关系似乎变得更近,聊的话题仿佛也收到了黑夜的感召,变得深邃且复杂。</p> <p><img src="@assets/2026/202601_dahab_climbing_08.webp" alt="" /></p> <p>我接过了 Mo 刚泡好的茶水,温润甜腻的口味很有滋味,躺在地毯上,朝着天空发呆。随着柴火逐渐被耗尽,火焰也被驯服,变得不再猛烈,而安静的火光让星辰更突出和明亮。山石的黑色影子将天空勾勒出几条明显的分界线,视野被收窄成一束。</p> <p>火焰最终暗淡了下来,交谈声也逐渐消失。所有的声音都被岩石和沙子吸走了,山谷非常安静。印象中,我从没有经历过这么安静的时刻,任何动作的摩挲声,甚至是心脏的跳动声都变得无比清晰。我漂浮了起来,漂浮在峡谷中,沐浴在星辰下。所有元素是自然且原始的,它们在不断向我辐射着古老的气味,使我得以剥离掉现代技术和思想,回归最原初的状态和感受。此刻我才发现,存在于日常生活的噪音是多么的喧闹和复杂。耳朵中永远有滴滴答答的声音,不是来自邻居,就是来自房间内的装置。即便是合上眼睛,也永远会有些许光线穿透进来。在睡觉的时候,噪音也依旧萦绕着我们。它们仿佛成了噩梦的元凶,让身体在整个生命周期中不间断地被迫接纳并感受。从没有真正黑暗过,也从没有真正安静过。</p> <p><img src="@assets/2026/202601_dahab_climbing_09.webp" alt="" /></p> <p>山谷中的安静好似清冽的泉水,流过我的感受器,使它清洁,让它变得敏感。能感受到它被清洁的能力,大概率不是来自童年时候的城市记忆,更像是在襁褓之中,甚至在生命诞生之前,生物的基因在我的耳畔悄悄地说「好好感受它吧,这是一种馈赠,是生命最珍贵的礼物」。类似的感知在围坐在火堆旁的人之间流淌,不必言说,也不必传递任何信号,只需要在此时此刻漂浮。大家共享这原初的宁静,在星空和岩石间以同样的频率起伏上下。</p> <p><img src="@assets/2026/202601_dahab_climbing_10.webp" alt="" /></p> <p>时间流逝得更快。篝火最终熄灭了,我们不得不起身离开。一场安静的梦被惊醒了,每个人都恋恋不舍,恨不得在此处彻夜徜徉。坐在车的后斗上,看着两侧的黑色山峦不断撤退,被车灯照得改变了形状,和白天的时候是完全不一样的姿态。随着卡车行驶到主路,两旁的路灯和远处的小镇逐渐亮起,天空的星辰变得昏暗,隐退到了另外一个时空。</p> <p>我们重新驶向了那个充满强光与嘈杂的人造感官世界,而那份仿如梦幻般的宁静,也已与篝火一同熄灭,被永远留在了山谷中。</p> 拔牙的失去与获得https://blog.kaiyikang.com/posts/2026/%E6%8B%94%E7%89%99%E7%9A%84%E5%A4%B1%E5%8E%BB%E4%B8%8E%E8%8E%B7%E5%BE%97/https://blog.kaiyikang.com/posts/2026/%E6%8B%94%E7%89%99%E7%9A%84%E5%A4%B1%E5%8E%BB%E4%B8%8E%E8%8E%B7%E5%BE%97/Sat, 07 Feb 2026 00:00:00 GMT<p><img src="@assets/2026/202602_wisdom_teeth.webp" alt="" /></p> <p>鲜红的血不断地从我的嘴里涌出来。</p> <p>除了在影视剧里面,我从未有过如此经历。我定了定神,身体向后倾斜,稍用些力气把上下牙齿咬紧,忍住腥味将血水吞下。过了一阵,血才止住,伤口也逐渐安静下来。</p> <p>我纠结了数年,在不同牙医的催促下,终于鼓起勇气去拔出智齿。德国的习惯是一次性拔掉四颗,所以我也入乡随俗,免去之后更多的困扰。</p> <p>牙医很和善,言语中充满了鼓励,但有时也有不少敷衍,例如术后没有给出任何的康复建议。幸好,拔牙的过程进行得还算顺利,仅有左下的牙齿遭遇了一点点波折。我听到了不少电锯的声音,闻到了烧焦的糊味,但因为麻药没有一丝感觉,脑子里却不断构想着医生在嘴里施工的场景。拔完了牙,阿巴阿巴的和医护人员商量完后续事项,就利索地乘公交车回了家。</p> <p>当晚的创口最严重,于是就发生了开头的那一幕。</p> <p>血水流遍了垃圾袋,洗手池,还摸到了和血豆腐一样的东西(称为血凝块,作用于闭合伤口,非常重要)。后来的数个小时,不敢再低头张嘴,只靠在垫子上闭着嘴,吞咽着口感和味道都非常复杂的神秘流体。</p> <p>虽然描述起来可怕,但最恐惧的时候,其实是在拔牙前的那几天。未知的恐惧和社交软件上帖子的推波助澜,即使大白天走在路上,我也会因为想到这事,心跳飙升。于是我知道了,越是模模糊糊的事情,就越不能看软件。情况相差太多,他们的经历未必出现在我的身上,而往往决定事情走向的又是一些具体细节,所以根据具体细节和情况再去查询如何解决,倒是最为合适的做法。</p> <p>而且我还发现,在恢复身体的时候,短视频真的可以起到「止痛药」的功效。刷着刷着,注意力就转移到了手机屏幕里。伤口也没什么感觉了。想起我日常也有在刷短视频,颇有药物滥用的意思。不行,得想着改改。</p> <p>所以我放下手机,转而去读书。</p> <p>书里里面写的是如何金钱和生活乃至自我的关系,它呼吁人们要明晰自我,要知道自己生命能量的流逝,要对身边金钱的来去充满感知。除了一些抽象的讨论,它还给出了不少实践的建议,不过大多都是长期实践,只能在未来娓娓道来。</p> <p>因此在实践前,我先揣着书里的点子,来到了门口的超市,打算买点东西。</p> <p>若是我牙口好的时候,不论我之后是否会吃,恨不得见什么都买上一些,乃至最后有些东西烂在冰箱和柜子里,甚至生了虫卵。我愧疚不已。但这次却很特殊,我牙口不好,选择就受到了极大的限制。所以我径直地来到果汁区,拿了一瓶清澈透明的苹果汁,浑浊的柳橙汁看都不看一眼,随后拿了一片松软面包,转身立刻就朝着收银台走去。没有加糖的冰咖啡,没有薯片,也没有肉肉,只有在特殊时期,我最需要拿来补充必要营养的。</p> <p>它限制了我的能力,进而强行收窄了我的选择。在结账的那一刻,我的眼睛和思维变得澄澈且干净,我获得了一种珍贵的感觉,即知道什么才是自己真正需要的。在此之前,花花世界围绕在我身边,而我又是一个能力健全的男子,这种多项配多项的选择让我迷茫和眩晕。虽然最终我仍会恢复健全的牙齿,但起码这一次我是带着能够识别自我真需求的感知回来的。</p> <p>生理的受限,恰好成了一次好的「实践」,强行帮我过滤掉了那些不必要的生命能量消耗,对金钱的去向也有了一次直观的感知。</p> <p>这算是拔牙的好处。</p> <p>在家修养了几天,我终于有心思出门走走,碰巧赶上好友的毕业展,所以计划过去捧捧场。我送上了咖啡和祝福,和他在走廊里有一搭没一搭地聊了会儿,见他还需要社交,就借机打算去其他展厅转转。</p> <p>展览形式虽然不同,但每年都有,我也曾来过四五次。踏进展厅的某个瞬间,我猛然回忆起那时的自己,像极了刘姥姥进大观园,什么都看不懂,但见什么都感叹,见什么都记录拍照。</p> <p>久而久之,行为有了惯性,推着我,对所遭遇的一切发出感叹,不论自我是否真正喜欢。如果选择了一切,等于什么都没有选择,而什么都不想错过,也等于自己不知道想要什么。「来都来了」这个魔咒在我耳畔回荡,因此那时的我,像强迫症一样,不愿错过任何一个房间和展厅,只是为逛而逛。</p> <p>自我的表现形式之一就是其独特的选择。</p> <p>这次,我不再贪恋新奇的展厅,也不再因错过什么而感到遗憾。漫步了几个房间,拍了两三张带着天空的画作后,我和好友简单道别,就转身离开了。</p> <p>回到家,我继续养伤,思考自己到底想要什么,想到等养好了牙齿,去运动,去吃好吃的,做点自己真正想的做的。</p> 理解 Java 异常https://blog.kaiyikang.com/posts/2026/%E7%90%86%E8%A7%A3java%E5%BC%82%E5%B8%B8/https://blog.kaiyikang.com/posts/2026/%E7%90%86%E8%A7%A3java%E5%BC%82%E5%B8%B8/Thu, 19 Feb 2026 00:00:00 GMT<p>程序运行有时不是一帆风顺的,如果出现了错误,程序直接退出,或带着错误继续运行直到我们根本不知道错误出现在什么地方,都是十分可怕的。</p> <p>为了解决这个问题,人们引入了 Exception,它可以隔离错误,优雅退出程序,让系统变得更加健壮。</p> <h2>所以什么是 Exception?</h2> <p>Exception 是对非预期状态的对象化。</p> <p>执行程序,本质上是对 stack push 和 pop 的过程:</p> <blockquote> <p>A 调用了 B,B 调用了 C,C 运行完了返回给 B,B 再给 A。</p> </blockquote> <p>非预期状态,说的是如果 C 的执行发生了异常,不能遵循正常的返回路径。</p> <p>异常发生后,JVM 会寻找「异常处理器」,即 catch 模块,如果此时 B 没有,那么直接会 pop 它,再去找 A,最后返回。</p> <p>对象化,即 Java 会把错误包装成一个 Object,其中包含了有用的信息:</p> <ul> <li>类型:发生了什么 <code>e.getClass().getName()</code></li> <li>状态:错误的详细描述 <code>e.getMessage()</code></li> <li>上下文:Stack Trace <code>e.printStackTrace()</code></li> </ul> <p>举个例子</p> <pre><code>public class ExceptionDemo { public static void main(String[] args) { try { calculate(10, 0); } catch (ArithmeticException e) { // e 就是那个被包装出来的 Object System.out.println("1. 类型 (Class): " + e.getClass().getName()); System.out.println("2. 状态 (Message): " + e.getMessage()); System.out.println("3. 上下文 (Stack Trace):"); e.printStackTrace(); } } public static void calculate(int a, int b) { int res = a / b; // 这里会产生异常对象 } } </code></pre> <p><code>try</code> 中发生的异常都继承自 <code>Throwable</code>,主要分为三大类:</p> <ul> <li><strong>Checked Exception(受检异常):</strong> 必须在编译期处理(try-catch 或 throws),否则无法编译。如 <code>IOException</code>。</li> <li><strong>Runtime Exception(运行时异常):</strong> 也叫非受检异常(Unchecked Exception),通常是代码逻辑错误,编译器不强制要求捕获。如 <code>NullPointerException</code> 或 <code>ArithmeticException</code>。</li> <li>Error:通常是 JVM 级别的严重错误,如 <code>OutOfMemoryError</code>,程序一般无法恢复,不建议捕获。</li> </ul> <p>例中的 ArithmeticException 就属于 Runtime Exception。</p> <p>它在运行时会跳出。</p> <h2>Throw 和 Throws</h2> <p>我们也可以手动抛出一个 exception,即使用 <code>throw</code>,比如:</p> <pre><code>public void setAge(int age) { if (age &lt; 0 || age &gt; 150) { throw new IllegalArgumentException("年龄非法:" + age + ",必须在0-150之间"); } this.age = age; } </code></pre> <p>还有一个类似的代码 <code>throws</code>,在方法签名中使用。</p> <p>它意味着,这个方法明确不会处理 exception,需要由调用者处理。</p> <p>举个例子:</p> <pre><code>public void loadConfig() throws FileNotFoundException { FileReader fr = new FileReader("config.txt"); } </code></pre> <p>这里必须声明 <code>FileNotFoundException</code>,因为编译器知道读取文件可能会失效。</p> <h2>自定义 Exception</h2> <p>Java 内置的 Exception 通常是一些技术错误,比如 空指针,网络断开等等,但是无法描述业务错误。</p> <p>大部分情况下,我们应该继承 <code>RuntimeException</code>。</p> <p>比如:</p> <pre><code>public class InsufficientBalanceException extends RuntimeException { // 1. 除了消息,还可以携带具体的业务数据(这是自制的核心优势) private final double balance; private final double amountRequested; // 2. 提供构造函数,把信息传递给父类 public InsufficientBalanceException(double balance, double amountRequested) { // 调用父类构造器,生成标准的错误描述 super("转账失败:当前余额 " + balance + " 元,尝试转账 " + amountRequested + " 元"); this.balance = balance; this.amountRequested = amountRequested; } // 3. 提供 Getter,方便在 catch 块里提取这些数据做补偿逻辑 public double getBalance() { return balance; } } </code></pre> <p>我们可以精准捕获这个特殊异常:</p> <pre><code>try { // 这里会发生 InsufficientBalanceException 异常 bankService.withdraw(100); } catch (InsufficientBalanceException e) { // 只处理钱不够的情况 showDepositDialog(); } </code></pre> <p>在例子的 <code>super</code> 中,我们将拼接好的字段传给了父类。当后续调用 <code>e.getMessage()</code> 的时候,就会打印该信息。</p> <p>另外,我们还可以传入 <code>Throwable</code>:</p> <pre><code>public InsufficientBalanceException(double balance, double amountRequested, Throwable cause) { super("转账失败:当前余额 " + balance + " 元,尝试转账 " + amountRequested + " 元", cause); this.balance = balance; this.amountRequested = amountRequested; } </code></pre> <p><code>cause</code> 代表了罪魁祸首,它可以接受其它异常信息,比如:</p> <pre><code>try { // 1. 模拟从数据库获取余额,可能会抛出 SQLException currentBalance = database.getBalance(userId); if (currentBalance &lt; amount) { // 2. 情况 A:逻辑错误,主动抛出业务异常(此时没有上级异常,cause 传 null) throw new InsufficientBalanceException(currentBalance, amount, null); } // do something } catch (SQLException sqlEx) { // 3. 情况 B:技术错误,数据库挂了 // 包装成业务异常,并把“元凶” sqlEx 传给 cause 记录在案 throw new InsufficientBalanceException(currentBalance, amount, sqlEx); } </code></pre> <h2>JVM 处理 Exception</h2> <p>针对这个例子。</p> <pre><code>try { a / 0; } catch (ArithmeticException e) { // Do something } </code></pre> <p>当 JVM 运行到 <code>a/0</code> 的时候,错误发生,应该立刻跳转到 catch。而它并不是通过逐行扫描代码来寻找 catch 块的,那样效率太低了。</p> <p>实际上,为了跳过从错误发生处到 catch 块之间不需要运行的代码,Java 编译器(javac)在编译时,会在 <code>.class</code> 文件中生成了一张异常表(Exception Table)。</p> <p>它包含了如下信息:</p> <ul> <li><strong>From</strong>:try 块开始的字节码指令行。</li> <li><strong>To</strong>:try 块结束的字节码指令行。</li> <li><strong>Target</strong>:对于 catch 开始的字节码指令行号。</li> <li><strong>Type</strong>:可以捕获的类型。</li> </ul> <p>当 <code>a/0</code> 触发了错误,JVM 会立刻拿着当前的行号查表。</p> <p>如果在 From 和 To 之间,且类型批匹配,那么直接跳转到 Target 处执行,非常快。</p> <h2>如果没有 try-catch 呢?</h2> <p>如果错误发生了,但此时没有 try-catch,JVM 也没有办法在异常表中找到匹配的项,那该怎么办?</p> <p>它会做一个很重的操作:<strong>栈帧回溯</strong></p> <p>比如 执行 C 发生了异常:</p> <ol> <li>强行 pop:JVM 不会管 C 中的局部变量,而是直接把 Stack Frame 从虚拟机栈中弹出。</li> <li>恢复现场:恢复上一层方法 B 的执行环境。</li> <li>继续查表:拿着刚才 C 的异常对象,查 B 的表。</li> <li>循环直至死亡:循环刚才的过程,直到退到 <code>main</code> 退无可退为止,最后导致当前线程意外终止,甚至整个程序崩溃退出。</li> </ol> <p>所以说,不要忘记写 try-catch,不然它会掉入深渊。</p> <h2>关于 Exception 的性能问题</h2> <p>在高并发的场景下,大量抛出异常会导致服务器的 cpu 飙升。</p> <p>原因在于我们之前提到的 <code>e.printStackTrace()</code>,它记录了 Stack Trace。</p> <p>对底层而言,当 <code>new InsufficientBalanceException()</code> 时,祖先类 <code>Throwable</code> 的构造函数中的本地方法(Native Method)<code>fillInStackTrace()</code> 会被调用。</p> <p>重点是,调用 <code>fillInStackTrace()</code> 会暂停 Java 执行流或当前线程,抓取从栈顶到 <code>main</code> 方法的每一个 Stack Frame(类名,方法名…),最后记录下来。</p> <p>它消耗巨大。</p> <p>所以,对于像 <code>InsufficientBalanceException</code> 类似的,自己定义的业务异常,我们只关心状态码和具体的业务消息,不太在乎堆栈信息,因此可以直接返回 <code>this</code>:</p> <pre><code>// 在 InsufficientBalanceException 里加入 @Override public synchronized Throwable fillInStackTrace() { // 阻断 JVM 抓取堆栈的操作,从而提升性能 return this; } </code></pre> <h2>总结</h2> <p>Java Exception 是将被打断的「非预期执行路径」封装成一个携带现场数据和堆栈信息的 Object。</p> <p>在实践中,我们可以通过 <code>throw</code> 触发异常、<code>throws</code> 声明风险,并利用「异常链」在保证底层溯源的同时实现业务逻辑的解耦。</p> 诊所里被石头压住的时间https://blog.kaiyikang.com/posts/2026/%E8%AF%8A%E6%89%80%E9%87%8C%E8%A2%AB%E7%9F%B3%E5%A4%B4%E5%8E%8B%E4%BD%8F%E7%9A%84%E6%97%B6%E9%97%B4/https://blog.kaiyikang.com/posts/2026/%E8%AF%8A%E6%89%80%E9%87%8C%E8%A2%AB%E7%9F%B3%E5%A4%B4%E5%8E%8B%E4%BD%8F%E7%9A%84%E6%97%B6%E9%97%B4/Sat, 10 Jan 2026 00:00:00 GMT<p>还有两天才离开,我希望利用好在海边的时间,找机会多潜水。问了问潜水老师,她推荐我找当地的大夫诊断一下,看看能不能潜水,还另外推荐了她认识的司机,说我可以联系,他会帮助我。</p> <p>他叫 Mustafa,加联系方式的时候,看到了个性签名的位置是一连串长长的阿拉伯文,我复制下来问问 Gemini 是什么意思,它说这段签名包含了穆斯林生活中追求的六个核心愿望:有益的知识,洁净的生计方式,能被接纳的善功,谦恭的心,赞念的舌头(时刻记得上天,言语中充满善意和赞美)以及坚韧的身体。</p> <p>我和他简单做了沟通,告知了位置和地点。过了一会儿,他似乎察觉出来我使用软件的语言是德语,随即给我发送了一条语音。司机会发送语音这件事情并不常见,因为大部分司机只会阿拉伯语,英文仅仅是能用的程度。发送英文的短信交流时间地点还有价格,是最常见的。点开语音,听到他夹杂着英文和德文向我问好,我觉得很有趣,回复自己住在德国,但来自中国。</p> <p><img src="@assets/2026/dahab_clinic_02.webp" alt="" /></p> <p>第二天清晨,他如约而至。我上了车,有一搭没一搭地攀谈起来。我用软件里面的 share live location 帮助他定位,但不熟悉功能,打开就忘记关闭了。过了几个小时,我们沿着海边散步,实时定位也跟着改变。他笑着跟我提起,他儿子看到手机,还在纳闷说,这个乘客的位置离医生的距离那么近,为什么还需要打车?</p> <p>我好奇向他询问为什么会德语,他说前十几年都在潜店帮忙,有太多来自德国的客人,与他们沟通,久而久之就学会用德语听说。我说了几句德语,他都明白,也会做简单的回复。</p> <p>车行了十分钟左右就到了医生的诊所,门脸不大,门口是正在修建度假村的施工工地,一如既往的混乱。我询问如何就诊,Mustafa 说交给他。只见他从兜里掏出一张纸,正面似乎是一张护照的复印件,他将带内容的一面折进去,空白的一面向外,叠成一个长条形。手里攥着纸条,我们走到诊所门口,门前已经坐着一位女士,司机询问了她姓名,写在了纸条的顶部,之后又在后面顺着写了我的名字。见到没有其他人在等待,他在地上找了块石头,压住纸条,放在诊所大门口,嘱咐我,你可以先到处逛逛,开门了回来等着。开始检查就给我发消息,我就会过来。</p> <p><img src="@assets/2026/dahab_clinic_01.webp" alt="" /></p> <p>我想象到他曾接待过同我类似的人,但更让我思考的是这个简约乃至简陋的预约系统。它足够简单,足够便宜,也完全有效。太多的自动化电子系统让我麻木,它们大多能正确运行,但时不时也会出现莫名其妙的错误。电子预约系统足够现代,同样足够美观,但也大多千篇一律。纸条上留着不同人的姓名和笔迹,还有签字笔墨水不足的痕迹。它是动态的,时刻在变化,不需要任何经验都能够看懂,并立刻使用。真是个优雅且高效的解决方案,我喜欢这样朴素的系统。</p> <p>等到开门的时候,见到一个大爷出现在房间中,穿着黄色的袍子,土黄色的马甲。他拖完地,把诊所的门打开,拾起了门口被石头压着的小纸条。我随着门口等候的人慢慢悠悠地走进了候诊室,不大的空间,摆着一圈医院里标准的钢制座椅,墙壁上画着海洋的儿童画,下面摆着被翻烂的漫画书。远处的墙壁则挂着一些艺术画,记得有阿拉伯文的书法,还有伊斯兰女性的眼睛。</p> <p><img src="@assets/2026/dahab_clinic_03.webp" alt="" /></p> <p>我以为大爷只是帮忙的助理,没想到他开始叫起第一位病人的名字,见人答应,就划掉名字,带着对方走进了诊室。看来他就是那个医生,这让我有些出乎意料。我排在第二,所以很快就被叫到了号。诊室空间不大,只有一盏顶灯亮着,有些昏暗。墙壁上挂着巨大的耳鼻喉的剖面图,桌子上还摆着耳鼻喉的模型。他请我坐下,我开始用英文简单叙述我的状况。他听完缓慢地点了点头,问了些琐碎的问题,然后拿着专业的设备检查我的耳朵与鼻子,边检查还要求我尝试做耳压。</p> <p>检查完,他向我解释了状况,说并不是常见的受伤,而是鼻腔黏膜发炎肿胀导致了咽鼓管的阻塞,分泌液也无法正确的被排出。这就是我能听见异响的原因。他的英文极其流利,语速缓慢且语气平和,抑扬顿挫地向我解释。很多专业术语我不懂,但也能猜出个大概。他给我开了几副标准的药物,标准到在我和其他潜水教练描述的时候,都可以流利的说出这些药分别是什么,除此以外,他还特别的嘱咐,每当我要潜水之前和其间,都要使用鼻子喷雾,排除发炎的风险。</p> <p><img src="@assets/2026/dahab_clinic_05.webp" alt="" /></p> <p>结束了会诊,我付给他 300 埃镑,大概是 6 欧元左右。我说他大概是最好的耳鼻喉医生,毕竟他在一个潜水胜地。他手放在前胸,谦虚地笑了笑,表示感谢。就这样,我结束了整个会诊的过程。</p> <p>回到住处,我和 Mustafa 告别,他一脚油门,继续去寻找下一个能诉说自己故事的客人。</p> <p>我站在路边,感慨良多。无论是数字化的预约系统,还是高昂的医保,在此时都显得有些多余。我的困扰,最终被一块石头与纸条,还有近乎免费的费用给解决了。大概这就是这片土地特有的性格:粗粝、随意,却有着一种直击本质的、朴素的智慧。</p> <hr /> <p>在这片神奇的土地上,总会发生神奇的故事。还有另一段关于求医问药的轶事。</p> <p>我们总会觉得,攀岩或潜水是非常危险的事情,但我这次遇到最棘手的事情,其实是甲沟炎。指甲剪得太秃,受了轻微的感染,起了小包,按上去有些疼痛。大约是晚上九、十点多的时候,我不想让脚继续恶化,以免耽误了第二天的攀岩计划,正苦恼着,女友主动提出要帮我出门买药。</p> <p>她走得急,没提前查好地点,出了门才发现手机没有信号,只好凭着印象去摸索。沿着黑黢黢的街道走了一会儿,走到跟前才发现是一个牙医诊所。失望之余,她打算打道回府,结果刚巧就在街角遇到了 hostel 的管理员 Mo,于是赶忙询问药店在哪里。</p> <p>Mo 指了指马路对面,说药店在那里。她道谢后,走过马路继续寻觅,最后终于幸运地找到了还在营业的药店。</p> <p>药店里,抓药的店员不太会说英文,所以她掏出手机,展示出我带着小脓包的指甲。正展示着,店里又来了一位客人,看上去像是一位知识分子。巧合的是,他会说阿拉伯文和英文,以及一点点中文。</p> <p>店员从后面的大架子取来了抗生素药膏,两份竟然是一模一样的,一份给了我女友,一份给了他。这才惊讶的发现,世间竟有如此巧合的是,他也是为他得甲沟炎的朋友来买药的。</p> <p>惊讶之余,店员又拿来了一个小白瓶子,上面贴着阿拉伯文的标签,透露出古老神秘中东的气息。他向两人介绍,头两天可以用这瓶子药,把脓肿给「吸」出来,如果还有感染,再涂抗生素药膏。那位知识分子听后也来了兴趣,向店员详细打探了这瓶药的秘密,本想着就买一支药,结果听完介绍以后,觉得此药甚好,因此一并买了下来。</p> <p>出了药房,回到住处,她向我兴奋地展示晚上的这段奇遇。我边听她讲,边拿着瓶子端详,愈发觉得神秘了起来。有一种世间巧合都在向它聚拢,我不用它疗伤就是对这段宇宙因缘的亵渎。</p> <p>打开瓶盖,看见里面是灰土黄色的药膏,沉在透明的甘油中。我用手机查了查,说这泥是高岭土,具有极强的物理吸附性,能像磁铁一样,把皮肤深层的脓液吸附到表面上来。</p> <p>我半信半疑,把泥抹到了甲缝中间,就这样连续抹了三四天,肿块奇迹般地消失了,脚踩在地上也不会感到疼痛了。字面意义上说,是偶然和巧遇治愈了我的脚趾。</p> <p>真是神奇的土地。</p> Dahab - 抵达凌晨的山峦https://blog.kaiyikang.com/posts/2025/dahab_1%E6%8A%B5%E8%BE%BE%E5%87%8C%E6%99%A8%E7%9A%84%E5%B1%B1%E5%B3%A6/https://blog.kaiyikang.com/posts/2025/dahab_1%E6%8A%B5%E8%BE%BE%E5%87%8C%E6%99%A8%E7%9A%84%E5%B1%B1%E5%B3%A6/Tue, 08 Apr 2025 00:00:00 GMT<p>凌晨 4 点下了飞机,经历了一整夜的飞行,我们已经身心俱疲。机场不大,设施看上去很陈旧。欢迎入口处,代表埃及的图腾象征性地堆在一起,旁边零散的矗立着假椰子树,配合分辨率不高的打印背景墙,整体塑料感十足。我们饶有兴致的撇了一眼,便匆匆随着人流走到边检,拿到行李并换了些钱,没走几步路就出了机场大门。</p> <p>起初我们十分担心,凌晨拖着疲惫的身子到达一个全然陌生的地方,如果没有预先安排好,会不会衍生出很多问题。为防止出错,我还特别向联络人详细问过「有什么步骤?需要什么信息?」等问题,结果对方却十分豪爽的说,什么都不需要,一切都安排好了,你们来就是了。虽然仍战战兢兢,但我姑且妥协了。</p> <p>事实上,的确是我们想的太多。出了机场大门,尽管时间已经是凌晨,但广场上灯火通明,两旁站满了人。我从左到右快速扫视一圈,立刻就发现了负责接送的司机。他手中攥着一张薄纸,上面书写「Dahab Kang」。虽然字迹十分脆弱,但非常容易分辨。</p> <p>这是他们处理事情的风格,原始且直接。有时我会深刻体到,现代城市生活对我行事风格太深,但凡做任何事情,都需考虑并遵循来自外界的程序以及限制,同时我也并不清楚这些程序的意义和作用是什么,由于太过模糊,以至于对程序错误所造成的结果有莫名其妙的担心。你也许明白,当想要在城市中处理什么事情,如果缺少了一些特定文件,你就需要来回跑来跑去,搭出很多时间和精力。相互一对比,更凸显出他们处理问题的方式有多么直接,这几乎贯穿了当地生活的方方面面。我忽然自惭形秽,感到有太多有的没的事情侵占了我生命的时间。</p> <p>我们和他友好地打了招呼。他没有太多的寒暄,就领着我们穿过人群,走向停车场。路不平整,坑坑洼洼的,路旁的马路牙子边还有裸露的砂石。路灯时好时坏,让光线变得支离破碎。</p> <p>司机走到一辆小车前,示意这就是我们要坐的车。坐进车内,古朴的装潢让我想到了北京 90 年代的出租车。前挡风玻璃下铺着红色绒毯,上面点缀着黄色碎花。车上很多部件都铺着透明塑料纸,也不知道是从未拆封,还是说为防灰尘而特地罩上去的。我坐在后排左侧,白色胶带缠着的车门扶手令我印象深刻,以至于我下次乘车是靠它才辨认出这是同一辆车。后挡风玻璃盖着一层黑色塑料网,我想作用应该和前面的红毯差不多,都是为了当车辆在沙漠太阳中暴晒时,尽量给车内降降温。</p> <p>车开了,嘟嘟嘟的发动机显然说不上太健康。随着出租车缓缓驶出,从玻璃窗中放眼望去,几乎没有什么好车停在停车场上。好车,指的并不是牌子很高级的车,而是从外观看上去健全的车。正如路面和陈旧的设施一般,这里的车大多都十分破旧。我不清楚这些车都经历了什么大风大浪,表面的漆没有一块是完整的,金属的梁柱也变形,保险杠也多一块少一块,大灯能亮全就算是很不错了。</p> <p>这里大部分车都被打回了原形——一个带有四个轮子的载人工具。工具这个词描述的恰如其分,车辆不再具备特别的身份属性,豪华的配置和功能在荒漠中也显得十分苍白无力,倒是那四个轮子以及带有阴凉的空间,才是当地人们最需要的东西。</p> <p><img src="@assets/2025/IMG_3219.JPG" alt="" /> <em>较为完整的出租车</em></p> <p>于是,我们看到了皮实且耐用的日产车满大街的跑来跑去。标记着 ISUZU 品牌的小型皮卡是行走在荒漠和城镇之间的神器,拉人拉货都堪称完美,在当地随处可见。空间更大一些的小型面包车,后面常被改装成纵向长椅,能够装下八个人,非常适合做小型的 road trip。比这个再好一些的车我们也乘坐过,外观功能完整,还带有舒适的空调和座椅,但坐惯了上面说的「破车」,再坐好车甚至还有些不适应。走在路上,但凡看到一些所谓高端的好牌子,我们都会调侃,这种车可太娇嫩了,真不适合在这种恶劣的环境里开。</p> <p>每每看见这些车的时候,我总会想起「话糙理不糙」这句话。「话」是表面功夫,而「理」则是背后的概念和哲学。生活在这里的人们一直在诠释并贯彻这句话的意涵,虽然车辆表面十分粗糙,但其隐含的功能却完全够人们使用了。除此之外的任何冗余,最终都会被大海和荒漠彻底打磨直至消失。</p> <p>在机场周围还有些零散的路灯,等出了机场向北,灯就完全不见了。路上几乎没有其它车,柏油路上,隔开车流的线完全失去了作用。路的走势大开大合,直线很长,弯也算不上急,旁边的交通警示牌稀稀疏疏,在车灯的照射下反射出孤寂的光。牌子上的阿拉伯文也没有英文翻译,根本不懂它想表达什么意思。</p> <p><img src="@assets/2025/IMG_3223.JPG" alt="" /> <em>外面一片漆黑</em></p> <p>车灯伴随着月光,勾勒出两旁山峦的轮廓。它们的身体黑黢黢的,轻微抬头,伴着天空,才能够勉强看到山顶的形状,随着车辆快速驶过,悄然融化在黑夜中,再也找不见踪影。</p> <p>我们身心俱疲,一路上随着车辆的颠簸醒醒睡睡。司机十分腼腆,话不多,声音也很轻,兴许是为了不打搅我们休息,也没有开任何的音乐和广播。夜变得更静谧,我们三人仿佛身处在一个特殊的时空之中,在向前极速地飘荡。</p> <p><img src="@assets/2025/IMG_3628.JPG" alt="" /> <em>天亮了,行驶在荒漠中</em></p> <p>迷迷糊糊不知过了多久,天空逐渐被点亮,睁开眼睛就已看到我们早已被群山环抱。到处是荒漠,没有一点植被,远处的山非常裸露和突兀。这是荒漠的日常景象,但对习惯了高楼和青山的我们来说却特别新鲜。坐在后排,我们彼此相视一笑,眼睛反射出凌晨的微光,诉说看到山峦异景的好奇和兴奋。</p> <p><img src="@assets/2025/IMG_3224.JPG" alt="" /> <em>车窗外的荒漠</em></p> <p>随着车驶出山区,我们逐渐接近了进入小镇的哨卡。哨卡虽然不大,但都处于关键位置的出入口,刚才出机场的时候也有,但夜晚太黑,所以不是很明显。天亮了以后,看着就更加真切。哨卡的路上摆着铁栅栏,旁边有一两个小屋子,里面坐着警察和官兵。一旁的武装车和他们手中的荷枪实弹,看上去颇有威慑力,气氛立刻就不一样了。</p> <p>这样的哨卡遍布在半岛的各个区域,只要乘车去往不同的城镇,就一定会遇到他们。在路上,我们需要随时准备护照,以应对对方查验。至于标准也是大相径庭,有时几乎不会查,司机说上几个词就能放行(不知这几个词到底是意味着什么,有什么特殊的魔力),而有时却要特地把车停下,等上几分钟才能放行。很难想象,不会阿拉伯语的人应该如何在城镇之间移动。自驾是不可能的,本地人的帮助是必要的。城镇之间被粗暴区隔开了,每当出城的时候,我都会有些恍惚,镇子中的繁华热闹与外界冷静的对比太过明显,连续的旅程也被分割成一段一段的。</p> <p>通过了哨卡,沉默良久的司机对着我们轻声说道「Welcome to Dahab」。我们透过车窗看去,附近的墙上张贴着宣传语,表示过了这里就进入了城镇。囿于旁边荷枪的军队,我们也没敢拿出相机拍照留念,只好转转头,尝试让这个颇有纪念意义的墙壁在眼睛里多驻足几秒钟。</p> <p>车继续行驶,宽阔的大海从远处随着漫长的大道逐渐下降展开,我感到有些心跳加速,熟悉的景色开始有了新的转机,大海慢慢地插入天空与山石之间。旅途迎来了它开始的标志,经历了漫长的熬夜和奔袭,我们终于抵达了 Dahab 小镇。</p> Dahab - 清晨的小镇https://blog.kaiyikang.com/posts/2025/dahab_2%E6%B8%85%E6%99%A8%E7%9A%84%E5%B0%8F%E9%95%87/https://blog.kaiyikang.com/posts/2025/dahab_2%E6%B8%85%E6%99%A8%E7%9A%84%E5%B0%8F%E9%95%87/Wed, 09 Apr 2025 00:00:00 GMT<p>我们乘坐出租车驶入小镇,离开柏油马路,拐入小径。</p> <p>小径路面尘土飞扬,低矮的建筑林立在两旁,说是建筑,不如说是由破碎墙壁和门板围成的方形区域。门口堆满着石料和塑料,远看还是近看都像是垃圾,像是即将开工的工地。路面十分原始,就是单纯的沙土地,上面零星布满着许多东西,最常见的是狗和羊的粪便,还有些塑料制品。粪便经过了阳光和高温的炙烤,变得像石头一样坚硬,没有什么味道,苍蝇也不来光顾。</p> <p><img src="@assets/2025/L1090619.jpg" alt="" /></p> <p><em>土路</em></p> <p>我尝试将这些随处可见的土路与建筑和我的经验互相融合,最终得到的结论是,它更像是中国的小区,或德国的 30km/h 区域,不过是埃及版本。通过将不同的印象相互结合,我才能逐渐接受这种全新形式的住宅区。这样的住宅非常极端,究极简陋且不包含任何设计,但却无比实用且非常符合当地的气候和人文条件。长期生在这片土地的人们,各个都是「形式追随功能」的大师,不过全是功能,没有一点形式罢了。</p> <p>每次走在土路上,都是一场微型的冒险。白天,墙头忽然会冒出的花朵和小猫,同时还能闻到阵阵的羊屎芬芳。夜晚,趁着夜色和微弱的灯光,我在沙地上灵巧的躲避地上的粪便和垃圾。习惯了之后,其实也还好,别有一番风味。</p> <p>当然,并非所有地方的建筑都这样,像我们选择的 hostel 就不是上面描述的样子。它是个小别墅楼,外表方正,窗户也不大。外墙和内院干净整洁,墙内种植的花束和绿植长得高过了头顶,四处散开,相隔老远就能看到。类似的小楼在小镇上也不少,但大多都被经营成了 Airbnb 或是 hostel。它们多数集中在镇中心,虽然设施都不是最新的,但胜在功能齐全,能看到不少现代的影子。</p> <p>规模大点的度假村都坐落在城镇边缘。我们都不太喜欢那些地方,每次路过门口,往里面张望,尽是萧条。栏杆和设施被海风严重侵蚀,虽然房间众多,但也不见得有多少人住在里面,冷冷清清的。总之,如果你有机会来到这里,我极力推荐 hostel 或 Airbnb,而不是度度假酒店。</p> <p><img src="@assets/2025/L1090630.jpg" alt="" /></p> <p><em>有些小院的门口就比较精致</em></p> <p>下了车,内敛却用心的司机帮助我们把行李搬下车。因为我们抵达太早,所以不得不反复敲叩击大门,并用电话将人叫醒。过了几分钟,负责人 Z 君开了门,一脸睡眼惺忪,向我们问好。我们和司机表示感谢后,将行李放进了大厅的走廊,Z 君简单地向我们介绍了些青旅的设施和使用规则。</p> <p>Z 君是名埃及青年,身材高大且皮肤黝黑,常穿着一身运动装,负责青旅的大小事务。每次清晨下楼经过客厅,都能看到他躺在沙发上惬意的睡觉或听歌。但据可靠消息称,这并不是在悠闲偷懒,而是他每天都会早起健身,等完成了一系列运动之后回来后,才躺在沙发上休息。刚起床的我们所见到的,其实是忙碌完后的他。</p> <p>除此之外,最令我们感谢的,是他万事通的能力。上到青旅内部事务,下到各类活动组织,还有小到像是在哪里可以打印东西等等问题,他都知道,且能很快提供回复。在这个网络信息闭塞的地方,地图 App 只能算勉强够用,但更新非常不及时。而第一手的最新信息,还得向他询问。我们很庆幸能选择这个青旅,结实友善且靠谱的当地人,为这趟旅程润滑不少。</p> <p>hostel 的客厅中,正门边上就是台饮水机,我最喜欢的是里面免费的冰水,每次回来都会喝上几杯。客厅中间是几个沙发,还有电视机,再往里面走是厨房和长桌。厨房中的红茶是免费的,而其他的冲泡饮品则要自助付费。</p> <p><img src="@assets/2025/L1090611.jpg" alt="" /></p> <p><em>清晨的 hostel</em></p> <p>埃及红茶是一种冲泡的茶粉,常见的被称作 El Arosa Tea,用很低的价格就能买上一大包。每次饮用的时候,只需挖一小半勺粉末,不需任何滤纸,直接用热水冲对,等待热水被茶粉染成透亮的夕阳红,就可以直接饮用。与红茶相对,欧洲常见的咖啡在这里看上去并不是主流饮品,起码这几天以来,我自己喝到的咖啡都算不上好喝,也缺乏咖啡的香气,反倒是红茶却喝的十分过瘾。</p> <p>hostel 的后院十分惬意,有舒适的沙发和地垫,以及长桌长椅,旁边的绿植增加不少自然气息。四周墙壁遮挡住了光照,即使是阳光强烈的时候,躲在阴凉下也能感受到一丝凉意。</p> <p><img src="@assets/2025/L1090618.jpg" alt="" /></p> <p><em>院子墙</em></p> <p>旅居在这里的人们常会惬意地躺在院子里,一边吃东西,一边喝茶聊天。我也是如此,看到有人在时,会尝试和他们攀谈几句,不过我并不擅长这样做,尤其当我看到对方正埋头看电子屏幕,或是彼此之间正讨论的热火朝天。对闲谈而言,我似乎从未找到任何一种理论和技术去处理它的开始和结束。它就像是一场场梦,每次回忆起来的时候都没有开头和结尾。</p> <p>我像是一条鱼,不太有主见的游来游去,有话题聊了就说上几句,想不到新的点子或表达不出复杂的想法,就找个理由窜到旁边的岩石缝中去。社交沟通的挑战大于享受,想到话题,插入讨论,表达自己观点同时还要尽可能不得罪他人。使用非母语会放大这种挑战,使之成为一种非常消耗精力的活动。</p> <p>充满矛盾的是,它算是一种习惯和技能,你越使用它,你就会越熟练,反之则退化,状况变得越来越糟。因此,外界环境推动我自身改变就变得尤为重要,我感觉生活在德国,有时很难支撑这种正面的进化,每当我使用蹩脚的德语尝试交谈时,总会收获对方或沉默的反馈哦。我知道,其实对方并没有任何恶意,但我无法获得反馈,也就从未知道自身想法是否正确地传递给了对方,还仅仅就是我单纯的自说自话。与之相对,那些来自阳光充沛地带的人,总是会给予我更加积极且正面的反馈,即便我们的语言其实并不怎么想通,但就像太阳的能量散发给所有人一样,我能感知到对方的情绪,理解与体谅。</p> <p>交谈就像是水流。你可以在平静的时候去激发水流,或是顺势加入已经存在的流中,不论是逆流还是顺流都可以,关键在于看清水的运动方式,然后找到契机加入或退出。我很开心,通过这次旅程让我有机会和不同的人发生交流和互动,一来增加了我的自信心,二来让我真正感知到了人和人之间交往的水流。</p> <p>因为我们抵达太早,眼见离 check-in 的时间还很久,为避免打扰他人休息,我们从客厅退到后院,找了一张柔软的垫子,就地平躺了下来。在此时,一声轻盈的响动引起了我们的注意,只见一只瘦削的三花猫从后院高墙上跳下来,灵巧地围到我们身边。作为爱猫人士,必然是逮着机会就要出手,对它的身子一阵上下其手,贪图一个爽快。</p> <p>后来 Z 君告诉我们,它的名字是 Orca,和其它的埃及小猫一样,最大的爱好是觅食。通过几日的观察发现,它常会蹲在后院的玻璃门旁,等看到有人拿着盘子和杯子进出时,立刻围上去,用灵巧的身子和脑袋蹭对方的大腿,等待对方坐下后,又会拿头使劲蹭椅子和桌子的角落,嗲里嗲气地喵喵向人叫嚷。我开始以为,它在对我表示欢喜。后来才意识到,它就是个势利鬼,只喜欢接近有食物的人。它的势利还体现在得到食物的时候,等真尝到了甜头,他的贪婪尽显,攻势变得愈加猛烈,不仅叫嚷的更凄厉凶狠,还拿尖锐的爪子去抓挠对方,真就成了一只「Orca」。我们自从知道了它的这种性格后,接近时候就多留了些心眼儿,留出更多空间,也不会无脑地接受它可爱的诱惑了。</p> <p><img src="@assets/2025/L1090633.jpg" alt="" /></p> <p><em>虎鲸在行走</em></p> <p>Dahab 的猫狗众多,大街上随处可见。狗的体型十分巨大,聚集时总会喧闹地吠叫和争斗,我已经见过好多次狗咬狗的场景。与之相对,猫就瘦弱灵巧许多,一般不会争斗和聚集,除非是为了吃东西。除此之外,猫确实很爱干净。有一次我亲眼看到一只小猫在沙滩上拉了几泡屎,盖上沙土,优雅地蹭蹭屁股离开了。它的优雅,却让我时常感到忐忑,每次赤脚去沙滩的时候都,会增加些提防,生怕踩到新鲜的地雷。</p> <p>虽说都是猫咪,但相比土耳其猫的慵懒,这里的猫就显得野性许多,为了食物总会不择手段。有些性格强势的猫咪甚至会直接跳到桌子和盘子边上,此刻我们必须要狠下心来,装作拍打它们的样子将其驱散,倘若心软下来,势必会遭到它的进犯。当然,也有那些性格与脾气更加温和的猫咪,它会默默地在一旁看着我们吃饭,直到再无觅食的可能便悄悄离开。对这些乖巧的小猫,我们自然会提供奖励,或是鸡肉或是牛肉,以表彰它优秀的餐桌礼仪。</p> <p>顺带一提,当地的猫咪有自己的方言,与普通喵喵声不一样,「Psst」对它们而言才意味着「过来」。这是当地人教我的,如果你想让埃及的猫咪靠近到你身边,可以直接对它 Psst 几声。</p> <p>Orca 性格自由懒散,被摸了会儿,沿着墙边爬走了。我们躺在软垫上,用帽子盖着眼睛,半醒半睡迷迷糊糊的。这里的苍蝇经过多代进化,都成了精,时不时的飞过来趴在皮肤上,普通有规律的晃动已经没用,必须加大幅度且随机摆动,才会不情愿的飞走。我们不堪其扰,反正也睡不着,决定干脆上街溜达一圈,领略下小镇清晨的风光。</p> <p>从住所到海边的直线距离其实不远,海岸边上的道路也十分平直,但一旁全是紧挨的建筑,找到一条从中穿过的小路其实不太容易,七拐八拐,我们终于是来到了海岸边。清晨,太阳才刚刚升起,风不大,海面很平静,气温十分舒服。</p> <p><img src="@assets/2025/L1090667.jpg" alt="" /></p> <p><em>咖啡店街的几个角落</em></p> <p>街上不见太多人,只有零星的几个人在晨跑,还有些人穿着湿衣,两三成群说说笑笑。街边咖啡店和潜水店林立,多数都还没开张,只有寥寥数个人,在玻璃窗户后面打扫卫生。咖啡店多是靠海而建,临沙滩的,会直接铺上地毯和垫子,再摆上个小茶几,算作是一桌。靠近浅滩的,会摆上一般的桌椅和沙滩躺椅,方便人们下海游泳或晒太阳。</p> <p>我们初来乍到,没见过这种类型的咖啡店,也不清楚消费用刷卡和付现金,再加上来得时间太早,每家店都冷冷清清,找不到人询问。我们摸不定主意,在海岸线上反复徘徊犹豫,后来实在累的不行,见到有两人躺在沙滩旁的地毯上睡觉,就一不做而不休选定这家,躺在海边,同他们一起睡觉。</p> <p>我们和老板打了声招呼,点了两杯冰咖啡,然后一股脑地躺在遮阳伞下的地毯上。等上少许片刻,老板拿了两杯塑料杯端了过来。接过了杯子,有气无力地表示感谢,嘬了一口咖啡,咿,真难喝,罢了,睡觉要紧,把小包揣在怀里,听着海浪的声音就睡着了。</p> <p>沙滩上的垫子积满了灰尘,没有一块是完整的。破洞处的布线一缕一缕的,灰色的棉质碎屑裸露出来,到处乱飘。地毯很久没有擦洗了,上面布满了各种颜色的污渍,掩盖了最初的红色花纹。我想,也许是因为当地淡水资源珍贵,清洗地毯会消耗太多水,性价比太低。或是由于当地的环境,地毯和垫子每天都会被强烈的阳光炙烤,这是天然的杀菌消毒,人们无需担心生出的细菌会让人生病。起初我以为只有一两家店会这样,去多了却发现大部分的餐厅咖啡店都是这样,即便如此,没人会觉得这是个问题,人们进了店,买了吃喝,依旧该坐坐,该躺躺。</p> <p>我习惯了城市生活。城市中的人们会理所应当地认为,任何东西都需要保持干净,如果有了污渍,需要立刻及时清洗,等变得干净了,才有资格继续使用。你看看各种广告,不都是以高效净白为卖点的么?「没有污渍」成了一种属于都市的属性,是使用物品的常识,也没有人会去质疑,为什么物品要保持洁净?而为什么只有干净的东西才能被使用?相比于埃及的地毯,脏不脏,破不破,从不是人们去考虑的事情,最重要的,恰恰是有没有。只要有,就能席地而坐,好好休息,没有,就起身来离开,寻找下一个能栖身的地方。</p> <p><img src="@assets/2025/L1090640.jpg" alt="" /></p> <p><em>路上的羊</em></p> <p>重回城市,我骑车路过高端家具店铺。硕大的玻璃橱窗中,展示着各式各样的高端沙发和座椅,材料不同,形态各异。看着如此多的变体,突然就想到了在埃及的地毯和垫子,它们看着又脏又破,且只提供坐和躺两种姿势。两相对比,我感到莫名的羞愧。后者和自然融合共生,而前者更像是一种迎合,去迎合人类在城市中构建出的模拟生态,生态中包含了钢筋水泥以及各种人工的行为和活动。什么环境,什么场合,就要用什么样的沙发或椅子,要是不契合,保准就会有人眉头一皱,愤然离场。</p> <p>不知道过了多久,我再次睁开惺忪的睡眼。周围仍如刚才一般平静,倒有几只野生的大狗聚集在旁边。面对他们,我不敢多动,只任凭它们来回嗅闻,<em>呈现出了五狗绕康的奇观</em>。它们呆了几分钟,见没找有食物,就一窝蜂的跑走了。</p> <p>躺在前方睡觉的两位大哥也醒了过来,其中一位在用阿拉伯语和老板攀谈,看样子应该是本地人。不久,只见他熟练地端来一盏水烟壶,摆放在他们喝剩的啤酒旁。清晨刚醒,便烟酒交融,我在心里不由得对他心生佩服。</p> <p>他环顾四周,见四下无他人,便多走了几步,来到我们身边站定,主动与我们攀谈。我们简单做了自我介绍,随即聊起一些有的没的,具体的内容我早已记不清,大概是关于刚抵达这个小镇的第一印象与感受。</p> <p>只记得在聊到换钱的时,他提到自己从事反洗钱相关的工作,今天一早刚下夜班,便直奔海边来放松。他用着算不上流利的英文提醒我们,别在黑市换钱,那些汇率坑得厉害;最稳妥的方式,是直接去银行找人工窗口换。</p> <p>眼看时间差不多,我们向他道谢并准备结账离开。临走前,他热情地与我们一一握手,还不忘补上一句:" 今晚附近会有 techno 音乐,欢迎来玩。"</p> <p>从小我便被教导要提防陌生人,尤其是那些主动示好的,总被告诫「有所图谋」。于是面对街头那些带着笑意靠近的身影,无论男女老少,我总会本能地保留几分戒备。而在德国的生活经历,也似乎一再验证了这种观念:不是上门推销网络的陌生人,就是在火车站向人讨钱的过客。久而久之,我几乎遗忘了,人之初的热情,其实是一种最质朴也最温暖的性格特质,它原本拥有拉近彼此距离的力量。</p> <p>我并不想指责那些假借热情行图利之事的人。我只是感到一丝悲伤——那份本应纯真的人性,被慢慢异化为一把锋利的工具,而它真正的珍贵与温柔,却很少有人愿意细细体会与珍惜。</p> <p>在我们停留的这几天里,类似的闲聊和对话几乎每天都会上演。无论对方是熟悉的朋友,还是素未谋面的陌生人,我都能真切地感受到那份来自人心的善意与温情。从个体的视角看,也许每个人都只是芸芸众生中平凡的一员,但正是这些不期而遇的交谈与相识,让每一个平凡的瞬间都散发出独特的光芒,使每一个人,在此刻,都显得那样与众不同,绽放出令人惊艳的非凡之光。</p> <p><img src="@assets/2025/L1090665.jpg" alt="" /></p> <p><em>不认识的当地年轻人,但却给他们拍了照</em></p> <p>趁着太阳还没升得太高,我们绕回到了 hostel。虽然还没到 check-in 时间,Z 却告诉我们,房间已经提前收拾好,可以直接入住。我忍不住伸出大拇指,用力对他表示了感谢,实际来的刚刚好。</p> <p>进了屋,我们把行李随处一丢,顾不上满身尘土与汗气,径直扑向了刚收拾好的床铺。眼睛一闭一睁,才发现时间不过正午,可身体与意识却仿佛已跨越了好几个世纪。那一刻疲惫袭来,却也在提醒着我们——这段旅程,才刚刚拉开序幕。</p> 自由潜入新一年https://blog.kaiyikang.com/posts/2026/%E8%87%AA%E7%94%B1%E6%BD%9C%E5%85%A5%E6%96%B0%E4%B8%80%E5%B9%B4/https://blog.kaiyikang.com/posts/2026/%E8%87%AA%E7%94%B1%E6%BD%9C%E5%85%A5%E6%96%B0%E4%B8%80%E5%B9%B4/Thu, 01 Jan 2026 00:00:00 GMT<p><img src="@assets/2026/dahab_diving_03.webp" alt="" /></p> <p>这次我们来 Dahab 玩,我报名了自由潜水 Wave 1 的课程(课程分多个等级,这是初级课),不过最后因为平衡压力的问题,耳朵有些受伤,遗憾没能完成所有项目,只能等着未来找机会补考。</p> <p>这是真实发生的事情,不是完整的英雄史诗,也没有克服困难与恐惧最终迎来美好结局的戏剧化情节。</p> <p>有天训练的风浪很大,尤其是训练快结束的时候,我们往岸边游。风朝岸边吹,海浪被卷着向岸边拍。海底的砂石被卷起来了,和沙尘暴似的,完全看不清水底。呼吸管几乎失去了功能,每前进几米就会进水,我喝了好几口海水,呸呸呸,真的太咸了。那是种苦咸,让嘴里发麻,味蕾成了累赘。我担心伤胃,没提前吃晕车药,飘了一会儿我就后悔了。身体里面的调节装置根本追不上海浪翻腾的速度,失去了功能。胃里上下翻腾,在离海岸十几米左右的位置,终于呕吐了。不过因为之前没怎么吃东西,所以也吐不出什么,只是肌肉痉挛,稍微意思了一下。不好意思,让海里的鱼失望了,你们损失了一顿美味的大餐。连滚带爬的,我终于挪到了岸边。海水拍着岸上的礁石,掀起巨浪。我们几个踉踉跄跄的,连拖带拽的终于站到了陆地上。看着白花花的海浪,心有余悸之际,还有些感慨,我刚才都在和什么可怕的玩意儿做斗争?</p> <p>经过了几个月间断的尝试,并经过下海尝试,我可以自信地说,我的确学会了法兰佐平衡。学习这个技巧全看命,就看你有没有被 Herman Frenzel 钦点(他发明的这个技巧),被点到的没几个小时就能学会,没被点到的就得花上好几个月。兴许是我名字有些难读,可惜没有被点到,为此我花了好几个月的时间来学习并确认自己到底到底会没会。它所涉及的所有部分全都在皮肤里面,并集中在鼻子和嘴周围。如果你不会区分里面肌肉的运动,会感觉自己脸上盘着一坨意大利面,不仅错综复杂,并且还带着奶酪,黏黏糊糊的。我花了很久才识别到软腭在哪里,中耳是哪部分,声带又在什么位置。在拆解了这坨意面后,我终于对面门有了清晰的认知,知道了去年以及前年的平衡技巧做的有多么糟糕:一入水技巧就是错的,耳朵压力平衡不了,压力一大就痛,痛了还不知道发生了什么。一度还以为自己耳朵是不是残疾,兴许要和潜水这项运动永远说再见了。从现在的理解出发,那时的我只是对自己极度不自知罢了。所以,即使带着些许遗憾,我也不会强迫自己一定要完成所有的项目,毕竟受伤就是受伤,我能实际感受到了它,唯一能做的就是等待,等恢复之后,未来找机会再去尝试。</p> <p><img src="@assets/2026/dahab_diving_07.webp" alt="" /></p> <p>很难说我在和什么做斗争,也许是自己,也许是大海,具体描述不出来,到底是什么在阻止我往下钻,明明气很充分,耳压也得到了适当的平衡。我想辩解,那是一种纯粹的恐慌,身体它自己就抽动了,我感受到了,我快不行了,所以我上来了。但抽象的解释大概没法说服其他人,毕竟那并非一种显形化的问题,说白了就是心里毛病。抽象的问题自然就会得到抽象的答案,最终的结论是:别慌,往下干就完了。我满意这个答案,以性格角度讲,我属于离危险还隔着十万八千里,就会杵在那里开始叫喊着,要完了,死定了。极度谨慎的情况下,即便我拼了老命赴死,最后也不过落得了一个擦破皮的结果。皮死了,我还活的好好的。精神意志似乎无法让我成为真正意义上的冒险家。</p> <p>水面之下相对平静。身体被来自四面八方的水包裹着,挤压着,时间变得异常古怪。有时候刷视频,看见新手下了水,腿踢蹼像是蹬三轮车,两只腿旋转的飞快,我躺在床上笑他们动作古怪,心想自己应该没什么问题,结果亲自下水啪啪就被打脸:自己的小三轮蹬得也起劲儿。后来分析,我太紧张了,注意力全在其他肌肉上,根本顾不上腿,所以潜意识就找了个最快的一种蹬腿姿势,也就是蹬车。虽然它对身体而言是最快的,但对海下而言效率却是最低的。身体的直觉不符合新世界的常识。为了成为新世界的移民,身体就需要学新的礼仪。其中一个重要的礼仪是缓慢的有接节律。平压要舒缓且带有节奏,摆腿踢脚蹼要缓慢并吃住力量,身子也要缓慢调整以保证姿态正确。不能是行事潦草的年轻人,得是精准有力的阿婆才行。</p> <p>咔,咔,咔,而不是咔咔咔。</p> <p><img src="@assets/2026/dahab_diving_00.webp" alt="" /></p> <p>标准流程听起来很诱人,但真的很看天时地利人和。风平浪静的海面,温暖和煦的阳光,充分休息好的潜水员,绝对是最棒的组合搭配。每个动作细节,以正确的方式正确排序组合,然后全部都在恰当的时机展现出来。身体感知到,还控制住了,哦天,这太棒了。当然,这是理想状况,实际上,我的身体更像个草台班子,各种动作元素因为受到了恐惧和紧张的操弄,变得非常无序且不可知。有时候我关注了呼吸,就忘了踢腿,想到踢腿的时候身体早就撞在了绳子上。上半身和下半身仿佛是两套系统,各自有各自的想法不说,甚至还相互敌对。头脑中的精神大将因为恐惧,变得麻木且恍惚,发挥不出应有的作用,身体就此变成了纯粹的躯体。</p> <p>空间也变得古怪。日常我们都生活在一个平面的空间,只能前后左右移动,就算向上跳,也仅能抬高一点点,但下了水,前后左右不太重要,反倒需要你能控制上下移动。如果以脚为下,头为上,意味着你得向上走几十米。如上所述,脚作为动力源非常重要,它需要稳定且有效的工作。身体姿态则是另外一个重点,要稍微挺直,顶胯收屁股。臂膀和头作为向导,引导火箭朝着正确的方向发射。后来我回顾了一下自己的动作,不像火箭,倒像是灵巧的死鱼,要么在莫名其妙的沿着绳子转圈圈,要么就横过来被强大的压力推回水面。从这个角度讲,我要是教练,我也很不愿意承认这是一位正确的入门者所做出的动作。我把所有相关的视频收集在文件夹中,并起名为「弱智行为集锦」,个人觉得恰如其分。</p> <p><img src="@assets/2026/dahab_diving_02.webp" alt="" /></p> <p>正确的动作不再是一种书面理论,而有特别强的实际意义。我们经验丰富的教练A曾提到,正确解决诸多小问题后,整体就不会出什么问题。我认为说的对,有些动作我感觉诡异极了,但等小问题得到纠正后,动作竟然变得丝滑不少,这真令人惊讶。水下像是个相对理想的沙盒环境,每次下水都会重置默认参数,你只要以正确的步骤和流程操作并运行身体,那么总会在这个环境中得到正确的结果。</p> <p>在海上漂习惯就不怎么吓人了,别看海下深不见底,让我够还真够不到,所以大部分都是视觉上的恐吓。我喜欢涂了沐浴液的面镜,清澈透亮不起雾,阳光透出来的时候,纯净的蓝色中穿着一抹鲜亮的橘色,这是引导绳的颜色,当然它可以是别的颜色,但我还挺喜欢这几次训练的小橘绳子的。有一两次,我看到了路过的小生物,不知道叫什么名字,像是一种微型水母,身体长长的,透明的,重要的器官是橘红色的,小小的包裹在透明的身体里,下面带着絮絮,很潇洒随着海水从我眼前飘走了。</p> <p>我看着它入了迷,这是属于海洋独特的奇幻时刻。</p> <p>我靠坐在一个软垫沙发上,沙发很牢固和平稳,但我的身体依然感觉到上下起伏,就像是海浪的起伏,还在时时刻刻的影响的身体感受器,还挺奇妙的。我想这会驱使我再去尝试潜水。</p> <p><img src="@assets/2026/dahab_diving_06.webp" alt="" /></p> Dahab - 颠簸的峡谷https://blog.kaiyikang.com/posts/2025/dahab_3%E9%A2%A0%E7%B0%B8%E7%9A%84%E5%B3%A1%E8%B0%B7/https://blog.kaiyikang.com/posts/2025/dahab_3%E9%A2%A0%E7%B0%B8%E7%9A%84%E5%B3%A1%E8%B0%B7/Sat, 12 Apr 2025 00:00:00 GMT<p>我们在 Dahab 的行程是分开的,她安排了自由潜水的训练和考核,而我没有那么硬核,只报名了潜水的初级入门课,一天就能结束,因此错开的时间我可以自由探索,随意安排。</p> <p>抵达的第二天,她早早开始了训练,我则租了辆自行车,在镇上闲逛。到了晚上,大家聚在一起交流白天的心得经验时,她见我说不出什么奇异精彩,便强烈推荐我去峡谷一日游。一不做二不休,我立刻在 hostel 群里招呼起来,管理员 Z 君反应迅速,马上联系了房主 M。房主 M 在这里朋友众多,几乎所有项目都有特别的门路,不出几个小时,我们就收到了价格和行程安排。我心想第二天也没什么特别安排,就欣然报名。住在其他房间的纪录片创作者 A 君看到群里的消息,临时起意,也决定一同前往。</p> <p>在 Dahab,许多活动或一日游大多围绕周边展开,车程一到两个小时,距离不算远,但由于语言不通且社会环境不同,自己安排几乎不可能,因此让旅行社安排是最好的选择。市中心海岸边有许多旅行社,即便你有意忽略,蹲在门口的小哥也会极其主动地靠近你,递上传单。走一路,问一路,竞争之激烈可想而知。</p> <p>游玩的活动规格虽有差异,但内容大同小异,因此和旅行社砍出一个好价格才是关键。我们有幸直接省略了这一步,毕竟每次都能让 hostel 帮忙联系,获得最合适的价格。每次想到这里,我都会感慨,幸亏当初选择了正确的住处,而不是破旧的度假酒店,否则真是亏大了。</p> <p>第二天一早,外头刮起了猛烈的东北风,虽不利于浮潜,却丝毫不妨碍我们去峡谷徒步。清晨,我们早早收拾好行李,坐在客厅里等待出发。时间一到,Z 君就匆匆走来通知车已到。我们于是起身,踏上了今天的行程。</p> <p>车是一辆改装的大面包车,后排座椅全部拆除,靠近窗户的位置安装了两排坐凳。没有固定座位,也没有安全带,我觉得自己像货物一样,被搬上了后备厢。车子出发了,在城内穿梭,为了接载后续的旅行者。</p> <p>车门第一次开启时,一位女性上了车。她穿着现代且略显浮夸,进了车厢就大声用英文与我们搭话,说自己来自开罗,目的是来这里旅行。清晨时分,我精神不足,便轻声应和,敷衍了事。车开了一会儿,又有一位女性走来,热情地与刚上车的那位打招呼,看来她们是结伴而行的好友。只见她手里端着纸壳,里面盛着两杯冰咖啡。咖啡在塑料杯中随着颠簸晃动,仿佛随时会洒出来。看着她们张扬浮夸且不拘小节的性格,冥冥之中,我预感会有些麻烦事发生。</p> <p>正当我盯着那快要洒出的咖啡思考时,她踩着车边缘上车,不巧没踩稳,手中的杯子脱手,咖啡正应验了我的感召,还真就洒了出来。靠近车尾的地方全是咖啡。见状,她们连忙拿出纸巾开始擦拭,还用阿拉伯语彼此念叨着什么。司机和导游似乎没多问,跑到旁边树下的阴凉处聊天,时不时回头望向车内,像是在等她们解决问题。咖啡没溅到我这边,却溅到了对面法国小哥的裤腿上。她们连忙道歉,他少言寡语,安静地说没事。虽嘴上如此,我却从他的表情中看出一丝疲倦和无奈。</p> <p>咖啡擦拭干净后,我们终于踏上路途,开始今天的行程。人的性格总包含各种特殊属性,人们因相似性格结为朋友,而某种特定性格的人,做事风格也往往统一且和谐。咖啡事件刚平息,车刚驶出镇子,那位女性就拿出随身携带的 JBL 小音箱,大声询问谁想听歌。见无人回应,她便自顾自地播放起带有现代韵味的阿拉伯歌曲。车尾的小曲没放几首,开车的小哥似乎察觉了什么,右手一边切换档位,一边用手指在操作盘上戳戳点点,前排忽然传来悠扬的古典阿拉伯歌曲。这两个人仿佛走路一瘸一拐,完全没有任何默契和协调。我们夹在中间,像肉片被夹在面包汉堡里,精神受到了折磨和历练。不知过了多久,前后的音乐最终逐渐消失,我们紧绷的神经也慢慢舒缓下来。</p> <p>旅途中,另一件关于音乐的奇事让 A 君心烦意乱。车行驶在荒漠途中,我听到音乐不断切换,每隔几秒就换一首歌。起初我以为是车载音响的技术故障,因此没太在意。过了一阵,A 君私下告诉我,这全是司机的缘故。他指向司机的位置,让我看他的右手。只见司机的右手不停点击按钮,每点一下,音乐就切换下一首。我恍然大悟,原来一切都是司机在暗中操作。A 君被声音烦扰,不停用英文提醒司机和导游,试图让他们安静下来。我没听清他们对话的细节,只见司机嘴里不断咀嚼着阿拉伯语,手却一刻不停地操作。A 君与对方沟通无果,只好转头向我诉苦。我没有被音乐折磨,只把它当作一场人类特殊行为的奇观。本着好奇心和观察研究的态度,我更愿意理解为司机像个严格的 DJ,有着自己的特殊品味和准则。他严苛到能在音乐开始前几秒内判断该曲是否合耳,结果几乎所有音乐都被他切掉,而唯一保留的一两首,从我的角度看,也没什么特别动听之处。我安慰 A 君,司机有自己的追求和想法,只需平和心态,顺从罢了。</p> <p>最后一件关于音乐的小插曲发生在走出峡谷时,我们遇到一片辽阔的荒漠。我走在前面,后面同行的一对旅伴带着一个小音箱。正当我欣赏沙漠风光时,身后传来磅礴的曲调,咚咚咚,像什么影视剧的原声。等到进入了主旋律,响起一段女性唱段「哈——啊——诶——」,我这才恍然大悟,原来是《Dune》的主题曲。我有些想笑,又有些无奈。这个曲目选择倒很符合氛围,但为何明明身处荒郊野外,却依然无法彻底摆脱人工制品的影响,可见它对我们的影响已深入骨髓。</p> <p>离开小镇大约一小时左右,只见车缓缓离开主路,驶入荒漠中。手机收不到信号,地图软件中也找不到方位,文明生活慢慢沉睡,自然与野性的另外一面逐渐苏醒过来。抵达目的地,我们纷纷下了车。导游用简单地英文告诉我们大致行程:下到峡谷,就是一条单程路,包放在车里,手里拿上水,一直往前走即可。</p> <p><img src="@assets/2025/L1090680.JPG" alt="" /></p> <p>下峡谷的路算不上困难,但的确陡峭。观光的游客众多,岩石被踩出了非常明显的落脚点。一条缆绳垂到谷底,手里紧握绳子,左一步,右一步,我们听从导游的指示,很快就下到谷底。巨大的白色岩壁围绕在身体周围,向上延伸,遮盖了部分天空和阳光。没有了阳光的照射,我忽然感受到一丝凉意。奶白色的岩石圆滚滚的,表面带有磨砂的质感,手指轻轻摸上去,很容易就能从岩石上带下白色的细沙,感觉有些不太牢固。而由于长时间的积淀,不同的岩石层层叠加,不同的颜色和质地一条一条的。流线型的形态,像是不同的丝线在墙壁上飘忽游走。身处其中像是进入了哈哈镜迷宫,惊喜到处都有,一点都不无聊。</p> <p>我们以不一致的步调持续向前走。阳光被岩壁遮盖,洒下斑斑驳驳的光影,中间的沙地和稀疏植物被阳光晒得发亮。仔细观察,还能看到时不时有甲虫路过。这些少见的活物,仿佛行走在一幅画框中,让人感觉像是在观赏一部高保真的沙漠纪录片。</p> <p><img src="@assets/2025/L1090809.JPG" alt="" /></p> <p>峡谷的道路并非一帆风顺,有时会遇到岔路口,我们需要在原地等待导游的提示才能继续前行。导游是一位贝都因人,头上绑着红色的头巾,身着 T 恤衫和布裤,脚下踏着一双简单的凉鞋。他没带任何特殊装备,最多就是手里拿了一瓶水。他非常尽责,不时沿着队伍来回跑动,确保没有人掉队或迷路。</p> <p><img src="@assets/2025/L1090837.JPG" alt="" /></p> <p>这位导游皮肤黝黑,脸上因岁月流逝而出现的沟壑宛如岩壁纹理,既古老又充满智慧,仿佛与这片土地融为一体。我从远处看到 A 君和他攀谈,之后 A 君兴奋地向我讲述了导游传递出的智慧。我感觉一切都很和谐自然,或许正是因为他一直生活在这片充满巨大能量的土地上,才能拥有如此朴素且充满力量的智慧。</p> <p><img src="@assets/2025/L1090817.JPG" alt="" /></p> <p>峡谷的终点是一片绿洲,那里充满着当地人生活的痕迹。导游带领我们进入一个棚子下休息。棚子的顶棚由棕榈叶搭成,下面铺满了阿拉伯地毯和坐垫,但因时间过去太久,使用的人太多,毯子早已灰乱破旧不堪。光线透过稀疏的棕榈叶照到地面,随着风缓慢摇曳,一切如梦似幻。棚子的旁边有个篝火坑,另外一位贝都因人蹲在那里帮我们煮茶,只见他缓慢地抬起煮开水的茶壶,异常平稳地将茶水倒进一个个水杯中。</p> <p><img src="@assets/2025/L1090938.JPG" alt="" /></p> <p>我们接过茶杯,连忙向他表示感谢。红茶十分美味。我一边啜饮茶水,一边坐在棚子的角落与 A 君闲聊。因为之前已经与他有过接触,加之身处这片奇幻的地方,我稍微放开了些表达的内容。用非母语表达深刻的事情对我一直是个挑战,可以说我从未有过类似经历,用外文表达自己所遵循的生活哲学和洞察。每次到达这个十字路口,我都会原路返回,或闭口聆听别人发表看法,或找个理由起身离开。能力在逐渐萎缩,但想法始终存在并悸动。我很感激他尝试倾听并追随我模糊不清的想法,并以客气的方式表示理解。我觉得自己像一位刚学乒乓球的菜鸟,努力尝试与对方打个来回。我能感受到,他在用更强的能力维持球的流动。我十分感谢他的努力,并会一直记住那个奇妙的时刻。愿我未来有足够的能力应对这种挑战。</p> <p><img src="@assets/2025/L1090942.JPG" alt="" /></p> <p>午餐是导游提前准备好的,虽然看起来非常简单,都是些素菜,但闻起来很香,味道非常自然,吃完后也感到十分舒服。吃喝结束,稍作休息后,我们又踏上了行程,前往彩色峡谷。</p> <p>本以为车辆会一直行驶在平直的柏油马路上,没想到司机一打方向盘,直接开进了荒漠。还没等我反应过来,身体已经随着路面开始颠簸。我们乘坐的是一辆朴素的面包车,没有任何减震等高级功能,脚下就是一块大铁盘,四个轮胎遇到任何颠簸,都会被放大,传递到身体上。</p> <p>一辆辆车行驶在坚硬的石头路上,压出了相互交织的车辙印。虽然司机努力控制车辆沿着既有轨迹驾驶,但难免出界,于是我们被迫穿梭在不同的时光之中,努力寻找此刻的印记。每一次穿梭,都是一次颠簸。车辆刚开始颠簸时,我还能感受到刺激,想着这一趟玩得很值,竟然还附带了过山车项目。颠簸持续了很久,且异常激烈,以至于我只能将全部心思放在防止自己飞出去上。我的肌肉仿佛消失了,只剩下一堆坚硬的骨骼在来回碰撞。</p> <p>我无心再和坐在旁边的 A 君分享感受。为缓解晕车,我只好向窗外看去,被迫接受着路面和司机硬塞给我的抖动画面。车还在疾驰,同行的另外一辆车也从侧面飞了过来。我盯着另外茫茫大荒漠上的吉普车,仍觉得自己还沉浸在荒野纪录片中。</p> <p><img src="@assets/2025/L1090961.JPG" alt="" /></p> <p>霎时间,荒漠成了大海。颠簸的路面是大风煽动的海浪,而砾石则是被激起的浪花。我们就像是坐在一艘小船上,行驶在苍茫无人的大海中央。任凭地势的引领,把我们带向神秘的彼岸。</p> <p>到达彼岸的路途并非一帆风顺。当车要翻越沙丘时,由于角度过大,无论怎样调整,车轮都无法获得足够的抓力。司机尝试了多次仍未成功,就跳下车,开始调试轮胎。在烈日炙烤下,他身上的白袍刺眼耀眼。其实我并不清楚他具体在做什么,只听见呲呲的声音,也许是在放气。他蹲在那里捣鼓了几分钟,随后重新发动了汽车。车轮在松软的沙丘上呈 S 形,像一条蛇般上下窜动。开了一会儿,终于翻过了沙丘。</p> <p>这里的司机总会遇到各种各样的问题,除了环境的影响,还有车辆自身的故障。我女友也曾遇到过一次类似的情况。那次她乘坐出租车前往城镇的北边进行泳池训练,途中车辆突然熄火。她们正商量是否换车时,司机毫不犹豫地跑到车前开始捣鼓。据她描述,司机先用两根线反复接触测试,未见反应。随后不知从哪里掏出一段导线,用留出的长指甲剥开线的塑胶外皮,再将亮晶晶的铜线缠绕在旧线上。司机一言不发,不知具体操作了什么,只知道折腾了一会儿,随着引擎轰鸣,车竟然重新启动了。</p> <p>一望无际的荒漠和公路,逼得你不得不相信并依赖司机,即使他们看上去多么不可靠。他们总会遇到各种问题,也总以粗犷而原始的方式解决。我们习惯青睐可靠的事物和人,对不确定性心存退避,可这些司机似乎总在不确定性的边缘徘徊。我们与他们之间仿佛绑着一根绳子,当他们安全时,我们松了口气;当他们险些坠落悬崖时,我们也会心惊胆战,不知未来将如何。只有安全抵达目的地,关上车门的那一刻,我们悬着的心才终于落下。</p> <p>在车辆的疾驰下,我们抵达了彩色峡谷。</p> <p>相比白色峡谷的纯白,我更愿称彩色峡谷为红色峡谷。山谷的岩壁仿佛被染上了色彩,展现出比白色峡谷更为丰富的质感。我承认它的美丽,但上午的行程和车上的颠簸让我异常疲惫,难以静心欣赏,只想着如何能尽快穿越,抵达终点。</p> <p><img src="@assets/2025/L1090973.JPG" alt="" /></p> <p>即使如此匆忙,岩壁的形状依然给我留下了极为深刻的印象和丰富的想象。由于风化作用,顶部的岩壁出现了许多空隙。随着时间推移,这些空隙被撕扯、扩大,内部形成了完整的通道,外部只剩下瘦弱的石柱支撑。拖着疲惫的身体,我抬头望去,竟觉得这些石柱和通道极像庙宇的走廊。迷你的僧人寄宿在石块之间,在天顶的石柱走廊中穿梭,传递精神,吟诵诗歌,仿佛试图将我召唤到另一个神秘的领域。我怔了怔,稍作停顿,才回过神来,继续向前走。</p> <p>我们抵达终点,坐回车里。先前喧闹的游客也不再折腾手中的音箱,唯有司机仍不停地点按切歌按钮,但我们已累得无暇理会。</p> <p>车辆从土路重新驶入柏油路,行驶得安静如同一张松软的床垫。我靠在车窗旁,背脊虽被迫挺直,却还是坐着睡着了。我没有做任何梦,也许梦境遗落在了峡谷里。等醒来时,就已经回到了镇子上。</p> <p><img src="@assets/2025/L1090985.JPG" alt="" /></p> Dahab - 尚未自由潜https://blog.kaiyikang.com/posts/2025/dahab_4%E5%B0%9A%E6%9C%AA%E8%87%AA%E7%94%B1%E6%BD%9C/https://blog.kaiyikang.com/posts/2025/dahab_4%E5%B0%9A%E6%9C%AA%E8%87%AA%E7%94%B1%E6%BD%9C/Mon, 14 Apr 2025 00:00:00 GMT<p>自由潜水不能算是休闲活动,而是一项运动和体育项目,且极富技巧。这些技巧既包括通常意义上的「看得见的技巧」,也包括「看不见的技巧」。后者通过海洋独特的环境,显化并放大,使其成为一种甚至决定性的因素。</p> <p>如果达不到特定的技术要求,就无法入门。而我现在正是在这个门口来回徘徊,犹豫不决,不确定未来是否有一天能迈入这扇大门。</p> <p>接下来,我会向你详细讲述我的经历。</p> <p><img src="@assets/2025/IMG_3439.JPG" alt="" /></p> <p><em>这是 <a href="https://www.linyue-zou.com">她</a></em></p> <p>Dahab 算不上什么旅游名胜,但凡来到这里的人,要么带着明确的目的,要么是被忽悠过来的,几乎没有中间状态。在这里游玩的规律是,几乎所有项目都有一定门槛。换句话说,想参与有趣的项目,要么天赋异禀,要么有特别的勇气和精力,要么拥有纯熟的技巧。几乎不存在那种不费吹灰之力就能体验的项目,类似参观名胜或博物馆,只需一双脚即可。</p> <p>经过几天的体验,我深刻感受到,Dahab 一点也不谄媚。它大方地展示蓝洞、峭壁、潟湖,如果你想玩,就可以来;至于你有没有能力,那是你的事,不是它的。它不会为了让你轻松玩耍而降低门槛和身段。这种主动性的气质在众多旅游目的地中独树一帜——要么你投身岩壁和海洋,要么你躺在阴凉处吐槽。因此,我的建议是,来之前请充分发挥想象力,明确自己想参与哪些运动,以避免无聊。</p> <p><img src="@assets/2025/DJI_20250418125714.jpg" alt="" /></p> <p><em>鱼</em></p> <p>我报名了自由潜水体验课,属于 AIDA 体系的一级。简单来说,潜水分为两大类:一种是我们日常所说的「潜水」,即带着氧气瓶装备下水;另一种是不带任何装备,仅凭肉体下水,这也是「自由」一词的含义。前者较为知名的是 PADI 系统,后者则有 AIDA 或 Molchanovs 系统。这些名字其实不重要,随手一查就有大量资料。虽然我加入了系统,课程也有对应等级,但我心里很清楚,这并不代表个人能力,仅是个到此一游的证明。我没有抱有特殊期待,也不愿过多宣传所谓的证书。</p> <p>由于当天风大,水面不平静,我们将一天的课程拆成两天,半天理论,半天实践。教练住在城镇北路,从住所出发步行约十几分钟。因为没有门牌号,我只能大致沿着微信定位走。到达后,我四处张望,分不清哪里是废墟,哪里是住宅,只见一丛黄花树投下阴影,还能闻到浓郁的羊粪味。</p> <p>教练见我到了,下来给我开门。我沿楼梯上去,和他进了屋。屋内装修朴素,像国内老房子,功能性强。地上铺着地毯,旁边放着教练的各种潜水的装备和教具,虽然叫不出名字,但看起来非常专业。因为是初级课程,内容不复杂,多为介绍性质。核心是安全,其次才是技术。课程结束后,我和教练闲聊了一会儿,时间差不多便离开了。</p> <p>课程中提到,自由潜水是一场「mental game」(精神游戏)。这句话让我印象深刻。一般来说,游戏是一个大框架,适用于大多数活动。它考验人们各种能力,如忍耐力、技巧、灵活性、记忆力等。我对不同能力的印象也会与相应活动挂钩,比如记忆力像带眼镜的书生,灵活性像瘦高的运动员,后者是前者的显化。至于精神能力强的人是什么样,精神显化后又如何,我说不准,因此更好奇,想亲自 " 玩玩这个游戏 ",体验精神力的强弱。</p> <p>另一个让我印象深刻的是「buddy 模式」,即每次自由潜水必定两人及以上同行,以确保安全。因此,自由潜水不仅对个体心智有要求,还要求人与人之间有连结。这两者结合,像创世的元素,一个向内,一个向外,内外合一才算完整。两人、生命、海洋,许多元素自然且和谐地融合在自由潜水中。</p> <p>回程路上,我一直在品味这些奇妙的概念。当然,理论终归是理论,最终还是要看实践如何。</p> <p><img src="@assets/2025/DJI_20250418144003.jpg" alt="" /></p> <p><em>鱼和珊瑚</em></p> <p>几天后,风停了。我们选择一个天气晴朗的上午,体验第一次下水。</p> <p>小镇南边有座灯塔,是镇上最热闹的地方。得益于得天独厚的地理优势,这里是潜水最佳地点,近处浅海适合浮潜,稍远则可进行水肺或自由潜水。</p> <p>我们找了家咖啡厅,教练从后院搬出浮标。我打听得知,教练给别人上课时,要么与潜店合作,要么自己单干。他只需提前和咖啡店打好招呼,把装备暂时存放那里,每次上课时点些吃喝,算是双方互利的交易。</p> <p>我先穿上湿衣。湿衣摸起来像化纤材质,有厚度,按上去软乎乎的,似乎中间填充了保温材料。穿湿衣要全身包裹严实,从头到脚。即便太阳长时间照射,海水依旧冰冷,若穿得不严实,身体容易失温。此外,因海水浮力大,为了下沉,身体还需绑额外配重。最后戴上面镜和呼吸管,拿起长脚蹼就可出发。自由潜装备虽不少,但比水肺潜水轻便太多,后者甚至需要推小车搬运装备。</p> <p>下水后,浅滩很浅,印象中水深只到大腿。教练指导我做几个简单动作,如吐水、趴着放松、如何用脚蹼等。其中呼吸放松最有趣,趴在水面上,扫描全身,放松身体,然后在最放松时屏息。身体被海水托起,一动不动漂浮,唯一能动的似乎只有大脑神经。思绪激荡,一会儿飞扬,一会儿沉寂。飞扬时五彩缤纷,沉寂时脑袋空空,只剩纯粹感觉。身体夹在天空与大海之间,变得薄如蝉翼,融化在交界面,随海浪飘荡。</p> <p>人们用各种方式平息跃动的神经,教材上写可以想象一支燃烧的蜡烛。有人想象动物,有人想象植物。我经验不足,平息毫无章法,甚至搬出「心经」救场,但效果不佳,因背诵时忘词,惹得我恼火,心态更不稳。最终我屏息一分半钟,算普通成绩,相比那些能憋气好几分钟的专业人士,差距明显。</p> <p>完成简单训练后,我们向稍远处前进。深度约七八米,海水清澈透亮,可见海底。教练固定浮漂后,我尝试下潜。</p> <p>我倒立,扶着绳子轻轻向下拉,耳朵压力骤变,疼痛随之而来。疼痛由面状逐渐变尖锐。每次下潜,我都会尝试平压技巧,但无论陆地训练多好,被海水包裹后立刻失效。即使冷静,仔细揣摩喉部、声带、咽部和面部肌肉,海浪一飘过,感觉又混淆在一起。海水压力来自四面八方,极致且无死角的包裹,没有定形流体会钻入任何可能的缝隙。深度越深,压力越贴合且紧密,疼痛随之加剧。压力如因果律般无条件发生,面对它,除了正确技巧别无他法。那种无力感在水下被放大,要么做不好,要么做不成,一点办法都没有。我没有怨言,仅下潜一两米便迅速返回水面。</p> <p>我尝试多次倒立下潜,均无功而返。挂在五米处的黄色网球鲜亮如海市蜃楼,向我招手呼唤,但可惜我无法满足它的期待和召唤。水压和技术成了拦路虎。</p> <p><img src="@assets/2025/DJI_20250418144205.jpg" alt="" /></p> <p><em>更多的鱼和珊瑚</em></p> <p>在海中待了一个多小时,身体开始发冷,我告诉教练可以结束。回岸上,阳光强烈,但身体仍不自觉颤抖。我回想这次下水,体验新鲜感占大部分,另一部分是不甘心,像临门一脚未成。虽不完满,但能接受,不至于让我放弃潜水。好消息是,截止到写作的时候,耳朵平压技巧已掌握,但仍需实际下水尝试和调整。</p> <p>后来和女朋友聊起这次经历,她说她的教练看到我们训练位置离岸距离,评价那个位置和深度非常「神奇」且「微妙」。</p> <p>我非常赞同,感同身受。完全不潜水的人会呆在岸边,想入门的人会离岸更远,而我正处于两者中间,既不算入门,也不算不入门,这非常准确地描述了我在学习潜水路上的矛盾徘徊。</p> <p>自由潜似乎本身带有矛盾属性。顺利下潜既需完成特定动作,又要保持绝对放松。大多数情况下,两者相互矛盾:动作牵拉肌肉使其紧张,放松则抵抗紧张使动作停滞。这种矛盾在特殊环境中被彻底激发。我相信,每位技艺高超的自由潜水者,都是追求平衡的大师。他们懂得在紧张与放松间找到平衡,像鱼般灵活游走于动与静的缝隙,追求最大深度和最深层的自我。</p> Dahab - Abu Galumhttps://blog.kaiyikang.com/posts/2025/dahab_5abu-galum/https://blog.kaiyikang.com/posts/2025/dahab_5abu-galum/Tue, 15 Apr 2025 00:00:00 GMT<p>Dahab 的北边是蓝洞,再往北就是 Abu Galum。抵达那里有几种方式:可以直接从 Dahab 坐船,也可以先到蓝洞,再换乘船只或步行前往。</p> <p>天气炎热干燥,能选择步行的,多少都带些信仰。我们比较懒,选择了前者,直接乘船过去。</p> <p>这次的行程由女友的朋友一手策划,我们只管跟着她。听闻那里相当偏僻,物资稀少,我们便提前一晚备好了行李,只带了些生活必需品。第二天一早,我们同乘出租车来到港口,只见岸边已经停了不少船。与其说是港口,不如说是一片小沙滩,坐落在几栋看似废墟的建筑旁。船只不大,七零八落地散在海边,毫无规整可言。远处的大海与初升的太阳,给这片船港蒙上了一层滤镜,让它总算勉强够看。</p> <p><img src="@assets/2025/L1090997.JPG" alt="" /></p> <p>岸边站着不少人,聚成一堆,脚下摊着行李和物资。我偷瞄了一眼,发现除了最基础的水和燃气,竟还有不少新鲜的蔬菜瓜果。见此情景,我心里便大概猜到,目的地该是多么物资匮乏。女友那位极度外向的朋友走上前去,跟他们打起了招呼,顺便也介绍了我们。原来,其中一位埃及朋友是位老板,正在那里经营着旅店生意,其余的人,有他的朋友,也有即将下榻的客人。</p> <p>那位老板不高,身形算不上魁梧,但很结实。他穿着舒服随意,不像老板,倒像是从大街上随便拉来的人。但听人说,他其实是个大房东,在当地有多套房产,生意也相当兴隆。我一边听他们有一搭没一搭地闲聊,一边向旁观望。不少当地人零散地站在沙滩上,大多是些小孩,嘴里说着叽里咕噜的阿拉伯语。他们身穿洗得发白的袍子,让人猜不出他们在这个时间、这个地点做什么。</p> <p>大概等了半个多小时,见他们陆陆续续开始把东西往船上搬,我们知道,这是要出发了。因为没有船坞,船直接搁浅在沙滩上,所以上船就像翻墙:先把东西甩上去,再用双手撑住船沿,借着屁股一顶才能翻上去。身材娇小的,甚至还需要旁人搭把手。</p> <p>船上除了我们这些乘客,还有两个穿白袍的当地人,一位年长,一位是小孩。我心想,大概是年长者负责开船吧,怎料船启动时,掌舵的竟是那个孩子。那孩子看着不过十几岁,换算一下,也就小学到初中的年纪。他从头到尾没怎么说过话,反倒是那位年长的偶尔会出声指点几句。</p> <p>船终于开了。由于前几日附近风浪巨大,此刻余波未平,红海并不平静。这种不平静反映在船上,就是剧烈而毫无规律的颠簸。小船行驶在红海之上,我一度觉得自己像是逍遥的海盗,可一个巨浪拍来,屁股被震得生疼,眼镜和脸上也溅满了咸涩的海水,瞬间将我打回现实。我不是海盗,只是一个在陌生大海上瑟瑟发抖、手无寸铁的普通人。与此类似的颠簸感,我只在游乐园里体验过,但这里可没有安全措施,唯一能保你命的,只有这一艘船,和一个孩子。</p> <p>我们一路向北,途中经过蓝洞,也遇到了返航的船只。每当两船交汇,许多人都会起身,向对面的船做出抬手致意的姿势,颇有几分庄严和神秘感。在茫茫的红海上行驶了大约三四十分钟后,我们终于抵达了 Abu Galum。</p> <p><img src="@assets/2025/L1100045.JPG" alt="" /></p> <p>下了船,脚下是鹅卵石滩,旁边有用心堆叠的石块作为标记,示意着船舶停靠点。这是一片沿海的巨大平原。从 Dahab 往北,几乎都是山海相连,即便有路,也仅容徒步。因此,Abu Galum 作为沿途遇到的第一片平原,显得格外难得。放眼望去,只能见到一个个或大或小的棚屋,那便是这里仅有的,供人栖身的住所。棚屋背靠高山巨石,虽然面朝大海,却没有春暖花开。</p> <p>这片土地充满了原始而巨大的能量,任何小巧精致的意象和幻想在它面前都会被碾压。</p> <p>我们走进一个由巨大棚子搭成的 " 前台 ",一种不可思议的感觉涌入大脑:这样的景象竟然真实存在。就像走进一出魔幻戏剧,但这戏剧竟是现实,连视网膜都对此感到不太适应。棚顶由巨大的草木编织而成,顶梁柱是根粗木,表面已被摩挲得十分光滑。脚下铺满了层层叠叠的地毯,中间特地留出一片土地,长着一株巨大的芦荟。空间里摆放着许多装饰小物,精致、自然,充满了创意,是真正的手工艺品。大厅里有吊床、乐器、书籍和棋盘,却不见任何高科技产品的踪影。</p> <p><img src="@assets/2025/L1100006.JPG" alt="" /></p> <p>前台后面是厨房,里面摆满了瓶瓶罐罐,装着不知名的香料和草药。如果想喝茶,随时可以自己跑到厨房烧水冲泡。这里的大多数物件虽已老旧,却都能照常使用。印象最深的是一个铁质水壶,壶把歪歪扭扭,外壳也已变形,连盖子都盖不严实。但就是这样一个水壶,人们依旧日复一日地用它烧水、喝茶。</p> <p>我们住的棚屋散布在附近,屋顶和墙壁同样由草木编成,仅能遮阳挡风。地上铺着一张床垫,除此之外,再无他物。旁边的厕所不大,洗澡更是奢侈,因为淡水资源极其有限,所有的水都集中在巨大的白色水桶里,由专人定期开车过来更换。地处沙漠,条件如此简陋,完全可以理解;或者说,这本身就是一种难得,毕竟这样的气质并非随处可寻。</p> <p>除了那位万事通老板,还有几个人也给我留下了深刻印象。一位是法国女性,已在此住了一阵子。她身材高挑瘦削,常穿一袭白色长裙。傍晚时分,我曾见她从山上走下,长裙随风摆动,飘飘欲仙。她还会唱歌,嗓音略带沙哑,却别有韵味。另一位是旅店的管理人,他身材更为瘦削,留着大胡子,头上裹着头巾,脚下从不穿鞋。我能感觉到,他与自然走得极近,几乎融为一体。他通晓这片土地的自然知识,会识别草药,也会烹饪。他弹奏乐器,吟唱诗歌,仿佛是自然在人间的代言人。</p> <p><img src="@assets/2025/L1100008.JPG" alt="" /></p> <p>这里几乎没有可以被动消磨时间的娱乐,想找点乐子,必须自己主动创造。你要么展开想象力摆弄石子,要么抓起吉他尝试弹出几个音符,要么就捧起一本满是宗教典故的书籍。不善于打发时光的人,在这里无聊感会被无限放大;而懂得感受与创造的人,则会如鱼得水。</p> <p>闲来无事,我们也会同其他人攀谈。一对来自英国的夫妇,因为对我手中的 Kindle 感到好奇,主动与我们聊了起来。他是做音乐与舞蹈策划的,来此地寻找灵感;他的夫人似乎不太喜欢这里,我们甚至偶尔会听到他们在一旁争吵。还有一对来自乌克兰的母女。傍晚,我与她们,以及几个从开罗来的年轻人,围坐在碎石滩上升起一堆火,将周边捡来的杂草和树枝投入火中,听他们讲述着各自的经历与故事。</p> <p>夜晚的到来充满戏剧性。</p> <p>先是听到有人呼喊,顺着他们手指的方向望向远方山峦,只见一轮巨月正从山后缓缓挪出。那时临近满月,月亮近乎完美的圆形,宽度占满了几个山尖。它起初躲在群山之后,随即以肉眼可见的速度逐渐上升。月光虽不算明亮,但月球表面的环形山却清晰可见。氤氲的黄昏色调萦绕在月亮周围,仿佛一个引力巨大的黑洞,不断吸引着我们的目光,牵引着我们的心神。月亮是主角,天空是背景板,随着主角的移动,天空的颜色也从淡雅的橙红逐渐变为深邃的漆黑。夜晚是白天的反色,而月亮,就像一盏巨大的白炽灯,高悬天际。</p> <p><img src="@assets/2025/L1100079.JPG" alt="" /></p> <p>银色的月光洒向红海,洒向大地与群山。人们被这氛围感染,渐渐沉默下来,不再高声说话。茶余饭后,大家三三两两地或坐或躺。管理人拿出一旁的吉他,开始弹唱。会唱歌的人随之加入,不会唱的便静静地打着节拍。轻柔的海风一阵阵拂过,也像是在为我们伴奏。音乐和欢笑声从棚屋中飘出,融化在皎洁的月光与温柔的黑夜里。</p> <p><img src="@assets/2025/L1100101.JPG" alt="" /></p> <p>深夜转瞬即逝。太阳准备升起,开启下一个循环。</p> <p>Abu Galum 的巨大能量似乎在感染着每一个到来的人。女友感冒的症状在这里反而加重了,而我的情绪则经历了剧烈的翻涌与波动。这感觉不太寻常,我们毕竟仍身处地球,但这地方却像受到了某种奇妙的辐射。只要人一踏入这里,就注定会受到它的影响,实在太神秘了。</p> <p><img src="@assets/2025/L1100149.JPG" alt="" /></p> 一个针对优化架构的简单例子https://blog.kaiyikang.com/posts/2025/%E4%B8%80%E4%B8%AA%E4%BC%98%E5%8C%96%E6%9E%B6%E6%9E%84%E7%9A%84%E7%AE%80%E5%8D%95%E4%BE%8B%E5%AD%90/https://blog.kaiyikang.com/posts/2025/%E4%B8%80%E4%B8%AA%E4%BC%98%E5%8C%96%E6%9E%B6%E6%9E%84%E7%9A%84%E7%AE%80%E5%8D%95%E4%BE%8B%E5%AD%90/Thu, 06 Nov 2025 00:00:00 GMT<h2>当前状况</h2> <p>有一个 <code>StatusService</code>,包含了名为的 <code>save()</code> 的方法,可以在 <code>Repo</code> 中存储新的 <code>StatusModel</code>。</p> <p>有两个模块会用到 <code>save()</code>,一个是 <code>StatusService</code>,另外一个是 <code>ResetStatus</code>。前者通过 <code>StatusResource</code> 接受请求,后者则通过 <code>ResetStatusEventListener</code>。</p> <p><code>ResetStatus</code> 会直接调用 <code>StatusService</code> 中的 <code>save()</code> 从而控制 status 数据的存储。StatusService,这个命名看上去像是包揽了或代理了所有和 Status 有关的一切事务,查询,存储,删除等等,但却为之后的测试埋下了隐患。</p> <p>当前逻辑的关系图:</p> <p><img src="@assets/2025/a-simple-example-for-optimising-architecture-an-1.jpg" alt="" /></p> <p>以下是具体的代码片段:</p> <pre><code>// StatusService.java public void save(final StatusModel statusModel) { final EntityID id = statusModel.id(); // Business validation logic guardEntityExists(id); final List&lt;ExternalInfo&gt; externalInfos = lookupExternalInfo(id, getContext()); guardInfoIsValid(statusModel, externalInfos); guardSomeBusinessRule(statusModel, externalInfos); // Data mapping logic final StatusModel mappedStatusModel = mapDataToMatchExternalInfo(statusModel, externalInfos); // Persistence and side-effects final boolean stateHasChanged = statusRepository.save(mappedStatusModel); if (stateHasChanged) { log.debug("Invalidating cache for ID={}", id); cacheService.invalidateCacheForIds(List.of(id.toString())); } } </code></pre> <p>有两个模块使用它,第一个模块是:</p> <pre><code>// StatusResource.java @PUT @Path("entities/{id}/status") public Response updateStatus( @PathParam("id") final EntityID id, @Valid final StatusUpdateRequest request) { statusService.save(mapToDomainModel(id, request.details())); return Response.ok().build(); } </code></pre> <p>第二个模块是:</p> <pre><code>// ResetStatus.java public void reset(@NonNull final EntityID id) { log.info("Invoking factory reset of status for ID={}", id); final StatusModel currentStatus = statusRepository.findById(id); final StatusModel resetStatus = currentStatus.withStatusReset(); statusService.save(resetStatus); } // ResetStatusEventListener.java public void handleEvent(final SystemResetEvent event) { resetStatus.reset(event.getId()); } </code></pre> <h2>该结构引发的问题</h2> <p>在做测试的时候,有个调用 reset 的 ITCase 返回了错误,显示在 inventory 中无法找到 Vin。经过排查,我发现由于 <code>StatusService.save()</code> 中 <code>guardEntityExists()</code> 会检查 Vin,而这个测试没有注册 Vin,从而产生了错误。根据 <code>StatusService</code> 原本的设计,它只是为 <code>StatusResource</code> 提供了服务。换句话说,<code>StatusService.save()</code> 的实现并不适合 Reset。</p> <p>从这个错误中,我们就找到两个清晰的 Use Case,一个是为了处理 Inbound 为 Resource 的请求,另外一个是为了处理 Inbound 为 Listener 的 Reset 请求。两个的共同之处是,它们都会使用到 <code>Repo</code> 的 <code>save()</code> 方法,而区别在于,Reset 不需要 <code>guardEntityExists()</code>。</p> <h2>思考</h2> <p>在经典的设计模式 Hexagonal Architecture 中,包含了 Inbound, Outbound, Use Case, Domain 等等模块。我们应该如何将现有的 java 类正确的归类,它们之间如何正确的交互,是非常重要的问题。</p> <p>首先可以明确的归类是:</p> <ul> <li>Inbound - <code>ResetStatusEventListener</code></li> <li>Inbound - <code>StatusResource</code></li> <li>Outbound - <code>StatusRepository</code></li> </ul> <p>剩下的就是确认 <code>ResetStatus</code> 和 <code>StatusService</code>。</p> <p>根据名字 <code>ResetStatus</code> 中的 Rest 强烈的表明它是一个 Use Case,然后观察 <code>StatusService</code> 中的方法,发现它其实包含了非常多的逻辑,不仅有代表 <code>StatusRepository</code> 的「贫血方法」,例如:</p> <pre><code>// StatusService public StatusModel findById(final EntityID id) { return statusRepository.findById(id); } </code></pre> <p>同时还加入了属于 Status 的 Domain 方法,比如 <code>lookupExternalInfo()</code>,或之前提到过的 <code>guardEntityExists()</code>。现在为了简化,我们先不去考虑这些看似 Domain 的方法,只将它看做是另外一个 Use Case。我们现在拥有了两个:</p> <ul> <li>Use Case - <code>StatusService</code></li> <li>Use Case - <code>ResetStatus</code></li> </ul> <p>Use Case 之间是本应该隔离的,但当前的结构并没有如此实现,所以看上去有些不舒服。经过这样的调整,结构就变得清晰许多。</p> <h2>重新梳理结构</h2> <p>新的结构中,<code>ResetStatus</code> 应该直接和 Outbound 连接,即直接将重置后的 status 保存进数据库,而不是再经过 <code>StatusService</code>。</p> <p>除此需要修改的部分是 <code>StatusService</code> 中的 <code>save()</code> 方法的名字。原因是这个方法并不纯粹,除了存储数据之外,还做了 guard,mapping,以及 invalidate 等额外的事务。很显然,它并没有只做「保存」这一件事,所以我更倾向于将它重新命名为 <code>update()</code>,或者其他类似的词,去表明它在编织一段流程,即彻底更新 status。</p> <p>现在,我们的新结构是这个样子:</p> <p><img src="@assets/2025/a-simple-example-for-optimising-architecture-an-2.jpg" alt="" /></p> <p>虽然整体结构清晰了,但 <code>StatusService</code> 的命名仍让人困惑。我查询了一些关于 Use Case 的定义和规范,结果是:一个 Use Case 类最好只有一个公共的方法。</p> <p>原因如下:</p> <ul> <li><strong>Single Responsibility Principle - SRP</strong>: 一个类应该只有一个改变的理由。如果一个类只代表一个 Use Case,那么只有这个特定流程需要改变时,我们才需要改变它。 <ul> <li>对应的,如果一个类包含多个 Use Case,任何流程修改都会影响到其他的 Use Case,增加了错误的风险。</li> </ul> </li> <li><strong>Clear Business Boundaries</strong>: 如果打开一个包,可以看到一系列清晰的 UseCase 列,就像系统功能的菜单,任何人都会立刻明白这个应用或服务提供了哪些业务。</li> <li><strong>Minimised Dependencies</strong>: 不同 Use Case 的依赖是不同的。如果将它们放入同一个类中,有些依赖可能完全没有必要,但仍需要被引入。这会导致依赖混乱和不必要的耦合。</li> <li><strong>Clear Transactional &amp; Security Boundaries</strong>: Spring 框架的@Transactional 通常都是应用在公共方法上的。因此一个类只有一个公共方法时,事务和安全的范围明确:要么整个 Use Case 成功,要么全部失败,从而避免了一个类中多个方法之间管理复杂状态的麻烦。</li> </ul> <p>根据该说明,<code>StatusService</code> 应该改名成 <code>UpdateStatusUseCase</code>,public 方法可以改成 <code>apply()</code> 或 <code>execute()</code>。</p> <p>更名后的结构是:</p> <p><img src="@assets/2025/a-simple-example-for-optimising-architecture-an-3.jpg" alt="" /></p> <p>之前我们都在讨论 UseCase,最后可以来说说 Domain。Domain 应该包含了真正的业务逻辑,而不是作为 Use Case 引入外部服务,编织并操作流程。在这个例子里,<code>UpdateStatusUseCase</code> (即原来的 <code>StatusService</code>) 的 <code>guardEntityExists()</code>,<code>lookupExternalInfo()</code>,或 <code>StatusModel</code> 其实属于 Domain 这个范畴。Use Case 需要 Domain 中的业务逻辑作为支持,比如 <code>ResetStatus</code> 需要知道 <code>StatusModel</code> 才可以正确的重置 Status。</p> <p>经过最后的整理,理想中的结构是:</p> <p><img src="@assets/2025/a-simple-example-for-optimising-architecture-an-4.jpg" alt="" /></p> <h2>后续的想法</h2> <p>我有两点发现。</p> <p>第一点是,当大家去讨论 Architecture 主题的时候,永远会花许多时间,它仿佛就像是吞噬时间的黑洞。另外一点是,讨论 Architecture 有点像讨论政治或生活哲学,不论经验多少,似乎所有人都可以来发表一点评论和意见。有丰富经验的人所提出的观点自然会比没什么经验的人(比如我)更加深刻且重要,但它很容易被淹没在众多观点的海洋中。</p> <p>这是个高熵的主题。</p> <p>当然,不论 Architecture 怎么变化,最终都是要解决实际问题的。在这个例子中,问题就是如何让相关的 ITCase 测试通过。通过这个例子,我们重新回到已经出现问题的代码中,通过反思并重构它,最终解决问题。</p> <p>如果让我从头开始,去构筑以充满清晰结构为目标的方案,我会感觉到无所适从,也找不到标准说,什么是所谓的更好。</p> <p>在当下这一时刻,我们把一个模块 A 放在位置 1 和位置 2,其实没有任何区别,也不会引起任何问题,更多的仅仅是品味和感受的问题。我很难将它们称为「真正的问题」,而真正的问题往往是当系统开始投入使用后才会出现的,因此在这一阶段也容易出现过度工程化。</p> <p>架构设计真正产生魅力的瞬间,或有经验的设计者的前瞻性,往往是通过时间才能检验出来的。</p> <p>总而言之,通过这个简单的例子,我得以了解不同设计模块的大体定义,以及模块之间的关系和边界的设计是如何切实的影响到小系统的运作的。</p> 三十https://blog.kaiyikang.com/posts/2025/%E4%B8%89%E5%8D%81/https://blog.kaiyikang.com/posts/2025/%E4%B8%89%E5%8D%81/Fri, 28 Feb 2025 00:00:00 GMT<p>我已经确认多次,时刻的感受几乎都是平的,只有将时间线拉的巨长时,差距对比才会变得显著。我也丝毫不怀疑,当我快要老到将死时,感受也不会发生太大变化,为所作出的行为改变而喝彩,脑中依旧残留些想做却做不了的想象。</p> <p>你可以感叹往昔,但也似乎持续不久,通常是碰触了那些变化差异巨大的人或事,才会有些感慨,而这劲儿一过,又继续该吃吃该喝喝,过那些日子。它也就是个瞬时的冲击,却伤不到巨大名为感受的海绵的丝毫。</p> <h2>亲密关系</h2> <p>这其中充满着好滋味,我能从和爱人的相处中察觉到。她那种近乎天才般的灵动怪感,让我欲罢不能。我想在同她经历并体验更多的事情,那弥足珍贵的记忆和触感,成为我回忆的一部分,更成为我的一部分,也是我喜欢的那部分。</p> <h2>到处都是智能</h2> <p>因为其它技术进展缓慢,所以近乎所有人都挤到了同一个主题上来,道路变得无比拥挤。即便每天都会刷新的新闻,看多了也会感到麻木和倦怠,仿佛在没有什么新鲜事了。</p> <p>兴许人们的信仰消耗殆尽,于是尝试自己动手造「智能」,然后让它尝试去解答信仰的问题。这会是可能的么?我觉得问题在于,它是人们知识和表达均匀后的产物,先有人类的知识创意及表达,然后再出现所谓的智能。你问它问题,你感觉它很非凡,这简直是太对不过的事情,因为它就是如此这样理解并设计的。如果它出了偏差,叙述出了些人类不认可乃至不理解的表达,反倒值得惊奇。</p> <p>我稍微了解 AI 背后的机理,所以每次看它或使用的时候,无不是带着使用工具的视角。我在想,如果有一群人去故意全然隐匿其背后的算法逻辑,只暴露出一个接受输入和返回输出的 API,那剩下的那群人就真的可能会被蒙蔽,从而觉得,哇,背后蹲着个如同神一般的存在。实际上,它就是个工具,和我们手边的电脑或锤子没什么本质区别,即便能创造奇迹,那也得看是谁在使用它。</p> <p>绝不低估人类的力量。</p> <p>还有一点我感到忌惮的是,它是圆逻辑方面的天才,可以说无比「正确」的废话,同时,它无法告诉你它所不知道的事情。在我们使用它的时候,会感觉舒服,但无法在现实中验证它所说的反馈。代码或数学题目可以被验证,但其他的内容,像是算命之类的,就很难被印证。</p> <p>像它这样表达的氛围,像极了工作中上下级。我们是上级领导,虽然不懂项目,但却觉得下级的报告非常有道理,从而升起一种以未知为基础的信任感。它圆滑得很,不停使我们感到满意。</p> <p>结合上面所说,模型是由人类训练出来的,带着很具人类味道的互动方式,因此大概率也映射着人类在潜意识中的互动方式,圆滑,权力关系。当然,我是不愿意这样相信的。</p> <h2>做事</h2> <p>我感觉「事业」这个词有些油腻,总会让我想到那些在酒桌上指点江山的人,同时在无形中带来一种束缚,仿佛这辈子有了事业就有了一切。</p> <p>我会希望有自己能够做的事,不是围绕着公司雇主或产业展开,而是围绕着自身的生活或相似的人。这是其中的一个方向,另外的一个方向则和性格有分不开的关系。有些人善于观察外界,总结规律以后,再展开行动。而有些人善于观察自己,总结自己的特征与喜好,并通过行动放大它,使它能够波及到同自身相似的人。我倾向后者,因为它会让我更安心,更有把握去做事。</p> <p>德国对开展副业不太友好。如果有想法,还需要通告当前雇主,这样势必会给当前主业带来负面的影响,因此不得不全职开展副业,这就和「副」一词背道而驰。</p> <p>我喜欢程序开发,不停的去思考解决方案,激活思维的火花让我感觉到了活力。它包含工程实践的同时,却仍能够涵盖美学和哲学,增加了无穷的探索深度。另外,工作内容依赖于二进制的世界,与现实的世界解耦,这样我的肉身与心灵变得自由灵活。</p> <p>My current challenge is communication and mindset. As German and English are not my mother tongues, the expression of the way of thinking is somethings blocked and there is little transparency between the inner and the outer world. This frustrates me. Anyway, I have to pay my time and energy for the flexibility, but overall it is worth it.</p> <h2>生活</h2> <p>过量的信息以及极具成瘾机制的算法使我疲倦,相比以前,是愈来愈来严重了。有时我会感觉过去的时光也不错,回看过去的十年,上一个整数的时候,我第一次步入德国的土地,学习然后漫游。(竟然已经快十年了,都没有怎么意识到,时光飞逝呀)</p> <p>反思并写作真的很好,将信息保存了下来,留给了十年之后的我。读上去,思维还是类似的思维,但浓度不再那么高了,大概都均匀给了生活。小学的时候,博客空间还非常的盛行,曾有一位熟络的网友感叹说我想的太多,太远,没有什么太大必要。我觉得他说对了一半,必要性要有,但太多太远的确没有必要。我的经验似乎被倒置过来了,一般人是先经验,然后在接受生活的乏味后,迷茫再反思。我迷茫不多,但反思不少,本应最早的经验反倒放在了现在,怪哉。</p> <p>漂浮在行为决策背后的幽灵正在变淡,打开电脑工作,拽取一个接一个 ticket,参加一场又一场的会议,期待每一个周末的懒觉,难得的随机事件是晴朗的天空和暖洋洋的阳光。此时我正身处一个 loop 中,用自我催眠的方式来描述就是来休养生息,等待厚积薄发,换种方式描述就是,丝毫不带有个人风格的迈入平庸且乏味的生活。</p> <p>决策消耗了脑子的精力,于是再没有多去想为什么,怎么了之类的问题,即使想了又能怎样呢,又能得到怎么样的结果呢?但我既然曾拥有过,那么我就不想丢掉,只不过我现在更加贪心,不仅要想,更要去行动。赞美伟大的行动家。</p> <p>就看看我能走到什么地方。</p> <h2>死亡</h2> <p>生命一端连接着死亡,所有人落在这道弧光的某个位置上,从左向右不间断的移动,时间是动力源。思索太多的死亡,会侵占生命的时间,得不偿失,我们应该将活着的时间和精力都留给生命,死亡是将死的时候才应该考虑的事情。</p> <p>年龄数字的翻新会提醒我它的存在,而它越是这样跳脱出戏,就越应当罔顾且淡然,不然我活了些什么?我到底是否在成长变大?竟然让它侵占我的生命,这是荒谬且不能接受的。</p> <p>心无旁骛,专心利用好活着的时光。</p> 入籍考试刷题工具https://blog.kaiyikang.com/posts/2025/%E5%85%A5%E7%B1%8D%E8%80%83%E8%AF%95%E5%88%B7%E9%A2%98%E5%B7%A5%E5%85%B7/https://blog.kaiyikang.com/posts/2025/%E5%85%A5%E7%B1%8D%E8%80%83%E8%AF%95%E5%88%B7%E9%A2%98%E5%B7%A5%E5%85%B7/Tue, 02 Dec 2025 00:00:00 GMT<p><a href="https://einbuergerungstestfragen.com/zh/">工具链接</a></p> <p>这个工具源自一场等待。</p> <p>记得早在 2025 年初的时候,我就向官方网站提交了永久居留的申请。准备过程非常繁琐,包含许多材料,还需要公司以及房东的签名。我本以为整个流程几个月就可以结束,但结果拖了半年多才得到反馈。</p> <p>那是一条由系统生成的邮件,排版丑陋且充满错误。其中仅有一条是签证官的回复,看上去还算是有点儿 「人类的味道」。上面写着,让我在两周之内提交入籍考试通过的证明,否则就要强制撤回申请。我对这个本该期待已久的回复感到诧异,因为官方网站上赫然写着:为了证明融入德国的程度,要么提供德国大学毕业证明,<strong>或</strong>是提供入籍考试证明。我盯着那个「或」字看了半天,确定自己没有看错。后来我拿着法律条文和官网信息去找他们申诉,但返回的结果依旧是 " 要考试证明,要考试证明 "。感觉像是在和上了发条的机器对话,甚至还不如 GPT 的回复。</p> <p>后来想着辩解太多也是白费力气,就别计较了,考了算了。网上再一查消息,最近的考试也要在三个月之后,大概是因为人数太多,爆满了。报名的方式也非常复古,是那种线下报名的方式。我能理解线下考试是为了确认身份、确保没有作弊,但都 2025 年了,居然报名也要线下实地进行,这着实让我百思不得其解。</p> <p>大概在考前一个月,我开始慢慢收集和考试有关的消息,比如规则、题目类型之类。简单在 Google 输入关键词,就能找到一大堆题目网站,有官网的,也有民间的,还有收集好题目做成 PDF 的。</p> <p>我浏览了一下这些排名靠前的网站,发现它们都不太符合我的需求。</p> <p>首先按常理来说,入籍考试面对的是非德国人,竟然没有任何一个网站提供多语言支持。我的母语是中文,虽然也学过德语,即便让我用德语刷也没问题,但有时也想看看翻译,以确保我的理解没错。但我没有找到有任何网站支持这一点。</p> <p>其次是刷题的方式,大概是受到了官方网站的启发,几乎所有网站都是 「一个页面一道题目」,选择后跳出答案,点击按钮才进入下一题。我无法看到刚才答题的结果,也没有一个全局的概览。如果想针对性地看看错误的题目,就需要一个个往回点击,十分不方便。</p> <p>最后是样式。在网页技术和 AI 技术日新月异的今天,竟然大部分网页的样式都非常简陋且繁杂。各种奇怪的样式、字体,还有不明所以的图片到处乱飞。我就是想要刷题,给我题目、答案还有是否正确就好,何必搞得如此花里胡哨?我不明白。</p> <p>经过简单的调研和思考,想到技术实现并不复杂,我当即决定要自己做个刷题工具。媒介就是网站,足够便宜,也要简单干净,满足 「哪里都可以刷题」 的需求就好。</p> <hr /> <p>整体思路是这样的:首先设计最基本的数据结构,结构上天然就支持多种语言;其次填充数据并加入多语言;再来才是制作网站,加入样式和核心逻辑功能;最后是部署。虽然描述是有顺序的,但做的时候都是并行穿插的,比如「网站部署」和「数据收集」就是同时完成的。我很享受网站被实现的那一刻。</p> <p>最初,我用的是 Google 的 AI Studio,可以免费使用 Gemini 2.5 Pro。这个模型对长上下文非常友好。一般的数据处理任务,我会把想要的输入和输出格式交给它,然后让它给我返回 Python 或 Bash 脚本。在每次提示中,我都会要求它尽量输出简单且容易理解的脚本,确保我能快速浏览和理解代码,这样对检查 Bug 更友好,我也觉得更有控制力。</p> <p>原始数据的语言是德语,格式是 JSON。多语言部分,我使用了额外的脚本,配合 OpenRouter 的 Qwen/Qwen3-Max 模型,价格便宜并且对多语言支持很不错。</p> <p>域名还有部署,都使用的是 Cloudflare。域名价格不算很高,可以一键配置部署好的平台。平台是它的 Pages 服务。每当代码写好,Pipeline 自动触发,生成静态网站并自动部署,同时它还额外提供分支预览功能,对开发非常友好。</p> <p>顺带一提,我的博客用的也是类似的技术。</p> <p>完成了基础的平台搭建和数据填充,终于可以正式开始设计网站了。网站的 Framework 使用的是 AstroJS,既有 HTML 和 JS 朴素的优点,又支持模块化并兼容其他现代化框架。我感觉它是一个非常有东方味道的框架,兼容并蓄。</p> <p>做网页开发的时候,我秉持的一个信念是:代码和文件必须保持精简,一个文件中可以完成的,绝不放在第二个文件中。太多的模块和文件会让应用变得复杂,也让我理解起来变得困难。另外,如果我想增加新功能或修复 Bug,我可以直接将包含全部上下文的代码发送到 Gemini 中。模型能快速定位,迅速解决问题。</p> <p>十几轮对话之后,工具就已经初具规模。它包含了最基础的样式,能显示所有的题目,能点击并且提示对错。在此之后的几天,我陆续加入了语言翻译、重置答案、预览结果等等功能。我对此非常满意,即使在地铁站或公交车上,也可以打开浏览器快速地做题。</p> <p>随着自己的使用,我体验出了一些使用不便的地方,比方说夜间模式,还有收藏模式。后者像是标记,哪道题目出错了,我可以简单标记上,即使答案重置了,我也可以跳回去反复去做,从而加深记忆。</p> <p>后来在女友的询问下,我出于好奇心购买了 Claude Pro 的一个月会员,大概 20 欧元左右。我想体验一下网传 Vibe Coding 是否真的足够惊艳。使用过之后,我想说是的,它的确足够惊艳,能凭借着我的几句话调用工具、修改代码,从而解决问题。刚买会员的那几天,我用得很上瘾,恨不得一下班就打开它修改网页。</p> <p>不过 「爽」 也是有代价的。当我数次向它请求重构代码之后,我发现逐步失去了对代码细节的掌控力。之前的代码在我印象里是一行行的,精确耦合在一起的,但现在它变成了一团雾。我需要钻进雾里,才能勉强看清代码的位置以及它是如何起作用的。虽然它会很自信地告诉我,代码现在的状态是最简洁、最干净的,但我仍能够时不时地浏览到一些很啰嗦的逻辑。直接 A 就能做的,非要从 A 到 B 再到 C,最后通过 C 来解决。</p> <p>也许是我自己的表达和思路不清晰,它工作有时候会变得非常随意。举例来说,我习惯所有的样式都用 TailwindCSS 来处理,而不是写额外的自定义 CSS。我觉得不论是代码、项目还是团队,都会有一种默契和习惯,读懂它们传递出来的空气是很重要的。这种像是物理惯性的东西节约了很多沟通成本。不过模型不会理解这个,它是以 Prompt 和结果为导向的。它没有办法读出当前情况下的「惯性」,但是人类会。</p> <p>意图这个东西,很多时候连我自己都意识不到。我会觉得这也许是个机会,通过模型的犯错,让我来纠正它,通过这个反馈来逐渐清晰我自己的意图,进而通过自然语言表达出来。但这个来回讨论过程的重点在于学习,而不是通过工程完成一件非常牢固的工具和产品。我能理解为什么不太懂代码的人会对它狂热,因为它有一种魔法感;也能理解为什么懂代码的人有时不太那么喜欢,因为它会脱离你的控制,欺骗你完成了事情,但实际上并没有遵循作者真正的意图,毕竟你能看懂它有时候写的代码真的不咋地。</p> <p>在这个小项目里,我力争在加入新功能和学习代码之间(或说对代码的掌控力之间)寻找到一种平衡。我对自己的评价是尚可,虽然很多逻辑代码我写不出来,但起码知道它大概是如何运作的,识别出小 Bug 的位置是足够了。</p> <hr /> <p>做完了网站工具,我也参加完了考试,也有时间对这个工具做进一步的优化和推广。为此还特地邀请了女友作为微型项目的 Product Manager。她尝试了一下工具,并也请朋友试用了一下,得到的反馈是页面排版不好,颜色指示也不清晰。特别要命的是,网站没有记忆功能,答过的题目刷新网页就消失了。</p> <p>为了实现更好的团队合作,我们使用 Trello 作为 Kanban Board。但因为还在摸索过程中,创建 Ticket 的规模和边界定义得都不是非常清晰,所以实践起来有一搭没一搭的。有时候遇到太小的 Bug,我会跳过或是合并多个后统一处理,跳过了 Ticket 的流程。</p> <p>除了改善工具使用体验以外,最重要的莫过于推广。我知道推广很重要,但是因为性格特征,尤其作为一个习惯写程序的,在互联网上大摇大摆地展示自己做的东西怎样怎样,实在感觉有些害臊,因此从没有认真地去做过推广。为了转换心态,我把这个行为定义换了个词,不叫「做广告」,而叫「帮助别人」。这让我的感觉好了很多。</p> <p>我的意图就是要帮助别人,帮助和我有着类似处境的人。有了这个信念之后,后续的事情似乎就自然了不少。我写了一段小文案,做了一些截图,放在一只手拿着 iPhone 的照片模版里,看起来很粗糙,但还是像那么点儿回事似的。</p> <p>推广的结果稍微显现,但仍欠火候。我们还得多了解一下搜索 SEO 的优化和推荐机制才行。</p> <hr /> <p>现在这个工具运行稳定,逻辑正常,虽然会有一些弹出的广告,但绝不影响使用。除了使用工具保证题目是最新以外,我们还计划加入更多额外的有用的信息,也同样支持多语言。</p> <p>当然,即使做了考试,对于永久居留的身份来说,我还需要再次提交,再次付费,再次等待,预计一切要拖到第二年才能尘埃落定。</p> 分离业务和控制逻辑https://blog.kaiyikang.com/posts/2025/%E5%88%86%E7%A6%BB%E4%B8%9A%E5%8A%A1%E5%92%8C%E6%8E%A7%E5%88%B6%E9%80%BB%E8%BE%91/https://blog.kaiyikang.com/posts/2025/%E5%88%86%E7%A6%BB%E4%B8%9A%E5%8A%A1%E5%92%8C%E6%8E%A7%E5%88%B6%E9%80%BB%E8%BE%91/Thu, 15 May 2025 00:00:00 GMT<p>核心逻辑是,将程序中业务,领域知识的部分(domain or business logic)与负责流程控制、协调和决策的部分(control logic)分开,提高代码的可维护性,可测试性和复用性。</p> <p>在 DDD 中,业务逻辑对应着领域逻辑,控制逻辑对应着「应用层」或「服务层」。</p> <p><strong>Business Logic</strong></p> <ul> <li>内容:业务规则、验证数据、计算公式</li> <li>职责:确保系统行为符合业务需求和规范</li> <li>示例: <ul> <li>计算订单的总价</li> <li>判断用户的权限</li> <li>业务状态的转换</li> </ul> </li> </ul> <p><strong>Control Logic</strong></p> <ul> <li>内容:流程控制、调用顺序、条件判断</li> <li>职责:组织和管理业务逻辑的执行,保证系统流程正确</li> <li>示例: <ul> <li>处理多步骤业务流程的状态切换</li> <li>事务开始和提交控制</li> </ul> </li> </ul> <h2>示例</h2> <h3>用 Python</h3> <h4>普通写法</h4> <pre><code>class UserService: def __init__(self): self.users = [] def register_user(self, username, password): # 1. validate username if username in self.users: return {"success": False, "message": "Username already exists"} # 2. validate password length if len(password) &lt; 6: return "Password too short" # 3. store user self.users.append(username) return {"success": True, "message": "User registered successfully"} </code></pre> <h4>分离后写法</h4> <pre><code>class UserRepository: def __init__(self): self.users = {} def exists(self, username): return username in self.users def save(self, user): self.users[user["username"]] = user # Domain Logic class UserDomainService: def __init__(self, user_repository): self.user_repository = user_repository def is_username_available(self, username): # Check if the username is already taken return self.user_repository.exists(username) def validate_password(self, password): # Validate the password according to your criteria return len(password) &gt;= 6 def create_user(self, username, password): user = {"username": username, "password": password} self.user_repository.save(user) return user # Application Logic class UserApplicationService: def __init__(self, user_domain_service): self.user_domain_service = user_domain_service def register_user(self, username, password): # 1. Check if the username is available if self.user_domain_service.is_username_available(username): return {"success": False, "message": "Username already taken."} # 2. Validate the password if not self.user_domain_service.validate_password(password): return {"success": False, "message": "Password does not meet criteria."} # 3. Create the user user = self.user_domain_service.create_user(username, password) return {"success": True, "user": user} repo = UserRepository() domain_service = UserDomainService(repo) app_service = UserApplicationService(domain_service) print(app_service.register_user("john_doe", "password123")) # Should succeed print( app_service.register_user("john_doe", "pass") ) # Should fail due to password criteria print( app_service.register_user("john_doe", "newpassword") ) # Should fail due to username already taken </code></pre> <ul> <li><strong>UserDomainService</strong> 只关注业务规则(用户名是否存在、密码校验、创建用户)。</li> <li><strong>UserApplicationService</strong> 负责流程控制(调用顺序、返回结果)。</li> </ul> <h3>用 Java</h3> <h4>普通写法</h4> <pre><code>import java.util.HashSet; import java.util.Set; public class UserService { private Set&lt;String&gt; users = new HashSet&lt;&gt;(); public Result register(String username, String password) { // 1. Check if the username is available if (users.contains(username)) { return new Result(false, "Username is already in use"); } // 2. Validate the password if (password.length() &lt; 6) { return new Result(false, "Password must be at least 6 characters"); } // 3. Create the user users.add(username); return new Result(true, "success"); } public static class Result { private boolean success; private String message; public Result(boolean success, String message) { this.success = success; this.message = message; } public boolean isSuccess() {return success;} public String getMessage() {return message;} } } </code></pre> <h4>分离后写法</h4> <pre><code>import java.util.HashMap; import java.util.Map; // Domain Logic class UserDomainService { private UserRepository userRepository; public UserDomainService(UserRepository userRepository) { this.userRepository = userRepository; } public boolean isUsernameTaken(String username) { return userRepository.exists(username); } public boolean validatePassword(String password){ return password != null &amp;&amp; password.length() &gt;= 6; } public void createUser(String username, String password) { User user = new User(username, password); userRepository.save(user); } } // Persistence Layer class UserRepository{ private Map&lt;String, User&gt; users = new HashMap&lt;&gt;(); public boolean exists(String username){ return users.containsKey(username); } public void save(User user){ users.put(user.getUsername(), user); } } // User Entity class User { private String username; private String password; public User(String username, String password) { this.username = username; this.password = password; } public String getUsername() { return username; } } class Result { private boolean success; private String message; public Result(boolean success, String message) { this.success = success; this.message = message; } public boolean isSuccess() {return success;} public String getMessage() {return message;} } // Application Logic class UserApplicationService { private UserDomainService userDomainService; public UserApplicationService(UserDomainService userDomainService) { this.userDomainService = userDomainService; } public Result register(String username, String password) { if(userDomainService.isUsernameTaken(username)) return new Result(false, "Username taken"); if(!userDomainService.validatePassword(password)) return new Result(false, "Invalid password"); userDomainService.createUser(username, password); return new Result(true, "success"); } } public class scratch_46 { public static void main(String[] args) { UserRepository userRepository = new UserRepository(); UserDomainService userDomainService = new UserDomainService(userRepository); UserApplicationService userApplicationService = new UserApplicationService(userDomainService); Result r1 = userApplicationService.register("alice", "12345"); System.out.println(r1.getMessage()); Result r2 = userApplicationService.register("alice", "123456"); System.out.println(r2.getMessage()); Result r3 = userApplicationService.register("alice", "1234567"); System.out.println(r3.getMessage()); } } </code></pre> <ul> <li><strong>UserDomainService</strong> 负责业务规则(用户名是否存在、密码校验、创建用户)。</li> <li><strong>UserApplicationService</strong> 负责控制流程(调用顺序、返回结果)。</li> <li><strong>UserRepository</strong> 模拟数据存储。</li> <li><strong>User</strong> 领域实体。</li> </ul> <h2>提示与建议</h2> <ol> <li>从简单方法开始构思</li> <li>简单方法转换到逻辑控制层</li> <li>分离出业务逻辑</li> </ol> 关于 Andrej 和 Dwarkesh 对谈的记录https://blog.kaiyikang.com/posts/2025/%E5%85%B3%E4%BA%8E-andrej-%E5%92%8C-dwarkesh-%E5%AF%B9%E8%B0%88%E7%9A%84%E7%AC%94%E8%AE%B0/https://blog.kaiyikang.com/posts/2025/%E5%85%B3%E4%BA%8E-andrej-%E5%92%8C-dwarkesh-%E5%AF%B9%E8%B0%88%E7%9A%84%E7%AC%94%E8%AE%B0/Thu, 23 Oct 2025 00:00:00 GMT<h2>寻找一个合适的应用场景</h2> <p>在对谈中,他们聊到了关于模型应用的问题。他觉得呼叫中心是个例子,在未来,呼叫中心的人工系统是极有可能被模型替代的,原因是在于它包含「固定的流程」和「相似的任务」,特定的能够在系统中被正确的处理并完成。除此之外,他抽象出了一些特点,具有这些特点的任务是适合用大模型来替代的。以下是我记录下的要点:</p> <ul> <li>排除外部的因素:没有外部的因素的干扰,这样就减少模型犯错的可能,像是稳定的工厂流水线需要特定的厂房,模型系统也是一样。</li> <li>足够封闭:类似上面的一点,也是尽可能减少系统外部的干扰。</li> <li>数字化:这也很好理解,因为大模型是基于 token,我们不能说用大模型去烘烤面包。</li> <li>80% AI / 20 % 人工:这点比较反常。我们是希望模型能够百分百的取代人类,完成彻底的自动化,不过从真正的工程实践角度看,几乎所有的系统都没有办法避免 20% 的人类部分。我们可以减少它们,但绝不能消除它们。对于一些黑天鹅事件或是非常诡异的全新状况,只有人类才能妥善的搞定。</li> </ul> <p>顺着他的总结,我其实一直也在想,什么样的用例适合来使用大模型来替代,换句话说就是寻找需求。我能想到的一个可能的,并且在实际工作中遇到的是测试结果总结:每天的例会中,都需要有人查看所有环境中的所有测试结果,并且与历史的结果比对,将新出现的或者比较奇怪的测试结果分享出来,最后询问其他人的意见和想法。</p> <p>这个例子中,它首先满足了数字化和足够封闭的特征,至于排除外部因素的干扰则并没有完成实现,因为服务系统的上下游关系和配置非常复杂,依赖关系也并不简单,但受到外部的干扰是属于小概率事件,在这里可以暂时性的忽略。最后的 8/2 关系也算符合,虽然现在都是有我们来完成,但像测试结果的历史比对,发现新冒出来的错误测试,以及测试的报告汇总还有初步的成因分析,大模型都可以完成。至于那些非常异常的结果,直接可以人工干涉进行分析。</p> <p>如果要是真的能实现了,又能省下一位程序员每天半小时到一小时左右的时间了。</p> <h2>我需要知道自己在干什么</h2> <p>Andrej 使用模型非常有主见。他知道什么是好的代码,也知道自己要的结果是什么。模型所做的,就是把他脑子里具体的东西给实现出来。如果恰巧模型实现不了,他也足够有能力自己来实现。</p> <p>有一种方法可以度量我们使用模型的方式:一端是完全不用,认为 AI 完全是邪恶的产物,或完全遵守 old school 的作风。另一种是完全使用,即便我一点代码都不懂,但是我所有功能全让他实现。</p> <p>所有人都在用同一套模型并且遵循同一种度量方式,所以使用模型的方式很大程度上映射这个人的经验和性格。AI 不会因为输入错误的信息就会产生正确的结果。</p> <p>因此,正如 Andrej 所说,知道自己要做什么,自己有强烈的主见和意志去实现一个东西,这样核心的东西属于自己是十分重要的。自己实现这个核心,而 AI 只是作为辅助,去帮我完成我已经知道的那些事情。</p> <p>这在另外一方面也说明了 AI 的局限性,那就是它不知道我的局限性在哪里。类似于它无法告诉我不知道什么,缺少什么。我作为使用者,只能对着它生成的文字发呆,仿佛已经理解学习到了什么,但这就是一种幻觉。我并没有掌握这些,以前更没有经历过这些。</p> <p>所以我使用它的态度同样是乐观却比较谨慎,以防止它把我给训练成傻子。</p> <h2>我得消费原文</h2> <p>在这这段采访出来之后,我的社交媒体的时间线上就充满了有关内容的视频片段,感受分享以及总结。带有人味的感想还好,但是 AI 总结实在是太无聊了。几乎是一致的结构,段落还有内容,唯一区别的是说法各有不同,可能是每个人用的 prompt 有不同。这里面有些讽刺,AI 总结最多的还是一段关于 AI 深刻洞见的长视频,实属是倒反天罡。</p> <p>在我花了一些时间看完了整段采访之后,我很确切的是那些总结对我一点价值都没有,有时甚至还有负面影响,影响了我对原视频的印象和理解。在原视频里,除了大段关于 AI 的主题,还有许多和学习与理解知识相关的内容。这些内容很具体,比如他们会聊会如何处理一段值得学习的代码片段,从使用它开始,关注每一个功能块,以及每个功能块的关系,抓住事情的主线。换句话说,它对我来说很有兴趣,但对你来说可能不是,对模型来说则更不是了。如果你全看模型总结,你获得是一个幽灵帮助你总结出来的所谓精华,它甚至都不是一个真实人的想法。在从总结,到最后发布总结的整个链条中,唯一存在的人性可能就是这个账号主人的口味和选择,而往往这样的选择中都带有广告。</p> <p>作为人类,我会理解我们不会倾向于喜欢二手的东西,比如二手烟之类的。但是我们天天却摄入海量的二手需求,二手观点,二手信息。有时甚至还不是二手的,而是经过层层包浆后的产物。它们全是 digital 的,都以二进制方式传递,因此非常具有欺骗性。</p> <p>为了能够确保多一些真实且富有个性的内容能传递出来,我需要消费原始的内容。</p> <h2>关于学习</h2> <p>他们描述了学习物理的好处,即通过系统性的训练可以获得一种能力,用它可以迅速把握一个领域的精髓和核心,通过抽象建立模型,去除暂时无用的噪音。一个使用数学的比喻就是泰勒展开一个新的知识领域,然后只留下其一阶和二阶,最精华的部分,有稳定的频率的基波。</p> <p>从内容上,从理解一个模块出发,提出问题,然后再抛出另外一个模块,能够巧妙的解决当前模块存在的问题,从而引发学生的啊哈时刻,然后逐渐拓展成一个简练和牢固的系统。当最基础的系统出现了之后,剩下的一切无非就是对效率的优化。大部分我们使用的系统或工具,都经过了无数优化的洗礼,变得极其复杂,以至于我们从外部观察完全无法理解。</p> <hr /> <h2>原版视频</h2> <p>&lt;iframe src="https://www.youtube.com/embed/lXUZvyajciY?si=JgsRgmanWJ4IYIr_" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;</p> <h2>翻译视频</h2> <p><a href="https://www.bilibili.com/video/BV1YvWpzcEdS/?vd_source=445faf5ad377780852f5150749f45ba9">B站地址</a></p> 在医院的急诊室https://blog.kaiyikang.com/posts/2025/%E5%9C%A8%E5%8C%BB%E9%99%A2%E7%9A%84%E6%80%A5%E8%AF%8A%E5%AE%A4/https://blog.kaiyikang.com/posts/2025/%E5%9C%A8%E5%8C%BB%E9%99%A2%E7%9A%84%E6%80%A5%E8%AF%8A%E5%AE%A4/Fri, 19 Sep 2025 00:00:00 GMT<p>在德国,这是我第一次走进急诊室,也是第一次夜晚在医院度过。</p> <p>事情的经过要从头一天的晚上说起。</p> <p>下了班,我在家旁边的抱石馆运动。场馆里脚臭味弥漫,白色的镁粉飞来飞去。场馆内人员拥挤,占满了通道。我往里走,找到了一片空地。眼前的墙向外倾斜,上面没什么人。我勉强爬了两三米,吊挂在上面没了力气,只好松开手。我没有经验,屁股先摔到了地上,而因为身体反应不及时,仍保持直立,最后腰椎受到了巨大的冲击。</p> <p>那一瞬间,世界暂停了。等身体恢复了知觉,腰部疼痛到无法呼吸。此刻想起,手里还会紧张到冒汗。我像只蠕虫在地垫上翻滚,耳朵里听到夹杂着德文和英文的关切,脑中一片空白,嘴里呢喃着说着还好还好。但其实并不好,我只不过想避免引人注目,节省出认知资源专心恢复。</p> <p>躺了一会儿,等身体恢复了,能起身走动,我拖着残破的身子回了家。忍痛洗漱完,躺在床上就掏出手机查各种信息。夜晚伤痛来得急,骨科专科是约不到了,于是赶忙约了个全科诊所,打算第二天一早就去查查。放下手机,关了灯,背脊传来阵痛,脑子充斥着杂乱的忧虑。我无法入睡,又打开了手机,歇斯底里地钻进了恶性循环,直到凌晨才昏昏沉沉的睡去。</p> <hr /> <p>第二天清晨,我先是去了全科诊所。大夫听了我的经历,简短地查看了状况,直接给了我两个选择,要么吃止疼药回去理疗,要么去急诊进一步检查。我不假思索的选择了后者。握着大夫给的转诊单,我径直坐车来到了家附近的大医院。</p> <p>兴许是因为上午,医院楼里人不多。一进来就能闻到独属于医院的味道,气氛因此变得大为不同。我顺着指示牌,找到了急诊室入口。那是一扇宽大的磨砂玻璃门,从外面完全看不到里面,大致只有些人影在隐约闪烁。我按了门铃,没怎么等待就开了。急诊室的人同样不多,医护人员看着也很松弛。他们查看了我的转诊单,让我在其中一个房间等待。而后来了一位看着像是印度裔的大夫进来给我诊断。她详细记录了我的状况,并指示我先去拍个 X 光片。</p> <p>放射科是单独的机构,接受外面的预约同时也和医院合作,后者优先级更高。X 光片的等候室在一个玻璃走廊上,外面是一条长长的甬道,尽头停着急救车。我心想,大概有不少生命垂危的人曾在这里穿梭。</p> <p>拍过 X 光片,本以为很快看到结果,但他们让我在大厅中等待。大厅中心很空旷,不锈钢椅子靠着周围墙壁。不少老人坐在那里,鲜有年轻人,这让我觉得自己很特殊。大厅周围连接了电梯,还有不少门。时不常有医生和护士出现在大厅,把病患推进急诊室。急诊室的磨砂玻璃门打开瞬间,我朝里面偷偷望去,见有不少病床,还有来回走动的医生。脑子里便生出了很多糟糕的幻想,也许都是从影视剧里看到的。那一刻,我切身体体会到了急诊科医生的辛苦。</p> <p>大概是忙得差不多了,我被叫了进去。急诊室里喧闹不少,有急救的人,也有被急救的人,乱哄哄的挤在走廊上。除了刚才的女医生,还多了另外一个男医生,我们一起站在急诊走廊的电脑前。他指着我的腰骨片子说:这里有阴影,可能是骨折,需要进一步查 MRI,现在有两个选项,要么自己预约拍片,要么在医院住一晚检查。我不假思索的说了后者,但其实我并没过脑子,以至于纠结是在决定后出现的,但后悔也毫无意义,因此我不得不顺势而为。</p> <p>我坐到一旁,女医生熟练地给我打了滞留针。瞬时,全徳预演警报响了起来。尖锐的滴答声,从每个人的手机中传出来,回荡在急诊室里。我忽然觉得,警报声和急诊室非常合拍,像某种舞台上的交响,一种特殊的背景旋律,把人们心中的匆忙和疼痛全部描摹了出来。急诊室变得更紧急了。</p> <hr /> <p>接过单据,出了急诊室,我上楼找到了住院区。前台的护士问我需要什么,我说要住院,并递上了单据。她愣了愣,旁边凑上来了同事,问我,你医生呢?我说她在忙,让我自己一上来。「哪里有让病人自己一个人上来的?」她们最后这句回复,让我印象深刻。</p> <p>电话沟通后,护士将我引向 28 号病房。</p> <p>房间内有四张大床,两个老人正卧床休息。我觉得自己格格不入。来自缅甸的护士给我事无巨细地介绍设施。床边有床头柜,旁边矗立着两个支架,一个用来吊点滴,另一个上面挂着仿佛来自上世纪的粗糙平板荧幕,上面有帮助按钮。我脱了鞋,把空空如也的袋子挂在一旁。身子平躺在床,眼睛盯着天花板,新鲜和恍惚。</p> <p>我知道我有很多时间,却不知能做什么。我无意识间弯曲手臂,滞留的针头搅动着肌肉,无时无刻提醒我在病床上。我刷手机,然后平躺发呆,如此往复下去。</p> <p>护士每次进来,都会是情绪饱满地询问我们需要什么帮助。我问她之后的安排,说有检测可能在明天,听消息就是。到了下午,我才稍感饥饿,餐盘上摆着水煮的西兰花,一些短粗的意面,以及牛肉粒与汤汁。我想到了大学的食堂,那里和这里差不多,总是吃的很凑活,咽下粗糙的食物,全靠能量饮料润滑。看在它们是食物,仍能填饱肚子,我没有抱怨。</p> <p>医院离家步行只需几分钟。我穿上外套,盖上针头,假借散步的名义回了趟家,拿了充电器和简单的洗漱用品,回去又继续躺在床上。又过了不知几小时,我以为检查会拖到明天,但护士通知说可以拍 MRI 的片子了,于是倍感惊喜,匆忙穿上外套下了楼,等待拍片。</p> <p>有人说进入核磁共振机,会激发幽闭恐惧。网上评论传来传去,有些人抗拒机器,把它说的和拔牙或做大手术似的。我觉得他们在胡扯,躺在机器上,闭上眼睛,戴上耳机,感受机器把我慢慢推入,感觉天逐渐变暗了。我因为不能动,听觉和触觉变得异常发达。机器的轰鸣声极其有节奏感,听着像 techno。脑子随着节奏,不受控制地飘荡,想到了热情的护士们:西班牙口音的护士,也许是个头头,各项事情安排的井井有条;缅甸护士,竟然能够说中文,她说自己在餐馆里从云南的老板那里学到的。她语气有能量又温柔,动作也及其麻利迅速,很厉害。</p> <p>没过十几分钟检查完,我回到病床,看到晚餐很简陋:面包片,猪肉片,酸黄瓜,黄油。不过让我意外的是,原来晚上即使吃这么少,也有足够的饱腹感。兴许是我只是躺在床上太久,没有任何消耗,但也足以让我反思晚餐是否有必要如此豪华,或说只是单纯的习惯或仪式罢了。</p> <p>窗外渐暗,白天值班的护士和我们道别,又迎来了一批值夜班。有两位比我还小的年轻人,看着还在实习,说要给我打抗血凝针。听到要打针我慌了神,想要推脱。他们同我耐心解释,说是医生授意,一定要打。我知道自己拗不过他们,因此就认怂了。年龄更小的男生显然将我作为实验的对象,在我肚子上扎针,手法算不上利索,但万幸没引起我更大的疼痛。</p> <hr /> <p>仿佛两个世界,病房的夜晚极其特殊,与平常的大不相同。我本以为夜晚的休息是对病人最好的疗愈,但其实却是痛苦的开端。</p> <p>我手表没了电,也就没太在意时间,用余光瞟到外面的天已经黑了。病房里的没有全黑,几盏小射灯仍能照出轮廓。除了我以外,还有两个老头,一个在侧边,一个在斜对面。他们都病的很重,似乎刚经历过巨大的手术,躺在那里蔫蔫的,也几乎无法靠自己的力量活动。止疼泵同他们的身体相连,各种检测仪器以错落的频率闪烁出不同颜色的光芒,同时发出富有节律的滴滴声。每过一段时间,不知什么地方的机器还会响起低沉的轰鸣声。</p> <p>我恍惚的半醒半睡,觉得窗外有人在放烟花,于是朝着小窗外望去。墙壁遮挡了视线,我只看到窗边的一侧有些色彩缤纷的倒影,同时也听到了轰鸣声。想到了句歌词,「看著窗外的光,分不清是路燈還是太陽」。我好奇,今天是什么特别的日子?城市中在庆祝什么?我们身处在另外一个世界空间动弹不得,仅能靠微弱的感官,去感受那个世界发生的事情。这个世界不会发生人类的共有。想到这些,我感觉到一丝落寞,于是挪了挪身子,尝试继续入睡。</p> <p>也许是夜晚太安静了,人的感官会被过度放大,因此痛苦也会。约莫过了零点,耳边传来了他们沉重的呻吟声。起初我并没有太过在意,想着有止疼泵的情况下,应该能帮助到他们,但随着操作止疼泵的咔哒声频繁响起,呻吟声反而变得越来越大,最后成了嘶吼。还能隐约识别到一些脏话,但词汇因为嘶吼的关系变得异常模糊。斜对面的老人叫的最为凄厉,持续的发声使他的嗓子嘶哑,即便如此,声音还在不断的持续。凌晨,护士曾来帮助过他一次,但也许是夜太深,他的痛苦并没因此而缓解。叫着叫着,直到后半夜,他失去了所有的力气,只能发出轻微的哼唧。病房再次沉寂下来,只留下机器在运作,我也能得以在恍惚中睡去。</p> <hr /> <p>这样度过了一个相当难熬的夜晚。第二天清晨,缅甸的护士来上早班,又用极富激情的语气呼唤我们起床。白天的光透过窗户,照亮了病房,直接冲散了夜晚沉重的气氛。早餐是面包,奶酪,还有咖啡。不得不说,医院里的咖啡感觉更好喝,温和又提神。</p> <p>我简单打理后,靠在床上发呆。斜对面的老头状态明显好了许多。他几乎没法活动,因此吃喝拉撒都需要护士的帮助。我看到护士将被子掀开,把宽大的,本质上就是前后两块布的手术服掀了起来,下面垫好像是一次性纸尿裤的东西,让他能顺利的排泄。结束后,护士利索地清理了现场,走进卫生间将排泄物给清理掉。整个过程极其专业且迅速,看得出来,她们已经做过太多次了。</p> <p>一方面,我感慨做护士或护工的辛苦,难怪类似岗位在德国需求量巨大。工作收入不高,却又脏又累,同时还要保持巨大的耐心和细心,不是菩萨做不来。另一方面我想到,用机器人取代真人是种糟糕的设想。技术一定会越来会好,在未来,机器人也确实有能力取代真人,但问题在于,这种设想来自于「健康人」,而不来自于「病人」。病人能做的,只能被动的接受,而决定不了对方是暖的还是冷的。相比技术进步,我更相信大部分的有钱有势的,会让真实的人类帮助他,而不是机器。</p> <p>还有个感慨是,人应该要终身保持锻炼与运动。之前没经历,只觉得运动是个精神泵,能让人精力充沛,充满活力就足够了。现在经历过了伤痛,体会到了它对身体物理方面的重要性。肌肉,筋,骨骼,神经等等,无时无刻的都会因为运动而受益,一旦它们强壮了,即便是年龄大了,身体机能降低,仍能帮助我们抵抗外部的各种风险。我希望在自己老的时候,身体还有能力去支撑我完成必要的动作,而不是全身脆弱,充满各种伤痛。</p> <hr /> <p>吃过早餐,医生开始绕着整个病房巡视。如同电视剧中看到的那样,一个稍微年长的医生打头阵,后面各种年轻的医生则跟在后面。进了病房,他们挨个巡诊,我是最后一个被转到的。</p> <p>看上去十分资深的女医生,先用德语和我解释检查的结果。我担心自己理解有误,所以又请她重新用英文解释了一遍。当然,「请」这个表达背后包含了很多勇气,尤其是看到她身后的年轻医生们拿着纸笔一边听一边记。我也不清楚,像我这种外国人是不是也会作为特殊的案例被他们收录进经验里。总而言之,最后诊断的结果算不上很糟。虽然腰部有压缩性骨折的情况,但万幸骨头没有移位,意味着不需要做手术或安装支架,只需要静养就可以。医生同时还强调,最起码六个星期内,不要做任何运动以及做任何需要腰的活动。</p> <p>面对网络上纷繁复杂令人焦虑的信息,医生的结论和建议让我释然了。我收拾东西临走前,医生把报告交给了我,还特地的嘱咐说,好好休息,不要去谷歌任何的信息,如果情况恶化了,直接来急诊室就是了。听完,我觉得无比安心。</p> <p>我走出病房,找到护士长,让她把我的预留针取出来,最后用磕绊的德语,十分用力地感谢她们的照顾和鼓励。等电梯的时候,我撇到有不少老人从病房里被推出来,又听到因痛苦而声嘶力竭的尖锐叫声,依旧在楼道深处回荡。</p> <p>我走出医院的大门,感觉通过了一道生与死的结界。结界的里侧是伤痛与死亡,外侧则是生机与希望。而细腻的温情和爱是少有能够跨过这道结界的虹光。</p> 快慢打字,深深深https://blog.kaiyikang.com/posts/2025/%E5%BF%AB%E6%85%A2%E6%89%93%E5%AD%97%E6%B7%B1%E6%B7%B1%E6%B7%B1/https://blog.kaiyikang.com/posts/2025/%E5%BF%AB%E6%85%A2%E6%89%93%E5%AD%97%E6%B7%B1%E6%B7%B1%E6%B7%B1/Mon, 13 Jan 2025 00:00:00 GMT<h2>慢写字,快打字</h2> <p>就在刚才尝试用笔写了一些字。不仅书写速度很慢,字在脑子里还要思维很久才能成型,而对比电脑打字,就更加缓慢了。</p> <p>因为长时间没有书写过文字,这种对比另我惊讶。哆哆嗦嗦的手像老年人一样写字,眼睛看着手,脑子站在一旁干着急,想写的话已经在脑子中重复了好几遍,手还没写完前几个字。</p> <p>我不知道是不是,但感觉上,缓慢的写字更符合自然的规律,也符合大脑的规律。极速的拼音输入法,一目十行,乃至思维,也飞速奔驰,完全停不下来。于是,人们嫌弃手写,也不再尝试手写。当人们长大了,从学校中出来,似乎再也不会在桌子上留下纸与笔,除了简单的速记,也不会写太长太长的。</p> <p>这种符合自然天性的缓慢,经过了时光的结晶,成了书信和贺卡,漂流进每个人都最珍爱的宝盒中。字迹变得模糊,纸张变得发黄,但质感和情绪依旧在那里。</p> <p>这是真实写字的独特魅力。</p> <h2>深深深</h2> <p>前有 Cal 的 Deep Work,后有 David 的 Depth Year。这就像是常年身处都市的人,去购买性能完全冗余的户外冲锋衣。我爱人她也问过我,我说这就像是一种向往,虽然自己没能达到,但是购买了,穿上了,就仿佛自己有了这种能力,愿景也变得稍微真实了一点点。</p> <p>对于 Deep 这个概念,我有类似的想法,很迷人,但是非常难做到。我会涉猎相关的主题,有一搭没一搭的,读着他们的概念,我就开始想象自己在做事的时候,也能变得十分 deep。而直到现在,我仍然觉得自己没有达到我所理解的 deep 状态,因此看着爱人在专心做事的时候,我会觉得尤其充满魅力。弱的时候,房间内的能量向她聚集,强的时候,宇宙能量向她发射高浓度射线。作为一个能量代理人,她只是投身在想做的事情上,而全然不会顾忌能量积聚这件事上。原初且纯粹,真是令人着迷。</p> <p>关于潜水,爱人是 freedriver,其中一个目标就是要潜水足够 deep。因此,这个意象会忽然不自主的和先前所提到的概念相互联系起来。我觉得它们具有一种感性上的关联。当我在尝试深入一件事情,而非真正深入意见事情的时候,三心二意会将我拉回到现实中,会让我重新感知到我在做一件不能中断的重要事情。简单的想象是,我在做一件重复的劳动,这样的劳动,每一次都需要伴随着惯性,要足够精确,要足够耐心,一旦分心,动作出现了偏差,那么劳动的结果就不会令人如意。我有明确的感觉,可能开始的几分钟我完全投入了,思维也完全沉浸或放空了,但坐着坐着,脑子突然升起了大的思维,将我从惯性抽离出来,然后我不再变得沉浸,反倒感觉恐惧,害怕自己的中断,担心事情无法顺利的执行下去。惶恐就像是正反馈电路一样,不断加强信号,从而把我彻底丢入到上空,直到无法生存的宇宙中。</p> <p>如果说上面的 deep 是时间上的和动作事物上的,还有一种则是精神上的。这点我同样也有感触,类似于背诵快忘没忘的单词,做一个还有些许记忆的代码题目,或读一本已经有模糊记忆的书籍。当我再次遇到他们的时候,我感觉自己在接触记忆或精神上的鼻涕。有沾上了一点,但也不多,牵拉一下还能成丝线,我也不知道什么时候回中断,有轻微的恐惧感,但更多是嫌弃。不够干脆清爽,不像是用一个带油的勺子用过了洗洁精,带着阻力的愉悦感。每当这种感觉袭来的时候,我会有些打退堂鼓,但次数不算多,很多时候是一咬牙一跺脚把那一大坨东西拿过来,让它变得轮廓清晰分明。一旦克服了,就会有别样的愉悦感和成就感。</p> <p>做的要够慢且够深,我觉得也算是一种做事智慧。</p> 往返于法兰克福和慕尼黑之间https://blog.kaiyikang.com/posts/2025/%E5%BE%80%E8%BF%94%E4%BA%8E%E6%B3%95%E5%85%B0%E5%85%8B%E7%A6%8F%E5%92%8C%E6%85%95%E5%B0%BC%E9%BB%91%E4%B9%8B%E9%97%B4/https://blog.kaiyikang.com/posts/2025/%E5%BE%80%E8%BF%94%E4%BA%8E%E6%B3%95%E5%85%B0%E5%85%8B%E7%A6%8F%E5%92%8C%E6%85%95%E5%B0%BC%E9%BB%91%E4%B9%8B%E9%97%B4/Sun, 30 Mar 2025 00:00:00 GMT<p>我们从未认真商量过具体几点起床,几点出发,只是估计了个上午九十点钟。等着起了床,简单收拾了行李,看着时间都快到了 11 点,才悻悻的出发前往停车场。</p> <p>从家出发走到停车场,我们路过火车站,看到行人们错落的站在站台上,听到喇叭里面的信息广播。景象和声音召唤着我的大脑回路,仿佛下一秒我就要岔开路,走向站台,但随即转念一想,有了车,大部分情况下,我们就无需光临德铁的车厢了。霎时间,竟然生出了些许感慨,但一想到德铁延误给我造成的糟糕感受,感慨也就烟消云散了。</p> <p>去的路途全是由她完成的。我曾询问她是否需要换着来开,她似乎状态很好,说着不用,就一直开完了全程。</p> <p>从 A5 一路向南,到了斯图加特掉转方向进入 A8 就能直接抵达慕尼黑。</p> <p>还记得一路上的天气由阴转阳,开始是乌云密布,小雨淅淅沥沥的,随着向南驾驶,大片的云散开,露出了太阳。我坐在副驾驶,看着变换的风景从眼前飞过,尤其是从狭窄到宽阔处,令我的印象尤其深刻。单向是宽阔的四车道,即便是在最右侧也可以疾驰到 130 以上,两旁是绿树林,远处有些恍惚的小山,天上的云则是格外的肥厚。一会儿是清淡飘逸的,一会儿又是浓密厚重的,天气也随着速度盘飘忽不定。</p> <p>去程的时候,我们没有特地的准备音乐,只是上了高速以后才慢慢甄选。我们以「印象派」的方式进行选取,印象深的排在最前面,而后是浅的。最深的,自然要数《超高音质 - 车载必备金曲》。只记得每个大叔大妈的车里都会有多张类似的 CD 碟片,专辑没有固定的名字,但必然是这些形容词的任意排列组合,而且最后歌手那个条目也必然是「艺术家」、「群星」之类的,不明所以的称呼。味道真是冲,但也真的醇。</p> <p>「内蒙古」,「草原」,「西藏」等等元素似乎更受老一辈的青睐,「纠结的情爱」都要往后靠一靠,要么是中年男人浑厚的嗓音,要么是奉为国宝艺术家的女高音,各类杂七杂八的气质都会汇聚在一张 CD 中。小时候听多了,听烦了,自己开上车以后竟然格外的带感,难道这就是所谓的老一辈的智慧?「诶嘿~嘿诶~」突然一阵俏皮的声音出现在了山歌中,把我一股股得抽回到了儿时的国内记忆中。看来,这也不失为一个感受回国气息的好方法。</p> <p>高速公路上,每过一段间隔,都会遇到休息站。站点的排布似乎非常有讲究,因为只要下体的膀胱开始抽动,等再开上十几分钟,就能见到快意释放的地方,所以,我大胆假设,这些站点的排布距离,也许就是根据成年人的平均膀胱尺寸和忍耐时间综合计算出来的,所以才会如此之精妙,让我们坐在车中啧啧称赞。</p> <p>休息站的消费感非常浓重,只要停下来,就让你不得不消费。除了昂贵的油费,还体现在厕所和食物的消费方式上。如厕要一欧元,投入硬币就会吐出一张一欧元的代金券,店内消费的时候,就可以使用它减免。感觉商店中的物价是平常的 1.5 倍,再加上代金券,价格其实还好,但购买一件东西才能使用一张券,因此多出来的券只好自己存着,筹划着下次再用。如厕是必须的,因此拿到券也是必然的,为了让自己感觉「值一点」,所以券也得用出去,这不又得买一杯咖啡,完后喝完坐车里继续等着咖啡因催尿,从而实现下一个休息站的如厕和消费循环。这些人,精得很,也坏得很。</p> <p>大概开了 5 个小时,终于到了慕尼黑,她累到沉睡过去。我没打扰,心里联想到了欧盟长途安全规定,说的是司机开长途,每次驾驶 4.5 小时后都要休息 45 分钟。这很合理,毕竟开车确实消耗精力,但对比国内却有不同。记忆中,在中国似乎不会有大巴车会司机会停下休息,又类似于土耳其的司机,穿个白褂子,中途还会停靠揽行人,休息最多就是喝口咖啡或是茶水。他们都像是大仙,盘着手里的巨大方向盘,在曲折的山路里各种游走,好不自在,但赶来赶去说白了还是为了能多赚些钱,多点休息的时间,为此在无形之中修炼并精进了各种驾驶能力。令人钦佩,但也确实是辛苦。</p> <p>因为我们已经领略过了 A5 的狭窄,所以回程我选择从 A9 转 A3,先是向北,再向西。实际体验下来也确实更好一些,道路更加宽阔,沿途的设施也更齐全。</p> <p>有了上次的长途驾驶经验后,我们两人有了经验,只要要提前存储好歌单,以免手忙脚乱,不知道听些什么。因为要检索自己最喜欢的歌曲,所以一不小心就鉴赏到了凌晨,最后整理出来了一张长长的列表。我们几乎没有添加重复的歌曲,蹊跷的是,唯独「套马杆」被添加了两次,兴许是草原和骏马的魅力,让我们的选择不谋而合,做得好!</p> <p>相对于她的稳扎稳开,我驾驶则稍微激进些。指示牌,车,我,三者的最低限决定了速度的上限,习惯以后,我能从超车中感受到了些快乐,当然,一切还要以安全为准。</p> <p>天气这次是反着的,由阴转阳。太阳逐渐从云中冒出来,光线照在手臂和身上,暖呼呼的。得益于宽阔的道路和丰富的植被,视野和景观非常好。看着山上的小城逐渐出现,再逐渐隐去,脑子里也会幻想着他们截然不同的生活方式,不以围绕着繁华中心而活的生活方式。</p> <p>开了两个多小时,我们找到了一个膀胱休息站,停靠了,上过厕所没有去买什么吃的,而是从袋子里拿出了她做的简单鱼汉堡。太阳被云雾笼罩着,天气变得阴阴的,风也在刮着。我们两人坐在车里,听着财经播客,一边吃着汉堡一边点评,口干了再嘬两口咖啡,还要防止汉堡渣渣掉进干净的车里。也许是因为开车不熟练的缘故,眼睛感到胀胀的,因此就把驾驶的宝座交给了她,让她负责之后的行程。</p> <p>熟练了以后,她开的更好了,车速控制得当,超车也更从容了。从中我们还获得了一个经验,就是遇到什么事情,先不要惊呼出来,而是稍微冷静的直接说明。因为惊呼会让驾驶的人感到慌张,而一慌张操作就会变形,车开不好,还闹的情绪不佳,所以抑制住呼喊的冲动,保持平和且镇静,对大家都好。</p> <p>一路上风平浪静,进了城,驶入了停车场。身体似乎没有之前的那样疲惫,也逐渐了习惯了开车和坐车的感觉,往后大概会越来越频繁了。</p> 数字证书的处理流程https://blog.kaiyikang.com/posts/2025/%E6%95%B0%E5%AD%97%E8%AF%81%E4%B9%A6%E7%9A%84%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B/https://blog.kaiyikang.com/posts/2025/%E6%95%B0%E5%AD%97%E8%AF%81%E4%B9%A6%E7%9A%84%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B/Mon, 24 Feb 2025 00:00:00 GMT<h2>例子</h2> <p>为了证明我的身份,让别人可以安全的和我通信,以及防止别人冒充我的身份,我可以申请身份证明:</p> <ol> <li>创建自己的秘密(私钥)和公开信息(公钥)</li> <li>使用自己的信息,填写申请表</li> <li>将申请表提交给第三方,第三方盖章,证明我是我</li> <li>最后我将证明和秘密放在一起,保存好</li> </ol> <p>使用身份证明的方法是:</p> <ol> <li>我使用秘密生成一个签名</li> <li>对方使用公开信息验证签名</li> <li>第三方使用签名验证我的身份</li> </ol> <h2>流程</h2> <p>申请数字证书的标准流程如下:</p> <ol> <li><strong>密钥对生成</strong></li> </ol> <ul> <li>创建私钥 (需严格保密)</li> <li>生成对应的公钥</li> </ul> <ol> <li><strong>证书申请</strong></li> </ol> <ul> <li>准备申请者信息</li> <li>填写证书申请表 (CSR)</li> </ul> <ol> <li><strong>CA 认证</strong></li> </ol> <ul> <li>提交至认证机构 (CA)</li> <li>CA 验证并签发证书</li> </ul> <ol> <li><strong>证书安装</strong></li> </ol> <ul> <li>合并私钥与证书</li> <li>安全保存完整证书</li> </ul> <h2>流程细节</h2> <p>使用技术的语言来讲,申请方首先应该创建 P12,它是 PKCS#12 的简称,全称是 Public Key Cryptography Standards,里面包含了:</p> <ul> <li>私钥</li> <li>证书(如果之后有)</li> <li>证书链(如果之后有)</li> </ul> <pre><code>keytool -genkeypair \ -keysize 4096 \ -keystore keystore_file.p12 \ -storetype PKCS12 \ -alias keystore_file \ -dname "CN=keystore_file,OU=dataproxy-services" \ -keyalg RSA \ -storepass abc </code></pre> <p>生成的 p12 文件本身就是一种 keystore,可以直接被用作 csr,即 Certificate Signing Request:</p> <pre><code>keytool -certreq \ -keystore keystore_file.p12 \ -alias keystore_file \ -file certificate.csr \ -storepass abc </code></pre> <p>其中包括了从私钥中提取出来的公钥,申请着的信息,以及使用私钥对上述信息的签名。这个文件将会在之后发送给 CA 机构进行签发。</p> <p>第三方的 CA 签发机构会验证信息,并使用自己的私钥签名 CSR 并且生成证书(链),发送回给申请者。</p> <p>申请者要将原始的,包含私钥的 p12 合并,完成证书的安装:</p> <pre><code>keytool -importcert \ -keystore keystore_file.p12 \ -file certificate.p7b \ -alias keystore_file \ -trustcacerts \ -noprompt \ -storepass abca </code></pre> <h2>如何使用</h2> <p>我希望将 p12 和 vault 结合起来使用。</p> <p>因为 p12 是二进制文件,不适合存储和传输,因此为了存储和使用,首先需要将 p12 转化为 base64:</p> <pre><code>base64 -i keystore_file.p12 -o p12.base64 </code></pre> <p>Vault 包含加密存储的功能,而 kv 适合存储证书类型的数据。我们将改文件存储到 Vault 中:</p> <pre><code>vault kv put custom/internal/certificates/kafkaKeyStore [email protected] </code></pre> <p>最后就是使用:</p> <pre><code>jks: secretList: - name: kafkaStore vaultPath: "custom/internal/certificates/kafkaKeyStore" </code></pre> <p>应用启动的时候,就可以直接获得证书了。</p> 蹦床https://blog.kaiyikang.com/posts/2025/%E8%B9%A6%E5%BA%8A/https://blog.kaiyikang.com/posts/2025/%E8%B9%A6%E5%BA%8A/Sat, 01 Mar 2025 00:00:00 GMT<h2>小时候的蹦床</h2> <p>在我 30 岁生日的时候,爱人提出了几个建议供我参考,其中一个是去玩电动卡丁车,另一个是玩蹦床。我琢磨了下,之后买了车可以肆意的开,不需要再特地花钱去开卡丁车,因此选择了前者去跳蹦床。</p> <p>我小时候就喜欢跳蹦床。那时长辈会带我去地坛公园玩耍,记得是从安定门桥下来的一个门进入公园,走过一段植满树木的路,就能到游乐园。游乐园虽然特别小,也很简陋,但我也很小,对比之下它就显得非常大。</p> <p>游乐园没有一个所谓的大门,各种玩乐设施随意的散落在各个角落,而在门口处的钓鱼和再往里面的蹦床是我的最爱。钓鱼远没有名字听上去的高大上,只记得就一个老头儿坐在低矮的板凳上,面前铺着塑料布,上面摆着钓到鱼以后的奖励玩具,旁边是个不大不小的「池塘」。充气的泳池里灌满着水,里面漂浮着五彩的塑料小鱼。小鱼一直在随着流动的水游动,至于水如何是如何变活的,我记忆模糊了。</p> <p>我常会在水池边上钓得入迷,每次不拿到一个塑料小恐龙之类的玩具,决不罢休返回。</p> <p>让我沉迷的不仅仅是钓鱼,往里走的蹦蹦床更是我的挚爱。为了保护小孩的安全,蹦床是封闭的,依稀的记得它有一道小铁门,孩子钻进去以后,老板会从外面关上,如果想要出去,就叫嚷一声,他就会起身上来给我们开门。</p> <p>蹦床似乎不止有一层,入口进去就是蹦床层,下面的一层铺着塑料小球,上面则是用网编织的通道,小孩可以从中来回穿梭。当然,我呆的最久的还是蹦床。蹦床的网编织的并不致密,透过方形的孔可以向下望,有时我蹦累了,就会趴在网上,透过小洞向下观望。五颜六色的弹性绳灰突突的相互重叠在一起,长久的使用痕迹显得蹦床很陈旧,但并不影响我跳跃的心情。我只记得身体很轻盈,每跳一下都像是飞了起来,为了让自己不停的飞,我会不停的跳。跳的时候,我会摆弄身体,让它在那一瞬间呈现出奇妙且怪异的形状。我觉得我成了异形。</p> <p>跳到尽兴,我就往周围一圈的软垫靠上去,头向着护栏外张望,找寻着长辈,想知道他们正在做什么,如果找不到踪影或仍聊的尽兴,我就会放松一口气,等待片刻又会重新回到蹦床的中央。</p> <p>从蹦床出来以后,脚上还有刚刚弹软的触感,走在坚实的大地上还觉得有些不适应。沉溺在上一刻的身体,反倒觉得脚下的坚实才是幻觉,于是不自主地用力一跃,等着脚底心和大地结结实实地碰撞在一起,我这才意识到,原来这是在回家的路上。</p> <h2>长大后的蹦床</h2> <p>一片宽阔的室内场地被分割成不同的区域,在区域中散落着切成矩形的蹦床。蹦床的材质编织的非常致密,看不清任何孔隙。</p> <p>时隔多年再跳上蹦床,脚下的坚实触感消失不见了,身体被抛在了半空中,虽然很短暂,但对身体的感知和体察却变得全方位起来。我只觉得身子变得巨大和沉重,前几日健身所造成的肌肉阵痛还没有消退,飘在空中,任何一个身体的扭动和造型都需要付出大量的能量。</p> <p>我对这样的变化感到有些吃惊,不过大概是身体还没有得到充分热身,这样的疲倦在蹦跳过一阵后就逐渐缓解,身体也变得越来越灵活。</p> <p>有几个场地布置了跳跑的障碍物,就是在蹦床之间加入了高低和形状不同的障碍物,灵巧的人可以迅速的翻越,像是一个专业的跑酷者。而不太熟练的人则需要在每个蹦床之间做停歇,上下跳动,寻找一个合适的契机再翻越。</p> <p>我做了些许尝试。也许是不知道正确的跳落和衔接技巧,身体到了半空中,盯着眼前的障碍物会不知所措。「哦!那个障碍物突然出现了,我应该立刻做些什么?它太高,太远了,我够不到…现在高度合适,我可以跳了!….不,我错过了那个最佳的时机,要等下一次跳跃了。」内心需要足够的准备才能开始,但那一瞬间只会在身体到达最高点才会出现,转瞬即逝间,身体就会掉落,即使意识准备的再充分,不去真正的跳跃也是无济于事。</p> <p>这成了勇敢者的游戏。身体升到最高点,意识快速的准备,勇气是绳索和抓手,让我迅速能够把握这快速的一瞬做出真正的反应,我朝着皮质软垫撞了上去。着陆的姿态有些狼狈,但我还是抓住了障碍物,手脚并用的爬上了它,不太利索的通过了第一个障碍。</p> <p>类似的过程要重复数次,直至意识心对此感到麻木,不再需要太多时间的思索和准备,身体和勇气已经提前做出了判断。经过多次跳跃,我总结出经验,心中突然出现的胆小鬼会让自己受到伤害。因为一切都已经就位了,如果身体突然卸力平衡了蹦床的弹力,身体就会留在地面上然后径直撞向障碍物。这可不是好迹象。</p> <p>心思越少,玩的越好,你会看到在场的许多小孩子都会玩得无比轻松和写意。</p> <p>临走前,我和爱人得出的结论是,蹦床应该是越年轻玩越好,等身体变得不再那么灵巧了,虽然也可以玩,但却少了很多的乐趣。身体漂浮在半空中,只有短暂的瞬间,这像是童年和青年的时光,顷刻之间就成了过往的回忆。</p> 命名:起一个明白的名字https://blog.kaiyikang.com/posts/2025/%E8%B5%B7%E4%B8%80%E4%B8%AA%E6%98%8E%E7%99%BD%E7%9A%84%E5%90%8D%E5%AD%97/https://blog.kaiyikang.com/posts/2025/%E8%B5%B7%E4%B8%80%E4%B8%AA%E6%98%8E%E7%99%BD%E7%9A%84%E5%90%8D%E5%AD%97/Fri, 28 Mar 2025 00:00:00 GMT<p>配置白名单,是一个技术术语,与它关联的上下文以及所造成的结果才是最重要的。这些信息应该被归档。</p> <p>具体而言,当我们修改了白名单后,系统会在初始化的时候读取白名单。当系统进行到 ServiceInfoCalculation 的环节时,会触发 ServiceStatusChangedDetector 以检查新的 ServiceInfo 和旧的区别。这些变化将通过不同方式被发送,被广播。而其中只有在 whitelist 中的 Service,它的变化才能够被广播出去。</p> <p>换句话说,白名单只不过是消息发送其中的一个环节,代表该处使用了这个技术,而其他的技术也可以被使用,比方说使用 hardcode array。对文档来,最重要的是 use case,是使用该技术所产生的影响和后果。</p> <p>如果单纯以白名单作为中心,描述需求,会让读者或使用者感到困惑,是一种不合适的方式。最终,我们都应以描述整体作为关键。</p> 重构一段简单用例的旅程与思考https://blog.kaiyikang.com/posts/2025/%E9%87%8D%E6%9E%84%E4%B8%80%E6%AE%B5%E7%AE%80%E5%8D%95%E7%94%A8%E4%BE%8B%E7%9A%84%E6%97%85%E7%A8%8B%E4%B8%8E%E6%80%9D%E8%80%83/https://blog.kaiyikang.com/posts/2025/%E9%87%8D%E6%9E%84%E4%B8%80%E6%AE%B5%E7%AE%80%E5%8D%95%E7%94%A8%E4%BE%8B%E7%9A%84%E6%97%85%E7%A8%8B%E4%B8%8E%E6%80%9D%E8%80%83/Tue, 18 Nov 2025 00:00:00 GMT<p>在<a href="a-simple-example-for-optimizing-architecture/">上一篇日志</a>中,我们要处理的核心主题是 <strong>ModuleStatus(模块状态)</strong>,并讨论了一个简单的例子。该例子包含两个用例(Use Case):一个用于更新数据,另一个用于重置数据。前者通过 HTTP 触发,后者通过 Events 触发。二者在逻辑上的主要区别在于:前者需要通过外部服务获取租户当前已存在的数据并进行存储;而后者则直接存储即可。</p> <p>在上一篇发布后,我收到了同事的反馈,并与其进行了一番讨论。讨论之后,我意识到上一篇日志存在一些问题:不仅对 Use Case 的定义不够明确,而且对经典的 DDD 和 Clean Architecture 的概念也存在模糊与混淆。</p> <p>因此,我打算对当前的实现(Implementation)进行一次较为完整的重构,以便让自己对它有更清晰的认知。这段旅程将从核心 Domain 出发,经历多个 Use Case。其中一个用例涉及与外部系统的交互,逻辑相对复杂,因此我会作为重点进行考察。最后,我将以如何调用这些 Use Case 作为结尾。</p> <p>从文件目录结构上看,我将其分为三个部分:</p> <ul> <li><strong>Application</strong>:主要负责应用逻辑,核心包含 Use Case 和 Port。</li> <li><strong>Domain</strong>:核心逻辑所在,仅包含最基本的算法,不依赖任何外部技术实现。</li> <li><strong>Infrastructure</strong>:主要负责 Port 的 Adapter 实现,即各种外部适配器,它们依赖于 Application 中的 Port。</li> </ul> <p>这是一张完整的架构地图。不用担心,我们会从头开始搭建。</p> <pre><code>. ├── application │ ├── exception │ │ └── ModuleStatusApiException.java │ ├── port │ │ ├── inbound │ │ │ └── ModuleStatusQueryPort.java │ │ └── outbound │ │ ├── ModuleStatusRepositoryPort.java │ │ ├── RegistryModulePort.java │ │ ├── EventServicePort.java │ │ └── TenantDirectoryPort.java │ └── usecase │ ├── ModuleStatusQueryUseCase.java │ ├── ResetModuleStatusUseCase.java │ └── UpdateModuleStatus.java ├── domain │ ├── exception │ │ ├── EnterpriseModuleNotFoundDomainException.java │ │ ├── ModuleNotFoundDomainException.java │ │ └── TenantNotFoundDomainException.java │ ├── model │ │ ├── EnterpriseModuleStatus.java │ │ ├── TenantModuleStatuses.java │ │ └── TenantModuleStatus.java │ └── service │ └── ModuleStatusService.java └── infrastructure └── adapter ├── inbound │ └── rest │ ├── EnterpriseModuleStatusResource.java │ └── dto │ └── TenantModuleStatusesRequest.java └── outbound ├── mapper │ └── RegistryModuleToEnterpriseModuleStatusMapper.java ├── RegistryModuleAdapter.java ├── EventServiceAdapter.java └── TenantDirectoryAdapter.java </code></pre> <h2>创建 Domain 与简单的用例</h2> <p>混沌初开之时,数据结构是一切的起点。记得有人曾说过 " 软件系统 = 数据结构 + 算法 ",最近浏览的一篇日志也提到,数据结构决定了产品的形态。</p> <p>因此,我们要做的第一件事就是创建数据模型和结构。毫无疑问,它属于 Domain 中的 Model。以下是数据模型的代码:模块 ID 及其状态组成了最基本的 Status,而 Statuses 则包含了一个 Status 列表以及租户标识(TenantId)。</p> <pre><code>package com.example.modulesystem.domain.model; public record TenantModuleStatus(@NonNull ModuleId moduleId, Boolean isActive) { public TenantModuleStatus withStatusReset(){ return new TenantModuleStatus(this.moduleId(), false); } } </code></pre> <pre><code>package com.example.modulesystem.domain.model; public record TenantModuleStatuses(@NonNull TenantId tenantId, @NonNull List&lt;TenantModuleStatus&gt; tenantModuleStatus) { public Optional&lt;TenantModuleStatus&gt; getModuleStatusFor( @NonNull final ModuleId moduleId) { return tenantModuleStatus.stream() .filter(fs -&gt; fs.moduleId().toString().equalsIgnoreCase(moduleId.toString())) .findFirst(); } } </code></pre> <p>对应数据库的 Entity 和 Table 均已提前创建。</p> <p>接下来,我们需要定义 Repository 接口。虽然其结构简单,符合模版化的增删改查(CRUD),但关键在于:<strong>它定义了我在 Use Case 中需要使用什么接口</strong>。Interface 及其对应的实现通常包含大量样板代码,因此这里只列出 Port 的部分,忽略具体的实现细节。</p> <pre><code>package com.example.modulesystem.application.port.outbound; public interface ModuleStatusRepositoryPort { TenantModuleStatuses findByTenantId(@NonNull final TenantId tenantId); boolean save(@NonNull final TenantModuleStatuses tenantModuleStatuses); } </code></pre> <p>在 Clean/Hexagonal Architecture 中,Repository 的 Port 属于 Application Outbound,它的存在是为了服务于 Use Case。Application 作为业务的主导者,明确知道自己需要外部世界提供什么样的服务和工具来实现特定业务。它不关心具体的实现细节,也不关心数据的来源或获取方式。</p> <p>准备好了与数据库交互的 Port 后,我们现在可以以产品经理的视角出发,在 Application 中创建最基础的 Use Case。以下是两个简单的例子:</p> <p>第一个用例是直接从数据库获取数据,并假设不需要记录任何日志。</p> <pre><code>package com.example.modulesystem.application.usecase; public class ModuleStatusQueryUseCase { private final ModuleStatusRepositoryPort moduleStatusRepository; public ModuleStatusQueryUseCase(final ModuleStatusRepositoryPort moduleStatusRepository) { this.moduleStatusRepository = moduleStatusRepository; } public TenantModuleStatuses apply(@NonNull final TenantId tenantId) { return moduleStatusRepository.findByTenantId(tenantId); } } </code></pre> <p>第二个用例稍微复杂一点,但仍旧只需要依赖 Repository 的 Port:从数据库读取数据,重置状态,并保存回数据库。</p> <p>Use Case 通过 Port 控制数据,不需要、也不应该知晓任何底层实现的细节。</p> <pre><code>package com.example.modulesystem.application.usecase; @Slf4j public class ResetModuleStatusUseCase { private final ModuleStatusRepositoryPort moduleStatusRepository; public ResetModuleStatusUseCase(final ModuleStatusRepositoryPort moduleStatusRepository) { this.moduleStatusRepository = moduleStatusRepository; } public void apply(@NonNull final TenantId tenantId) { log.info("Invoking factory reset of module statuses for TenantId={}", tenantId); final TenantModuleStatuses currentStatuses = moduleStatusRepository.findByTenantId(tenantId); final TenantModuleStatuses resetStatuses = new TenantModuleStatuses( currentStatuses.tenantId(), currentStatuses.tenantModuleStatus() .stream() .map(TenantModuleStatus::withStatusReset) .toList() ); moduleStatusRepository.save(resetStatuses); } } </code></pre> <p>到这里我们会面临一个问题:试想如果我们有一个 Controller 或 Resource 需要查询数据库中的数据,是否可以直接通过 Controller 连接 Repository 的 Port,而不经过 Use Case 直接处理?答案是否定的。因为位于 Inbound Adapter 层的 Controller,其职责是翻译外部的调用。它就像前台员工,不能不经过中间经理(Use Case)的审批,就直接跑到数据中心调取数据。严格来讲,即便是非常简单的逻辑,也理应属于一种特定的 Case,不能因为它简单就让它 " 钻后门 "。</p> <p>这是当前的目录树结构:</p> <pre><code>. ├── application │ ├── port │ │ └── outbound │ │ └── ModuleStatusRepositoryPort.java │ └── usecase │ ├── ModuleStatusQueryUseCase.java │ └── ResetModuleStatusUseCase.java └── domain └── model ├── TenantModuleStatus.java └── TenantModuleStatuses.java </code></pre> <p>第三个用例最为复杂,但也最接近现实情况。我们需要依次完成下列步骤:</p> <ol> <li>从输入参数中获取 TenantId。</li> <li>通过外部 TenantDirectory 服务,确保 TenantId 存在。</li> <li>从外部 <code>RegistryLookup</code> 和 <code>RegistryModuleService</code> 中获取 <code>RegistryModule</code> 清单,判断 Modules 是否存在于该清单中。</li> <li>判断 Modules 是否属于 Enterprise 类型。</li> <li>转换数据类型,并存储。</li> <li>调用外部服务 EventService,触发 Cache 失效。</li> </ol> <p>原始的核心代码简要地描述了这个过程:</p> <pre><code>public void apply(final ModuleStatusModel moduleStatusModel) { // Step 1 final TenantId tenantId = moduleStatusModel.tenantId(); // Step 2 guardTenantExists(tenantId); // Step 3 final List&lt;RegistryModule&gt; registryModules = lookupModulesForRegistry(tenantId, getContext()); guardModuleExists(moduleStatusModel, registryModules); // Step 4 guardModuleIsEnterprise(moduleStatusModel, registryModules); // Step 5 final ModuleStatusModel mappedModuleStatusModel = mapModuleIdToMatchRegistry(moduleStatusModel, registryModules); final boolean stateHasChanged = moduleStatusRepository.save(mappedModuleStatusModel); // Step 6 if (stateHasChanged) { log.debug("Invalidating ModuleCache for TenantId={}", tenantId); eventService.invalidateCacheForTenants(List.of(tenantId.toString())); } } </code></pre> <h2>深入并拆解用例</h2> <p>步骤一是从输入参数中获取 TenantId,非常简单,我们可以直接跳到步骤二。其中 TenantId 类型默认为系统定义的核心类型,在此不多赘述。</p> <pre><code>final TenantId tenantId = moduleStatusModel.tenantId(); </code></pre> <h3>步骤二</h3> <p>步骤二需要通过外部系统 <code>TenantDirectory</code> 判断该 TenantId 是否存在。原始的方法如下:</p> <pre><code>private void guardTenantExists(final TenantId tenantId) { // 模拟专有驱动连接控制 try (final var rc = InternalDriver.controlRouting(RoutingControl.READ_ONLY, getClass().getName())) { final boolean tenantExists = tenantDirectory.tenantExists(tenantId); if (!tenantExists) { log.warn("No tenant found for id '{}'", tenantId); throw ModuleStatusApiException.unknownTenant(tenantId); } } } </code></pre> <p>这段代码看似简单,但却融合了三部分含义:</p> <ul> <li><strong>Infrastructure</strong>:具体的技术控制,如 <code>InternalDriver.controlRouting</code>。</li> <li><strong>Outbound Port</strong>:调用外部依赖,如 <code>tenantDirectory.tenantExists</code>。</li> <li><strong>Domain</strong>:业务规则,即租户必须存在 <code>if(!tenantExists)</code>。</li> </ul> <p>我们需要对它进行拆分。</p> <p>首先剥离出 Domain 逻辑。作为 Domain,它不应知道如何查询,也不应该清楚日志记录,不对外部有依赖且不了解技术栈,只知道 " 如果不存在,就报错 "。按照这个思路,简化后的逻辑是:</p> <pre><code>package com.example.modulesystem.domain.service; public class TenantValidator { public void guardTenantExists(final TenantId tenantId, final boolean exists) { if (!exists){ throw new TenantNotFoundDomainException(tenantId); } } } </code></pre> <p>以及这里需要的 Exception:</p> <pre><code>package com.example.modulesystem.domain.exception; public class TenantNotFoundDomainException extends RuntimeException { private static final long serialVersionUID = 1L; private final TenantId tenantId; public TenantNotFoundDomainException(final TenantId tenantId) { super("No tenant found for TenantId: "+tenantId.toString()); this.tenantId = tenantId; } public TenantId getTenantId() { return tenantId; } } </code></pre> <p>应该专门为 Domain 创建一个特定的 Exception,之后在 Use Case 中捕获这个 Exception。这样做的目的是为了让 Domain 能够与外界解耦。通俗地理解就是,即便删除 Application 和 Infrastructure 部分,IDE 依旧不会报错,编译也能通过。这就是我们熟知的<strong>依赖</strong>管理——Domain 不应该依赖外部世界。</p> <p>接下来我们向外拓展,创建 Use Case 需要的外部能力,即 <code>TenantDirectory</code>。它可以将 exists 的 boolean 值传递进内部系统,最后交给 Domain 做判断。为此,我们需要在 Application 中定义一个 Outbound Port。这个 Port 应该由内部的 Application 定义,而不是外部。</p> <pre><code>package com.example.modulesystem.application.port.outbound; public interface TenantDirectoryPort { boolean tenantExists(TenantId tenantId); } </code></pre> <p>实现的 Adapter 位于最外层的 Infrastructure。它依赖内部定义的 Port 并引入外部库,两者共同实现内部所需要的功能。</p> <pre><code>package com.example.modulesystem.infrastructure.adapter.outbound; public class TenantDirectoryAdapter implements TenantDirectoryPort { private final TenantDirectory tenantDirectory; public TenantDirectoryAdapter(final TenantDirectory tenantDirectory) { this.tenantDirectory = tenantDirectory; } @Override public boolean tenantExists(final TenantId tenantId) { // 模拟技术细节实现 try (var rc = InternalDriver.controlRouting( InternalDriver.READ_ONLY, getClass().getName())) { return tenantDirectory.tenantExists(tenantId); } } } </code></pre> <p>回顾先前的代码,发现还有一个日志逻辑:<code>log.warn("No tenant found for id '{}'", tenantId);</code>。它应该被放在哪里?因为 Domain 只会表达核心的规则(即规则不满足就失败),不会关心具体的记录,所以日志可以交给 Use Case 或 Infrastructure。</p> <p>这里我将其理解为记录外部系统的状态,所以在 Infrastructure 中加入 <code>log.debug("Checked tenant existence for ID {}: {}", tenantId, exists);</code> 以作为技术日志。</p> <p>或者你也可以选择在 Use Case 中加入。</p> <p>该步骤在 Use Case 中的完整代码是:</p> <pre><code>// Step 2 UseCase final boolean exists = tenantDirectoryPort.tenantExists(tenantId); try{ tenantValidator.guardTenantExists(tenantId, exists); } catch (final TenantNotFoundDomainException ex) { log.warn("No tenant found for id '{}'", tenantId); throw ModuleStatusApiException.unknownTenant(tenantId); } </code></pre> <h3>步骤三</h3> <p>首先看原来的代码,它分为两个部分:先获取 <code>RegistryModule</code>,然后调用 <code>guardModuleExists</code> 与输入参数比较。</p> <pre><code>final List&lt;RegistryModule&gt; registryModules = lookupModulesForRegistry(tenantId, getContext()); guardModuleExists(moduleStatusModel, registryModules); </code></pre> <p>要注意的是,我们引入了外部的模型 <code>RegistryModule</code>。为了不引入与 Domain 无关的信息,或者说避免 " 污染 " 系统,首先要定义一个专属于 Domain 的数据类型。</p> <pre><code>package com.example.modulesystem.domain.model; public record EnterpriseModuleStatus(@NonNull ModuleId moduleId, @NonNull boolean isEnterprise) { } </code></pre> <p>然后我们需要一个 Mapper,能将外部的 <code>RegistryModule</code> 转化为内部数据模型。</p> <p>我把它放进 <code>infrastructure.outbound</code> 中,因为它代表的是将外部数据转换至内部数据,该转换发生在 Infrastructure -&gt; Application 的边缘位置。我理解的边缘,类似于 Return 位置或是方法入参位置。</p> <pre><code>package com.example.modulesystem.infrastructure.adapter.outbound.mapper; public class RegistryModuleToEnterpriseModuleStatusMapper { public static List&lt;EnterpriseModuleStatus&gt; toDomain(final List&lt;RegistryModule&gt; registryModules) { return registryModules .stream() .map(module -&gt; new EnterpriseModuleStatus( ModuleId.of(module.getModuleId()), hasEnterpriseTag(module) )).toList(); } private static boolean hasEnterpriseTag(final RegistryModule module) { return module.getTags().stream().anyMatch(tag -&gt; tag.equalsIgnoreCase(ModuleTag.IS_ENTERPRISE.value())); } } </code></pre> <p>有了内部的数据类型和转换器,我们可以在 Application 的 Outbound 中创建一个获取 <code>EnterpriseModuleStatus</code>(或说 <code>RegistryModule</code>)的 Port,专门供 Use Case 使用。而这个 Port 的实现,因为不是 Application 关注的重点,可以一股脑地全部放进 Infrastructure 的 Adapter 中。</p> <p>这也就是人们常说的依赖倒置(Dependency Inversion),即高层模块不依赖外部实现的 Infrastructure,而只依赖抽象 Port,但是 Infrastructure 必须依赖 Port。</p> <p>我们首先定义一个 Port:</p> <pre><code>package com.example.modulesystem.application.port.outbound; public interface RegistryModulePort { List&lt;EnterpriseModuleStatus&gt; findByTenantId(TenantId tenantId); } </code></pre> <p>然后实现该 Interface 的 Adapter,其中的大部分代码可以直接复制。你不必阅读完整的细节,但可以特地看看方法最后的转换部分,它将外部的 <code>RegistryModule</code> 转换成了 <code>EnterpriseModuleStatus</code>。</p> <pre><code>package com.example.modulesystem.infrastructure.adapter.outbound; @Slf4j public class RegistryModuleAdapter implements RegistryModulePort { private final EventService eventService; private final RegistryLookup registryLookup; private final RegistryModuleService registryModuleService; public RegistryModuleAdapter( final EventService eventService, final RegistryLookup registryLookup, final RegistryModuleService registryModuleService){ this.eventService = eventService; this.registryLookup = registryLookup; this.registryModuleService = registryModuleService; } // 省略具体的上下文构建方法… @Override public List&lt;EnterpriseModuleStatus&gt; findByTenantId(final TenantId tenantId) { // 模拟上下文 final var context = new Object(); log.debug("Fetching RegistryModules for TenantId={}", tenantId); final List&lt;RegistryInfo&gt; registries = registryLookup.getRegistriesFor(tenantId, null, context); if (registries.isEmpty()) { return Collections.emptyList(); } final List&lt;RegistryModule&gt; associatedModules = new ArrayList&lt;&gt;(); for (final RegistryInfo registry : registries) { final Optional&lt;RegistryModules&gt; modulesFromCache = Optional.ofNullable( registryModuleService.findUnfilteredByRegistryIdCached(registry.getRegistryId())); final RegistryModules modules = modulesFromCache.orElseGet( () -&gt; registryModuleService.findUnfilteredByRegistryId(registry.getRegistryId())); if (modules != null) { associatedModules.addAll(modules.getAssociatedModules()); } else { log.debug("No Modules for registry '{}' found", registry.getRegistryId()); } } return RegistryModuleToEnterpriseModuleStatusMapper.toDomain(associatedModules); } } </code></pre> <p>有了 Port,也有了实际的 Adapter,终于可以开始搭建 Use Case 和 Domain 的逻辑了。核心逻辑是:我们需要确保输入模块和已经存在的模块相互匹配,如果模块根本不存在,则直接抛出错误。</p> <pre><code>package com.example.modulesystem.domain.service; public class ModuleGuard { public void guardModuleExists( final TenantModuleStatuses tenantModuleStatuses, final Collection&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses) { final List&lt;String&gt; moduleIds = enterpriseModuleStatuses.stream() .map(moduleIsEnterprise -&gt; moduleIsEnterprise.moduleId().toString()) .toList(); final List&lt;String&gt; missingIds = tenantModuleStatuses.tenantModuleStatus().stream().map( dfs -&gt; dfs.moduleId().toString().toLowerCase() ).filter(dfs -&gt; !moduleIds.contains(dfs)).toList(); if(moduleIds.isEmpty() || !missingIds.isEmpty()) { throw new ModuleNotFoundDomainException(tenantModuleStatuses.tenantId(), missingIds); } } } </code></pre> <p>最后 Use Case 使用这个逻辑:</p> <pre><code>final List&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses = registryModulePort.findByTenantId(tenantId); try { moduleGuard.guardModuleExists(tenantModuleStatuses, enterpriseModuleStatuses); } catch (final ModuleNotFoundDomainException ex) { throw ModuleStatusApiException.noModuleForRegistryFound(tenantId, ex.getMissingModuleIds().toString()); } </code></pre> <p>同样,对于 Exception,只需要在它们所属的层定义:</p> <ul> <li><code>ModuleNotFoundDomainException</code> 位于 <code>domain.exception</code> 中。</li> <li><code>ModuleStatusApiException</code> 位于 <code>application.exception</code> 中。</li> </ul> <p>简单总结一下步骤三的流程:</p> <ol> <li>定义 Domain 模型和 Mapper,确保数据格式正确。</li> <li>定义 Port 和 Adapter,确保外部数据可以进入。</li> <li>准备 Domain 逻辑并加入 Use Case。</li> </ol> <h3>步骤四</h3> <p>这一步我们要判断模块的类别是否属于 Enterprise。可以看到原始代码严重依赖外部数据结构:</p> <pre><code>private static void guardModuleIsEnterprise( final TenantModuleStatusesModel moduleStatusModel, final Collection&lt;RegistryModule&gt; registryModules) { final List&lt;RegistryModule&gt; enterpriseModules = registryModules.stream() .filter(cf -&gt; cf.getTags().stream() .anyMatch(tag -&gt; tag.equalsIgnoreCase(ModuleTag.IS_ENTERPRISE.value()))) .toList();; // Filter Request for any non-Enterprise modules final List&lt;String&gt; nonEnterpriseModules = moduleStatusModel.moduleStatusModel() .stream() .map(fs -&gt; fs.moduleId().toString().toLowerCase()) .filter(fid -&gt; enterpriseModules.stream() .map(pf -&gt; pf.getModuleId().toLowerCase()) .noneMatch(fid::equals)) .toList(); if (!nonEnterpriseModules.isEmpty()) { final TenantId tenantId = moduleStatusModel.tenantId(); throw ModuleStatusApiException.nonEnterpriseModuleProvided(tenantId, nonEnterpriseModules); } } </code></pre> <p>得益于我们在步骤三中已经提前构建好了 Domain 的数据模型 <code>EnterpriseModuleStatus</code>,该步骤的逻辑可以大幅度简化,结构也十分清晰。</p> <pre><code>package com.example.modulesystem.domain.service; public class EnterpriseModuleGuard { public static void guardModuleIsEnterprise( final TenantModuleStatuses tenantModuleStatuses, final Collection&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses) { final List&lt;String&gt; enterpriseModules = enterpriseModuleStatuses .stream() .filter(EnterpriseModuleStatus::isEnterprise) .map(module -&gt; module.moduleId().toString()) .toList(); final List&lt;String&gt; nonEnterpriseModules = tenantModuleStatuses .tenantModuleStatus() .stream() .map(dfs -&gt; dfs.moduleId().toString().toLowerCase()) .filter(fid -&gt; enterpriseModuleStatuses.stream() .map(pfs -&gt; pfs.moduleId().toString()) .noneMatch(fid::equals)).toList(); if (!nonEnterpriseModules.isEmpty()) { final TenantId tenantId = tenantModuleStatuses.tenantId(); throw new EnterpriseModuleNotFoundDomainException(tenantId, nonEnterpriseModules); } } } </code></pre> <h3>步骤五</h3> <p>当输入的数据经过检查确认无误后,只需要经过简单的转换,就可以准备存储到系统的数据库中。由于是 Domain 内部的数据转换,我们直接将 Mapper 放在 Domain 内部来处理,新建一个 Mapper 即可,等待后续 Use Case 调用。</p> <pre><code>package com.example.modulesystem.domain.mapper; public class EnterpriseModuleToStatusMapper { public TenantModuleStatuses toTenantModuleStatuses( final TenantModuleStatuses tenantModuleStatuses, final Collection&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses ) { final Map&lt;String, String&gt; mappedModuleIds = enterpriseModuleStatuses .stream() .collect(Collectors.toMap( module -&gt; module.moduleId().toString().toLowerCase(), module -&gt; module.moduleId().toString(), (existingValue, newValue) -&gt; existingValue)); final List&lt;TenantModuleStatus&gt; correctlyCapitalizedModuleId = tenantModuleStatuses.tenantModuleStatus() .stream() .map(dfs -&gt; { final String moduleId = mappedModuleIds.get(dfs.moduleId().toString().toLowerCase()); return new TenantModuleStatus(ModuleId.of(moduleId), dfs.isActive()); }).toList(); return new TenantModuleStatuses(tenantModuleStatuses.tenantId(), correctlyCapitalizedModuleId); } } </code></pre> <p>在 Use Case 中,转换并存储。</p> <pre><code>final boolean statusHasChanged = moduleStatusRepositoryPort.save(enterpriseModuleToStatusMapper.toTenantModuleStatuses(tenantModuleStatuses,enterpriseModuleStatuses)); </code></pre> <h3>步骤六</h3> <p>最后一步很简单,触发外部系统的方法让 Cache 失效。类似步骤二,只需要创建 Port 和 Adapter。</p> <pre><code>package com.example.modulesystem.application.port.outbound; public interface EventServicePort { void invalidateModuleCacheForTenants(List&lt;String&gt; tenantIds); } </code></pre> <pre><code>package com.example.modulesystem.infrastructure.adapter.outbound; public class EventServiceAdapter implements EventServicePort { private final EventService eventService; public EventServiceAdapter(final EventService eventService) { this.eventService = eventService; } @Override public void invalidateModuleCacheForTenants(final List&lt;String&gt; tenantIds) { eventService.invalidateCacheForTenants(tenantIds); } } </code></pre> <h3>合并与整理</h3> <p>终于,将所有的步骤整合进来,我们得到了最终的 Use Case。</p> <pre><code>package com.example.modulesystem.application.usecase; @Slf4j public class UpdateModuleStatus { // outbound port private final TenantDirectoryPort tenantDirectoryPort; private final RegistryModulePort registryModulePort; private final ModuleStatusRepositoryPort moduleStatusRepositoryPort; private final EventServicePort eventServicePort; // domain service private final TenantValidator tenantValidator; private final ModuleGuard moduleGuard; private final EnterpriseModuleGuard enterpriseModuleGuard; private final EnterpriseModuleToStatusMapper enterpriseModuleToStatusMapper; public UpdateModuleStatus(final TenantDirectoryPort tenantDirectoryPort, final RegistryModulePort registryModulePort, final ModuleStatusRepositoryPort moduleStatusRepositoryPort, final EventServicePort eventServicePort, final TenantValidator tenantValidator, final ModuleGuard moduleGuard, final EnterpriseModuleGuard enterpriseModuleGuard, final EnterpriseModuleToStatusMapper enterpriseModuleToStatusMapper) { this.tenantDirectoryPort = tenantDirectoryPort; this.registryModulePort = registryModulePort; this.moduleStatusRepositoryPort = moduleStatusRepositoryPort; this.eventServicePort = eventServicePort; this.tenantValidator = tenantValidator; this.moduleGuard = moduleGuard; this.enterpriseModuleGuard = enterpriseModuleGuard; this.enterpriseModuleToStatusMapper = enterpriseModuleToStatusMapper; } public void handle(final TenantModuleStatuses tenantModuleStatuses) { final TenantId tenantId = tenantModuleStatuses.tenantId(); // Step 2 final boolean exists = tenantDirectoryPort.tenantExists(tenantId); try { tenantValidator.guardTenantExists(tenantId, exists); } catch (final TenantNotFoundDomainException ex) { log.warn("No tenant found for id '{}'", tenantId); throw ModuleStatusApiException.unknownTenant(tenantId); } // Step 3 final List&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses = registryModulePort.findByTenantId(tenantId); try { moduleGuard.guardModuleExists(tenantModuleStatuses, enterpriseModuleStatuses); } catch (final ModuleNotFoundDomainException ex) { throw ModuleStatusApiException.noModuleForRegistryFound(tenantId, ex.getMissingModuleIds().toString()); } // Step 4. try { enterpriseModuleGuard.guardModuleIsEnterprise(tenantModuleStatuses, enterpriseModuleStatuses); } catch (final ModuleNotFoundDomainException ex) { throw ModuleStatusApiException.nonEnterpriseModuleProvided(tenantId, ex.getMissingModuleIds()); } // Step. 5 final boolean statusHasChanged = moduleStatusRepositoryPort.save( enterpriseModuleToStatusMapper.toTenantModuleStatuses(tenantModuleStatuses,enterpriseModuleStatuses)); if (statusHasChanged) { log.debug("Invalidating ModuleCache for TenantId={}", tenantId); eventServicePort.invalidateModuleCacheForTenants(List.of(tenantId.toString())); } } } </code></pre> <p>当然,这还算不上最简洁的形式,我们还应对它进行进一步简化。</p> <p>首先整合 Catch 块。其次,我们可以假定所有的 Domain Services 边界都在 <code>ModuleStatusService</code> 中。经过这样的整理,神奇的事情发生了——它竟然和我们最初看到的逻辑十分接近!并且在外形上,非常像经典的、依赖于大 Service 的 DDD 模型。</p> <p>简化后的代码如下:</p> <pre><code>package com.example.modulesystem.application.usecase; @Slf4j public class UpdateModuleStatus { // outbound port private final TenantDirectoryPort tenantDirectoryPort; private final RegistryModulePort registryModulePort; private final ModuleStatusRepositoryPort moduleStatusRepositoryPort; private final EventServicePort eventServicePort; // domain service private final ModuleStatusService moduleStatusService; public UpdateModuleStatus(final TenantDirectoryPort tenantDirectoryPort, final RegistryModulePort registryModulePort, final ModuleStatusRepositoryPort moduleStatusRepositoryPort, final EventServicePort eventServicePort, final ModuleStatusService moduleStatusService ) { this.tenantDirectoryPort = tenantDirectoryPort; this.registryModulePort = registryModulePort; this.moduleStatusRepositoryPort = moduleStatusRepositoryPort; this.eventServicePort = eventServicePort; this.moduleStatusService = moduleStatusService; } public void handle(final TenantModuleStatuses tenantModuleStatuses) { final TenantId tenantId = tenantModuleStatuses.tenantId(); final boolean exists = tenantDirectoryPort.tenantExists(tenantId); try { moduleStatusService.guardTenantExists(tenantId, exists); final List&lt;EnterpriseModuleStatus&gt; enterpriseModuleStatuses = registryModulePort.findByTenantId(tenantId); moduleStatusService.guardModuleExists(tenantModuleStatuses, enterpriseModuleStatuses); moduleStatusService.guardModuleIsEnterprise(tenantModuleStatuses, enterpriseModuleStatuses); final boolean statusHasChanged = moduleStatusRepositoryPort.save( moduleStatusService.toTenantModuleStatuses(tenantModuleStatuses, enterpriseModuleStatuses)); if (statusHasChanged) { log.debug("Invalidating ModuleCache for TenantId={}", tenantId); eventServicePort.invalidateModuleCacheForTenants(List.of(tenantId.toString())); } } catch (final TenantNotFoundDomainException ex) { log.warn("No tenant found for id '{}'", tenantId); throw ModuleStatusApiException.unknownTenant(tenantId); } catch (final ModuleNotFoundDomainException ex) { throw ModuleStatusApiException.noModuleForRegistryFound(tenantId, ex.getMissingModuleIds().toString()); } catch (final EnterpriseModuleNotFoundDomainException ex) { throw ModuleStatusApiException.nonEnterpriseModuleProvided(tenantId, ex.getMissingModuleIds()); } } } </code></pre> <p>最后我还注意到,这个 Use Case 的流程与 Jira Ticket 中的验收标准(Acceptance Criteria)非常接近。从另一个角度理解,Ticket 可以是 Implementation 的抽象,后者依赖于前者。</p> <h2>调用用例</h2> <p>在上述 Use Case 中,我们假设有两种方式调用它们:一种是使用 Java Interface,另一种是使用 Resource(即 HTTP Request)。</p> <p>Java Interface 最简单,只需要暴露 Use Case 类的接口即可,位置在 <code>application.port.inbound</code>。</p> <pre><code>package com.example.modulesystem.application.port.inbound; public interface ModuleStatusQueryPort { TenantModuleStatuses apply(@NonNull final TenantId tenantId); } </code></pre> <p>第二种方式稍微复杂一些。HTTP 请求属于一种技术实现,因此完全可以将它放进 Infrastructure 的 Inbound Adapter 中。它<strong>可以直接</strong>调用 Use Case 中的实现,因为它的职责就是处理边界的数据转换,并连接内部系统。</p> <p>代码如下:</p> <pre><code>package com.example.modulesystem.infrastructure.adapter.inbound.rest; @Slf4j @Path("/") @Component public class EnterpriseModuleStatusResource { private final UpdateModuleStatus updateModuleStatus; public EnterpriseModuleStatusResource(final UpdateModuleStatus updateModuleStatus) { this.updateModuleStatus = updateModuleStatus; } @PUT @Path("tenants/{tenantId}/provisioned") @Consumes({MediaType.APPLICATION_JSON}) public Response updateModuleStatusForTenants( @PathParam("tenantId") final TenantId tenantId, @Valid final TenantModuleStatusesRequest subscriptionStatusRequest) { log.info("Updating module statuses for TenantId: {}", tenantId); updateModuleStatus.handle(mapToDomainModel(tenantId, subscriptionStatusRequest.modules())); log.debug("Successfully updated module statuses"); return Response.ok().build(); } private static TenantModuleStatuses mapToDomainModel(final TenantId tenantId, final Map&lt;String, Boolean&gt; moduleStatusList) { final List&lt;TenantModuleStatus&gt; tenantModuleStatusModels = new ArrayList&lt;&gt;(); moduleStatusList.forEach((moduleId, isActive) -&gt; tenantModuleStatusModels.add(new TenantModuleStatus(ModuleId.of(moduleId), isActive))); return new TenantModuleStatuses(tenantId, tenantModuleStatusModels); } } </code></pre> <p>这里的 Mapper 是可选拆分,可以放在 Resource 中,也可以单独创建 Mapper。</p> <p>值得注意的是 <code>TenantModuleStatusesRequest</code>,作为与外部世界的协议(HTTP),它不应该放在 Application 或 Domain 中,因为 Adapter 采用的技术可能会发生变化(例如不再是 HTTP 而是 Kafka),而 Use Case 不应受到任何影响。</p> <pre><code>package com.example.modulesystem.infrastructure.adapter.inbound.rest.dto; public record TenantModuleStatusesRequest(Map&lt;String, Boolean&gt; modules) { } </code></pre> <p>这是相关的文件夹结构:</p> <pre><code>└── infrastructure └── adapter ├── inbound │ └── rest │ ├── EnterpriseModuleStatusResource.java -&gt; resource │ └── dto │ └── TenantModuleStatusesRequest.java -&gt; DTO └── outbound ├── mapper │ └── RegistryModuleToEnterpriseModuleStatusMapper.java ├── RegistryModuleAdapter.java ├── EventServiceAdapter.java └── TenantDirectoryAdapter.java </code></pre> <h2>总结与尾声</h2> <p>再回顾一下我们搭建的地图:</p> <pre><code>. ├── application │ ├── exception │ │ └── ModuleStatusApiException.java │ ├── port │ │ ├── inbound │ │ │ └── ModuleStatusQueryPort.java │ │ └── outbound │ │ ├── ModuleStatusRepositoryPort.java │ │ ├── RegistryModulePort.java │ │ ├── EventServicePort.java │ │ └── TenantDirectoryPort.java │ └── usecase │ ├── ModuleStatusQueryUseCase.java │ ├── ResetModuleStatusUseCase.java │ └── UpdateModuleStatus.java ├── domain │ ├── exception │ │ ├── EnterpriseModuleNotFoundDomainException.java │ │ ├── ModuleNotFoundDomainException.java │ │ └── TenantNotFoundDomainException.java │ ├── model │ │ ├── EnterpriseModuleStatus.java │ │ ├── TenantModuleStatuses.java │ │ └── TenantModuleStatus.java │ └── service │ └── ModuleStatusService.java └── infrastructure └── adapter ├── inbound │ └── rest │ ├── EnterpriseModuleStatusResource.java │ └── dto │ └── TenantModuleStatusesRequest.java └── outbound ├── mapper │ └── RegistryModuleToEnterpriseModuleStatusMapper.java ├── RegistryModuleAdapter.java ├── EventServiceAdapter.java └── TenantDirectoryAdapter.java </code></pre> <p>我们从最核心的 Domain 数据结构设计出发,配合 Repository 的 Port 设计出了两个简易的 Use Case。然后深入探讨了如何处理最复杂的 Case,包括引入外部数据、数据在边界及 Domain 内部的转换、如何处理 Exception,以及如何使用不同的方式触发 Use Case。</p> <p>当然,这仅仅是针对这个简单例子的复盘,实际开发中遇到的情况往往更加复杂。想对你们说,也同样是想对自己说的是:这绝对不是完美的解决方案,也不是适用所有情况的通用架构。其中繁杂的数据类型转换和过度工程化(Over-engineering)的问题依旧没有得到解决,反倒加剧了,这点看看上面的例子就能很快明白。</p> <p>当前,我将 Hexagonal/Clean Architecture 理解为对经典 DDD 的细化,它保留了纯净 Domain 的核心,区别在于从大而全的 Service 中分出了一部分职能给 Use Case。(个人觉得这个趋势有点像是从以功能技术开发为核心,转变为以快速适配不同用户的不同需求为核心)。</p> <p>利用 Interface 的特性,我们实现了依赖反转,依赖路径变成了 <strong>Infrastructure -&gt; Application -&gt; Domain</strong>。之前我只会关注 Interface 可以被 Implement,而没有关注它可以定义变量类型的特性。正是由于关注点的不同,控制权也悄然发生了转变。</p> <p>至于具体的项目而言,它依旧会非常庞大且古老,同时也几乎很难改变原始代码的结构。老式的 Service 依旧长存,新结构迟迟不会出现,但它的存在却会帮助我在脑海中树立一种清晰的架构意识,帮助我梳理并归类旧有的逻辑。</p> <blockquote> <p>免责声明: 代码示例已简化并通用化,仅用于教学目的,侧重于架构模式而非具体项目实现。</p> </blockquote>