YABOONG Flying Yaboong | Byte to Flight https://yaboong.github.io/ Thu, 19 Mar 2026 15:16:50 +0000 Thu, 19 Mar 2026 15:16:50 +0000 Jekyll v3.10.0 Low Wing Airplane Maneuver Tips - Steep Spiral <h2 id="intro">Intro</h2> <hr /> <ul> <li>Personally, I find the <strong>Steep Spiral</strong> to be one of <strong>the most challenging maneuvers</strong>.</li> <li>After plenty of trial and error in <strong>X-Plane</strong>, I’ve finally had a bit of a breakthrough.</li> <li>I’ve put together some <strong>insights</strong> and <strong>tips</strong> that I believe will make your training much more effective.</li> </ul> <!--more--> <h2 id="disclaimer">Disclaimer</h2> <blockquote> <p>⚠️ This is for educational and entertainment purposes only. I am not a certified flight instructor (CFI). Do not use this as a substitute for professional flight training. Always consult a CFI and follow your aircraft’s POH. I accept no liability for any actions taken based on this content.</p> </blockquote> <h2 id="tldr">TL;DR</h2> <p>Short on time? Just watch this.</p> <iframe width="560" height="315" src="https://www.youtube.com/embed/pjVw_VWNGiI?start=247" frameborder="0" allowfullscreen=""> </iframe> <ul> <li><strong>GS &lt; 80</strong>: Use a <strong>30° bank</strong>. (Don’t go much lower than this, or the radius gets too wide.)</li> <li><strong>GS = 90–95</strong>: Stick to the <strong>45° baseline</strong>.</li> <li><strong>GS &gt; 110</strong>: Increase to a <strong>55° bank</strong> (But remember, <strong>never exceed 60°</strong>).</li> </ul> <blockquote> <p>More GS, More Bank, More Back Pressure<br /> Less GS, Less Bank, Less Back Pressure</p> </blockquote> <h2 id="why-the-steep-spiral-is-so-challenging">Why the Steep Spiral is So Challenging</h2> <p>At first, I thought it was just a combination of <strong>Turns Around a Point</strong> and an <strong>Emergency Descent</strong>—both maneuvers I practiced during my Private Pilot training. I figured I just had to keep the wingtip on the reference point while descending rapidly, all while staying within bank and airspeed limits. It seemed simple enough in my head, but <strong>the reality was quite different</strong>.</p> <h4 id="1-how-much-bank-is-enough">1. How much bank is enough?</h4> <p>The ACS states, “<em>Establish and maintain a steep spiral, not to exceed 60° angle of bank.</em>” But does that mean <strong>anything under 60°</strong> is fine? Is 45° steep enough? What about 35° or 20°? The real challenge is <strong>maintaining</strong> a <strong>constant radius</strong> about a ground reference point. Finding that perfect balance between a specific bank angle and a consistent radius was personally the hardest part for me.</p> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-13/ACS-steep-spiral.png" target="_blank"> <img src="/posts_image/2026-02-13/ACS-steep-spiral.png" alt="ACS Steep Spiral" style="height:; width:" /> </a> <figcaption class="caption-text">ACS Steep Spiral</figcaption> </figure> <h4 id="2-what-about-the-airspeed">2. What about the airspeed?</h4> <p>The ACS allows a margin of ±10 knots, but <strong>±10 knots from what exactly?</strong> Best glide speed? maneuvering speed? or just 90kts? 100kts?</p> <h4 id="3-time-and-cost">3. Time and Cost</h4> <p>The ACS requires <strong>completing the maneuver no lower than 1,500 feet AGL</strong>, and since you have to perform three full 360-degree turns, you need plenty of altitude. Even with a conservative estimate of 1,000 feet per turn, you need to start at least at 4,500 feet AGL. My instructor prefers starting at 5,500 feet MSL (airport elevation 120ft MSL) for an extra margin of safety. Because you have to climb so high for every single attempt, this maneuver <strong>consumes significantly more time and money</strong> compared to others.</p> <h4 id="4-different-perspectives-left-vs-right-seat">4. Different Perspectives (Left vs. Right Seat)</h4> <p>The <strong>view</strong> of the ground reference point is completely <strong>different</strong> for the <strong>student in the left seat</strong> and the <strong>instructor in the right</strong>. Because of these different angles, it’s hard for the instructor to give precise advice based on exactly what I’m seeing at that moment.</p> <h4 id="5-the-low-wing-struggle">5. The “Low Wing” Struggle</h4> <p>In a <strong>low-wing</strong> aircraft, the wing often <strong>blocks the very ground reference</strong> you’re trying to follow. Maintaining visual contact throughout the entire 360-degree turn is much more difficult than it looks.</p> <h4 id="6-coriolis-illusion">6. Coriolis Illusion</h4> <p>During a steady steep turn, the fluid in your inner ear stabilizes. If you <strong>suddenly move your head</strong> to check your GS on the instruments and then look back out at the reference point, it can trigger the <strong>Coriolis Illusion</strong>.</p> <h4 id="7-constantly-shifting-winds">7. Constantly Shifting Winds</h4> <p>Not only does the wind direction relative to the nose change as you turn, but the <strong>wind speed and direction also shift as you descend to lower altitudes</strong>. You have to account for these changes constantly.</p> <h4 id="8-everything-happens-so-fast">8. Everything Happens So Fast</h4> <p>As always in aviation, <strong>everything happens in a flash</strong>. There are so many variables changing at once that if you miss a small correction window, the whole maneuver can fall apart before you know it.</p> <h2 id="airplane-flying-handbook">Airplane Flying Handbook</h2> <blockquote> <p>Update: This section has been updated based on feedback from a reader. <br /> Thanks to “<em>SeoulCitizen</em>”! (See what’s updated: <a target="_blank" href="https://github.com/yaboong/yaboong.github.io/commit/ee0d373c7cab7bb43412ef58e41884075e2b8601">ee0d373</a>)</p> </blockquote> <p>Let’s look at what the <strong>FAA Airplane Flying Handbook (AFH)</strong> says about the <strong>Steep Spiral</strong>.</p> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-13/AFH-10-3-steep-spiral.png" target="_blank"> <img src="/posts_image/2026-02-13/AFH-10-3-steep-spiral.png" alt="Airplane Flying Handbook 10-3" style="height:; width:" /> </a> <figcaption class="caption-text">Airplane Flying Handbook 10-3</figcaption> </figure> <p>The handbook uses key phrases such as “<em>rapidly dissipating substantial amounts of altitude</em>”, “<em>remaining over a selected spot</em>”, and “<em>useful during an emergency landing.</em>” While these describe the maneuver’s purpose, the execution details can still feel a bit vague.</p> <p>Specifically, the handbook mentions the following regarding the setup:</p> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-13/AFH-10-4-steep-spiral-setup.png" target="_blank"> <img src="/posts_image/2026-02-13/AFH-10-4-steep-spiral-setup.png" alt="Airplane Flying Handbook 10-4" style="height:; width:" /> </a> <figcaption class="caption-text">Airplane Flying Handbook 10-4</figcaption> </figure> <blockquote> <p>Although “<em>gliding</em>” is mentioned multiple times (e.g., “<em>gliding turn</em>” “<em>gliding speed is established</em>”), I don’t believe this implies the entire maneuver must be flown strictly at the best glide speed (Vg).</p> </blockquote> <p>Since the text says “<em>once the proper airspeed is attained, the pitch should be lowered and the airplane rolled…</em>” after mentioning the initial glide setup, I interpret “<em>gliding speed</em>” as part of the <strong>initial entry procedure rather than a strict requirement to maintain that exact airspeed throughout the entire steep spiral.</strong></p> <h4 id="how-steep-is-steep">How Steep is “Steep”?</h4> <p>How exactly do we define a “<strong>steep</strong>” turn? The <strong>Airplane Flying Handbook</strong> provides a very clear definition (<em>thanks again to “SeoulCitizen” for pointing this out</em>).</p> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-13/AFH-3-12-turn-bank-degree.png" target="_blank"> <img src="/posts_image/2026-02-13/AFH-3-12-turn-bank-degree.png" alt="Airplane Flying Handbook 3-12" style="height:; width:" /> </a> <figcaption class="caption-text">Airplane Flying Handbook 3-12</figcaption> </figure> <p>According to the <strong>AFH</strong>, “<em>turns are divided into three classes: shallow, medium, and steep</em>” where “<em>Steep turns—result from a degree of <strong>bank of approximately 45° or more.</strong></em>” Therefore, for a maneuver to be called a <strong>Steep Spiral</strong>, it must involve a bank angle of <strong>at least approximately 45°</strong>.</p> <h2 id="so-what-are-the-exact-numbers">So, What Are the Exact Numbers?</h2> <p>I’ve complained quite a bit, but that’s just because I truly believe the Steep Spiral is one of the trickiest maneuvers out there. At the end of the day, the goal is to find the “sweet spot” for bank angle and airspeed, while figuring out how to handle wind correction. I went through a lot of trial and error in X-Plane to find these numbers.</p> <blockquote> <p>⚠️ Keep in mind, these aren’t absolute values—they will change depending on the situation.</p> </blockquote> <h4 id="reasoning-behind-airspeed-90-kias-and-why-vg-might-not-be-ideal">Reasoning behind Airspeed: 90 KIAS (and why Vg might not be ideal)</h4> <p>While some suggest using Best Glide Speed (Vg), I found it a bit risky for this maneuver. In a PA-28-161, Vg is around 73 KIAS. However, at a <strong>60° bank</strong>, your <strong>stall speed increases by about 41%</strong>. <strong>Vs1</strong> for PA-28-161 is <strong>51</strong>, and if you maintain a 60° bank while maintaining Vg (73), you’re gonna be only 1 knot above (51*1.41=72) the stall speed. That’s why I prefer <strong>90 KIAS</strong> (within the Va range of 88-111) to Vg. And the reason why I didn’t choose 100KIAS is, 100 is too close to the maximum maneuvering speed 111, even though it depends on the weight of the aircraft. Therefore, I believe 90 KIAS is the sweet spot for a steep spiral.</p> <ul> <li><strong>Safety</strong>: It provides a much <strong>better stall margin</strong>, especially when you’re pulling steep banks.</li> <li><strong>Stability</strong>: The aircraft feels more <strong>stable</strong> and <strong>responsive</strong> to control inputs, even if you encounter sudden gusts.</li> </ul> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-13/AFH-5-9-AoA-60-bank-stall-speed-41-percent.png" target="_blank"> <img src="/posts_image/2026-02-13/AFH-5-9-AoA-60-bank-stall-speed-41-percent.png" alt="Airplane Flying Handbook 5-9" style="height:; width:" /> </a> <figcaption class="caption-text">Airplane Flying Handbook 5-9</figcaption> </figure> <h4 id="bank-angle-strategy-45-as-the-baseline">Bank Angle Strategy: 45° as the Baseline</h4> <p>Through my practice, I found that <strong>45°</strong> is a great <strong>baseline</strong>—but only if there’s <strong>no wind</strong>. To maintain a constant radius, you have to adjust based on your Ground Speed (GS):</p> <ul> <li>Approaching <strong>Tailwind</strong>: Your <strong>GS increases</strong>. You need a <strong>steeper bank</strong> here, or your turn radius will balloon out, taking you away from your reference point.</li> <li>Approaching <strong>Headwind</strong>: Your <strong>GS decreases</strong>. You need a <strong>shallower bank</strong> to prevent the circle from “pancaking” or getting too tight.</li> </ul> <h4 id="downwind-entry---wouldnt-headwind-be-easier">Downwind Entry - Wouldn’t Headwind be easier?</h4> <p>While a headwind entry might seem more “controlled” due to the slower initial ground speed (GS), it often leads to a common trap. I eventually realized that starting from the downwind (tailwind) leg is the most strategic way to stay within ACS limits.</p> <ul> <li><strong>The Problem with Headwind Entry</strong>: Starting with a headwind requires a shallow bank initially. However, as you turn toward the tailwind side, your GS increases rapidly. If the wind is strong, you may be forced to exceed a 60° bank just to maintain a constant radius—instantly busting the ACS limit</li> <li><strong>The Logic of Tailwind Entry</strong>: Entering downwind forces you to establish your <strong>steepest bank angle at the very beginning</strong>. By “setting the ceiling” (up to 60°) when your GS is at its peak, the rest of the maneuver becomes a matter of gradually shallowing out the bank as you turn into the wind. It eliminates the risk of needing “more bank than you have left” halfway through the turn.</li> </ul> <h2 id="the-simulation-x-plane">The Simulation (X-Plane)</h2> <p>Since I can’t just go up and fly 50 times in the real world, I used X-Plane. Initially, I just wanted to see what the ground reference should look like from the cockpit, but I got a little obsessed… and here we are. In the video below, I’ll show you three scenarios:</p> <ol> <li><strong>Wind Calm</strong>: How a constant <strong>45°</strong> bank and <strong>90-95 KIAS</strong> creates a perfect circle.</li> <li><strong>Wind 180@20kts</strong> &amp; <strong>No Correction</strong>: What happens to your circle when you hold a steady 45° bank without accounting for the wind.</li> <li><strong>Wind 180@20kts</strong> &amp; <strong>With Correction</strong>: My experimental results on how to achieve that “constant radius” while staying <strong>within the 60° ACS limit</strong>.</li> </ol> <iframe width="560" height="315" src="https://www.youtube.com/embed/pjVw_VWNGiI?start=" frameborder="0" allowfullscreen=""> </iframe> <h3 id="rule-of-thumb-based-on-gs">Rule of Thumb (based on GS)</h3> <ul> <li><strong>GS &lt; 80</strong>: Use a <strong>30° bank</strong>. (Don’t go much lower than this, or the radius gets too wide.)</li> <li><strong>GS = 90–95</strong>: Stick to the <strong>45° baseline</strong>.</li> <li><strong>GS &gt; 110</strong>: Increase to a <strong>55° bank</strong> (But remember, <strong>never exceed 60°</strong>).</li> </ul> <blockquote> <p>More GS, More Bank, More Back Pressure<br /> Less GS, Less Bank, Less Back Pressure</p> </blockquote> <h3 id="why-back-pressure-matters">Why Back Pressure Matters</h3> <p>Wait, why did I mention <strong>back pressure</strong>? It’s all about maintaining that target airspeed (90-95 KIAS) while your bank angle constantly changes.</p> <p>When <strong>GS increases</strong> (<strong>Tailwind</strong>):</p> <blockquote> <p>As you <strong>increase</strong> the <strong>bank</strong> to keep the radius constant, you <strong>lose the vertical component of lift</strong>. This causes the <strong>nose</strong> to <strong>drop</strong>, and your <strong>airspeed</strong> starts to <strong>bleed upward</strong>. To stay within the 90-95 KIAS range, you need to apply more back pressure to hold the pitch.</p> </blockquote> <p>When <strong>GS decreases</strong> (<strong>Headwind</strong>):</p> <blockquote> <p>As you <strong>shallow</strong> out the <strong>bank</strong>, the <strong>vertical lift increases</strong>. If you keep the same pitch, the <strong>nose will rise</strong> and your <strong>airspeed will drop</strong>. To maintain your target speed, you need to relax the elevator—less back pressure (or even a slight forward pressure)—to keep the nose down.</p> </blockquote> <h3 id="track-log">Track Log</h3> <p>It might not be perfect, but it gets the job done.</p> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-13/tracklog-google-earth-side.png" target="_blank"> <img src="/posts_image/2026-02-13/tracklog-google-earth-side.png" alt="Track Log on Google Earth (Side View)" style="height:; width:" /> </a> <figcaption class="caption-text">Track Log on Google Earth (Side View)</figcaption> </figure> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-13/tracklog-google-earth-top.png" target="_blank"> <img src="/posts_image/2026-02-13/tracklog-google-earth-top.png" alt="Track Log on Google Earth (Top View)" style="height:; width:" /> </a> <figcaption class="caption-text">Track Log on Google Earth (Top View)</figcaption> </figure> Fri, 13 Feb 2026 00:00:00 +0000 https://yaboong.github.io/airplane-maneuvers/2026/02/13/low-wing-airplane-maneuver-tips-commercial-CPL-maneuver-steep-spiral/ https://yaboong.github.io/airplane-maneuvers/2026/02/13/low-wing-airplane-maneuver-tips-commercial-CPL-maneuver-steep-spiral/ CPL commercial-pilot commercial-maneuver steep-spiral airplane-maneuvers Commercial Operations: From Confusion to Clarity - Part 119 & 119.1(e) <h2 id="intro">Intro</h2> <hr /> <ul> <li>The topic I underestimated the most was Commercial Operations.</li> <li>I soon realized that concepts like Private vs. Common Carriage, Wet vs. Dry Leases, Holding Out, and the complex web of regulations—Part 91, 119, 119.1(e), 121, 125, and 135—are deeply interconnected and very confusing.</li> <li>I’ve put a lot of effort into unraveling these tangled concepts to truly understand how they fit together.</li> <li>In this post, I’m sharing my findings and a simplified Decision Roadmap to help fellow pilots navigate these ‘gray areas’ with clarity.</li> </ul> <!--more--> <h2 id="disclaimer">Disclaimer</h2> <blockquote> <p>⚠️ This content is for educational and informational purposes only. It does not constitute legal advice or an official FAA guideline. Always refer to the latest FAR/AIM and official regulations for actual flight operations. The author assumes no liability for errors or omissions in the content.</p> </blockquote> <h2 id="opening-thoughts">Opening Thoughts</h2> <ul> <li>There are many ways to make money in the aviation industry. You could earn a living as a pilot, or make a profit by leasing your aircraft to others. You might even own the plane and fly it yourself for business. Another option is providing a full private jet service, where you supply both the aircraft and the pilot while managing the plane for the client.</li> <li>On the other hand, not all flights are for profit. You might fly for instruction, skydiving, or simply for fun. Some flights provide essential medical services to people in remote areas during emergencies.</li> <li>With so many different types of operations, you can’t just let everyone fly however they want. Without clear rules (even though it’s not very clear), there would be no legal standards to follow when accidents or issues occur. It’s not just about punishment; it’s about establishing safety regulations by law.</li> <li>Realistically, however, you cannot apply the same safety standards and regulations to someone flying alone for fun as you would to a private jet operator or a commercial airline carrying 300 paying passengers.</li> <li>This is why aviation law is naturally complex and, like anything made by humans, imperfect.</li> <li>Please see this writing as my own personal effort to make sense of these complicated regulations.</li> </ul> <h2 id="case-table">Case Table</h2> <hr /> <blockquote> <p>‼️ The table below is the key takeaway from this post ‼️<br />(Click on the image for a larger view)</p> </blockquote> <p>If the table below seems a bit confusing, I highly recommend reading through the <strong>Terms &amp; Definitions</strong> and <strong>Key Takeaways</strong> first. At the end of this post, I will walk you through each case in the table step by step to make sure everything is clear.</p> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-07/case-table-v3.png" target="_blank"> <img src="/posts_image/2026-02-07/case-table-v3.png" alt="Case Table" style="height:; width:" /> </a> <figcaption class="caption-text">Case Table</figcaption> </figure> <h2 id="terms--definitions">Terms &amp; Definitions</h2> <hr /> <ul> <li><strong>Compensation</strong>: <strong>Anything of value</strong> received for acting as PIC (not limited to cash) <ul> <li>Financial Gain: Direct payment (cash, salary).</li> <li>Flight Time: Building hours for free while someone else pays for the aircraft.</li> <li>Barter &amp; Gifts: Trading services for goods (e.g., free dinner, repairs).</li> <li>Intangible Benefits: Business goodwill or professional reputation gain.</li> <li>If you are better off (financially or professionally) after the flight, the FAA considers it Compensation.</li> </ul> </li> <li><strong>Hire</strong>: Air <strong>transport</strong> is the <strong>main</strong> business for <strong>profit</strong>, <strong>not</strong> <strong>incidental</strong> <ul> <li>Pilot for Hire: Selling labor/skills for compensation.</li> <li>Aircraft for Hire: Using the aircraft as a revenue-generating vehicle. <ul> <li>Requires a 100-hour inspection (91.409b) in addition to the Annual.</li> <li>An Operator must be identified as legally responsible for the flight.</li> <li>Usually requires a Part <strong>119</strong> Certificate (Part 135/121) unless it falls under Part 119.1(e) exceptions.</li> </ul> </li> </ul> </li> <li><strong>Holding out</strong>: An expression of <strong>willingness</strong> to the <strong>general public</strong> to <strong>provide</strong> air <strong>transportation</strong> for <strong>compensation</strong>.</li> <li><strong>Private carriage</strong>: Carriage <strong>for</strong> <strong>hire</strong> which does <strong>not</strong> involve “<strong>holding out</strong>” is private carriage.</li> <li><strong>Common carriage</strong>: (1) A <strong>holding out</strong> of a willingness to (2) <strong>transport</strong> persons or property (3) from <strong>place to place</strong> (4) for <strong>compensation</strong>.</li> <li><strong>Operational control</strong>: The authority to <strong>initiate</strong>, <strong>conduct</strong>, and <strong>terminate</strong> a flight.</li> <li><strong>Operator</strong>: the person or entity who is legally <strong>responsible</strong> for the safe conduct of the flight and <strong>exercises operational control</strong>.</li> <li><strong>Dry lease</strong>: A leasing arrangement where only the <strong>aircraft</strong> is <strong>provided</strong> <strong>without</strong> a <strong>crew</strong>; the <strong>lessee has operational control</strong> and acts as the operator.</li> <li><strong>Wet lease</strong>: A leasing arrangement where <strong>both</strong> the <strong>aircraft</strong> and <strong>at least one crew</strong> member are provided; the <strong>lessor typically retains operational control</strong> and acts as the operator.</li> <li><strong>Air carrier certificate</strong>: A certificate required for operators conducting <strong>common carriage</strong> (e.g., Part 121 airlines or Part 135 on-demand air taxis).</li> <li><strong>Operating certificate</strong>: a certificate required for operators conducting <strong>private carriage</strong> for compensation or hire (e.g., Part 125 or Part 135 <strong>management companies</strong>).</li> <li><strong>14 CFR</strong> <ul> <li><strong>part 119</strong>: Regulation that determines who must obtain a certificate and which operating rules (Part 121, 125, or 135) apply.</li> <li><strong>part 119.1.(e)</strong>: List of commercial operations (such as student instruction, crop dusting, and banner towing) that are exempt from commercial certification and allowed to operate under Part 91.</li> <li><strong>part 91</strong>: General operating and flight rules (basic rules for all aircraft). It covers non-commercial flights, corporate private operations, and the specialized operations listed in 119.1(e).</li> <li><strong>part 121/135:</strong> Regulations for commercial operators conducting Common or Private Carriage for compensation or hire.</li> <li><strong>part 125</strong>: Special regulations for the private operation of large aircraft (20+ seats or 6,000 lbs+ payload) that do not involve common carriage. (→ “you’re part 91 but too big, I want to manage you more strictly”)</li> </ul> </li> </ul> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-07/AC-120-12A-common-carriage.png" target="_blank"> <img src="/posts_image/2026-02-07/AC-120-12A-common-carriage.png" alt="AC 120-12A - Common Carrier" style="height:; width:" /> </a> <figcaption class="caption-text">AC 120-12A - Common Carrier</figcaption> </figure> <h2 id="key-takeaways">Key Takeaways</h2> <hr /> <ul> <li>Purpose of the certificate <ul> <li><strong>Purpose</strong> of the certificate is to <strong>strictly manage</strong> the operator who <strong>carries</strong> the <strong>general public</strong> or others for <strong>compensation</strong>.</li> <li>Private operation is not a commercial activity (carriage). Since the aircraft is used for own business or personal purposes, it is sufficient to just follow Part 91 flight rules without the need for a separate certificate.</li> </ul> </li> <li>Things possible <strong>without</strong> a <strong>certificate</strong> (air carrier / operating): <ul> <li>Pilot skill can be provided. Providing only pilot labor is ‘employment’, not an ‘air transportation service’. Operational control remains with the aircraft owner, not the pilot.</li> <li>Aircraft can be provided (dry lease). The lessee becomes the operator. Part (91/135/121/125) applies depends on how the lessee uses the aircraft.</li> <li>↔ <strong>Providing both</strong> “pilot skill + aircraft together (wet lease)” → <strong>Certificate</strong> (air carrier / operating) is <strong>required</strong>.</li> </ul> </li> <li><strong>Carriage</strong> vs non-carriage <ul> <li>The key to determining whether it is carriage or not is the <strong>lease type (dry/wet)</strong>, which means “<strong>who has operational control</strong>”.</li> <li><strong>Dry</strong> lease: The <strong>lessee</strong> has control → non-carriage (Part 91 possible).</li> <li><strong>Wet</strong> lease: The <strong>lessor/contractor</strong> has control → carriage (Part 135/125/121).</li> </ul> </li> <li><strong>Private</strong> carriage vs <strong>Common</strong> carriage <ul> <li>Key difference between private and common carriage is <strong>holding out</strong>.</li> <li><a href="https://www.faa.gov/documentLibrary/media/Advisory_Circular/AC%20120-12A.pdf">AC 120-12A</a> states “<em>(1) a <strong>holding out</strong> of a willingness to (2) <strong>transport</strong> persons or property (3) from <strong>place to place</strong> (4) for <strong>compensation</strong>.</em>”</li> </ul> </li> <li><strong>Common</strong> carriage <strong>always</strong> requires an <strong>air carrier certificate</strong>.</li> <li><strong>Part 125</strong> is focused more on “<strong>safety management for large aircraft</strong>” rather than being a “commercial transportation business” itself.</li> <li>In the event of an <strong>accident</strong>, legal <strong>responsibility</strong> lies with the person or entity that holds <strong>operational Control</strong>.</li> </ul> <h2 id="case-table---break-down">Case Table - Break Down</h2> <hr /> <blockquote> <p>‼️ The table below is the key takeaway from this post ‼️<br />(Click on the image for a larger view)</p> </blockquote> <p>Based on the <strong>Definitions</strong>, and <strong>Key Takeaways</strong> we’ve covered, let’s take another look at the table below.</p> <p>The <strong>main premise</strong> is this: providing just the pilot’s skills is allowed, and providing just the aircraft is allowed. However, <strong>providing both</strong> together is <strong>not possible without a certificate</strong> (either an Air Carrier or Operating Certificate).</p> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-07/case-table-v3.png" target="_blank"> <img src="/posts_image/2026-02-07/case-table-v3.png" alt="Case Table" style="height:; width:" /> </a> <figcaption class="caption-text">Case Table</figcaption> </figure> <h5 id="vary---14-cfr-1191e">Vary - <a href="https://www.ecfr.gov/current/title-14/part-119#p-119.1(e)">14 CFR 119.1(e)</a></h5> <ul> <li>There are various cases, such as flight instruction, banner towing, aerial photography, crop dusting, sightseeing, and parachute jumping.</li> <li>For these operations, I have marked Lease Type and Operational Control as <strong>Depends</strong>.</li> <li>This is because the scenarios vary greatly: an instructor might use their own plane or one owned by a flight school; a skydiving company might provide both the aircraft and the pilot; or a farm owner might use their own plane and simply hire a pilot for crop dusting.</li> <li>Because of this wide variety, Part 119.1(e) does not specify a single standard for these cases.</li> <li>In short, if an operation falls under these exemptions, it is not restricted by the usual regulations, regardless of whether there is <strong>holding out</strong> or if the flight is <strong>for compensation or hire</strong>.</li> </ul> <h5 id="corporate-jet-small--large">Corporate Jet (Small / Large)</h5> <ul> <li>In both cases (Small and Large), the company must own the aircraft and maintain <strong>Operational Control</strong> by using a <strong>Dry Lease</strong> structure. <ul> <li>A prime example is <strong>Mark Cuban</strong>, the owner of the Dallas Mavericks, who owns his own Boeing 757/767 and uses it for team operations.</li> </ul> </li> <li>The company can either hire its own crew directly or outsource the practical management to an <strong>Aircraft Management Company</strong>. <ul> <li>In the latter case, the management company only provides services like supplying pilots, purchasing fuel, and handling maintenance.</li> </ul> </li> <li>The applicable regulations depend on the aircraft size: <ul> <li>owning a <strong>Pilatus PC-12</strong> falls under <strong>Part 91</strong>, while owning a <strong>Boeing 737</strong> falls under <strong>Part 125</strong>.</li> <li>If the aircraft is large enough to be under <strong>Part 125</strong>, an <strong>Operating Certificate</strong> is required.</li> </ul> </li> <li><strong>Two Critical Points to Remember</strong> <ul> <li><strong>No “Holding Out”:</strong> It is illegal for a company to <strong>provide</strong> flight services to the <strong>general public</strong> using its private jet when it’s not in use. To earn revenue from an idle aircraft, the owner typically <strong>dry leases</strong> the plane to a company like Executive Jet Management (EJM). EJM then provides that aircraft to clients via a <strong>wet lease</strong> and shares the profit. In this scenario, the company providing the wet lease (like EJM) must hold a Part 135 Air Carrier Certificate.</li> <li><strong>Must be “Incidental to Business”:</strong> The primary purpose of the flight must be the business itself, with the aircraft serving merely as a means of transportation for that business. For example, if a friend of the company CEO uses the jet for a vacation, it is illegal. This is because the flight is no longer incidental to business; instead, the “place-to-place transportation” has become the primary purpose of the flight.</li> </ul> </li> </ul> <h5 id="gray-area">#GRAY AREA</h5> <ul> <li>This is the most <strong>controversial</strong> area and one that the <strong>FAA monitors very closely</strong>.</li> <li>The key factor here is whether the operation qualifies as <strong>“Common Carriage”</strong> as defined in <strong>AC 120-12A</strong>.</li> <li>If you are a pilot who owns an aircraft and provides transportation services via a <strong>wet lease</strong> for <strong>compensation</strong>, it can still fall under <strong>Part 91</strong> as long as there is no <strong>holding out.</strong> <ul> <li>This is possible if the customer finds you first or if the transportation stems from a very specific, pre-existing business relationship.</li> <li>A typical example would be: A CPL <strong>pilot</strong> with their <strong>own</strong> <strong>SR22</strong> providing long-term travel services exclusively for <strong>1–2 specific business</strong> partners under signed contracts.</li> <li>This is a case where the boundaries are <strong>not clearly defined</strong>, making it a “<strong>gray area</strong>” that is easily exploited.</li> </ul> </li> <li>Operating under <strong>Part 135</strong> requires much stricter and more expensive standards for pilot training, maintenance intervals, and insurance compared to <strong>Part 91</strong>. Some operators try to bypass these costs by running a Part 91-style operation while generating revenue like a Part 135 carrier.</li> <li>Regarding <strong>Private Carriage,</strong> <strong>AC 120-12A</strong> mentions that the FAA generally considers having <strong>18 to 24 or more contracts</strong> as a sign that you have effectively opened your services to the public. On the other hand, it suggests that having up to <strong>3 contracts</strong> is generally acceptable. <ul> <li>So, what about having between <strong>4 and 17 contracts</strong>?</li> <li>We have to accept that because laws are created by humans, they are inherently imperfect and <strong>some</strong> level of <strong>ambiguity</strong> is <strong>unavoidable</strong>.</li> </ul> </li> </ul> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-07/AC-120-12A-private-carriage.png" target="_blank"> <img src="/posts_image/2026-02-07/AC-120-12A-private-carriage.png" alt="AC 120-12A - Private Carrier" style="height:; width:" /> </a> <figcaption class="caption-text">AC 120-12A - Private Carrier</figcaption> </figure> <h5 id="aircraft-management-company">Aircraft Management Company</h5> <ul> <li>This case applies when an NBA team or a corporation wishes to operate a <strong>private jet without owning</strong> it, choosing instead to purchase <strong>transportation services</strong> from a company like <strong>EJM (Executive Jet Management)</strong>.</li> <li>Since the provider supplies both the crew and the aircraft as a package (<strong>Wet Lease</strong>), receives compensation, engages in <strong>holding out,</strong> and provides <strong>place-to-place transportation</strong>, it is classified as <strong>Common Carriage</strong>. This is still true even if tickets are not sold to the general public. (Reference: <strong>AC 120-12A</strong>)</li> <li><strong>Small Aircraft?</strong> → <strong>Part 135</strong> applies.</li> <li><strong>Large Aircraft?</strong> → In this case, you should not assume it falls under <strong>Part 125</strong> just because it is a large aircraft. The purpose of <strong>Part 125</strong> is to enforce stricter safety management on large aircraft that would otherwise be operated under Part 91. In this scenario, a <strong>Part 135 Air Carrier Certificate</strong> is required, and the operation must comply with the specific regulations for large aircraft within <strong>Part 135</strong>.</li> </ul> <h5 id="charter-company">Charter Company</h5> <ul> <li>An <strong>on-demand</strong> service (e.g., Tradewind Aviation) where anyone from the <strong>general public</strong> can book a seat for a weekend trip to Martha’s Vineyard.</li> <li>These companies engage in <strong>holding out</strong> by advertising their services to the <strong>public</strong> and operating based on individual customer requests.</li> </ul> <h5 id="airline">Airline</h5> <ul> <li>Large-scale carriers that operate on <strong>fixed routes</strong> and pre-determined schedules under <strong>Part 121</strong>.</li> <li>They are the most prominent example of <strong>holding out</strong> to the <strong>general public</strong>, providing <strong>scheduled</strong> transportation services to anyone willing to purchase a ticket.</li> </ul> <h2 id="gray-area-cases-not-mentioned-in-the-table">Gray Area Cases (Not Mentioned in the Table)</h2> <blockquote> <p><strong>Case 1: A corporate jet pilot flying the CEO’s family to the Bahamas</strong></p> </blockquote> <ul> <li>This is <strong>not Part 91</strong> operation because the flight is <strong>not incidental to business</strong>.</li> <li>Since the flight’s purpose is personal rather than business-related, it should technically fall under <strong>Part 135</strong> standards.</li> </ul> <blockquote> <p><strong>Case 2: If ‘L’ is the lessor, ‘P’ is the pilot, and ‘C’ is the client—can ‘L’ lease the plane to ‘C’ and also recommend ‘P’?</strong></p> </blockquote> <ul> <li><strong>No.</strong> The moment the lessor recommends a pilot, the arrangement is viewed as a <strong>Wet Lease</strong>.</li> <li>This brings it into the <strong>Part 135</strong> category, and without an Operating Certificate, it becomes an “illegal charter.”</li> </ul> <blockquote> <p><strong>Case 3: ‘L’ leases the plane to ‘C’ without recommending a pilot, but ‘C’ already knows ‘P’ and hires them independently.</strong></p> </blockquote> <ul> <li>While the end result looks the same as the previous case, it can be operated under <strong>Part 91</strong> because ‘L’ provided no information about ‘P’.</li> <li>There must be <strong>absolutely no professional link or connection</strong> between the aircraft provider and the pilot provider.</li> </ul> <blockquote> <p><strong>Case 4: Can a company provide a business jet via a wet lease but let the client keep Operational Control?</strong></p> </blockquote> <ul> <li><strong>No.</strong> It is a fundamental legal principle that the moment a <strong>Wet Lease</strong> agreement is made, <strong>Operational Control</strong> (the responsibility for flight safety) shifts to the service provider.</li> <li>Therefore, the provider must hold a <strong>Part 135 (or 121) Operating Certificate</strong>.</li> </ul> <blockquote> <p><strong>Case 5: Are there cases of providing a wet lease under Part 91/125 (Private Carriage) while also “holding out”?</strong></p> </blockquote> <ul> <li><strong>Legally, this cannot exist.</strong> However, companies like <strong>Executive Jet Management (EJM)</strong> appear to operate this way.</li> <li>The reason they can is that they actually hold the necessary <strong>Part 135 certificates</strong>, allowing them to legally bridge that gap.</li> </ul> Thu, 05 Feb 2026 00:00:00 +0000 https://yaboong.github.io/far/aim/2026/02/05/FAA-commercial-operations-decision-roadmap-part-119-119-1-(e)/ https://yaboong.github.io/far/aim/2026/02/05/FAA-commercial-operations-decision-roadmap-part-119-119-1-(e)/ FAR/AIM part-119 part-119-1-(e) part-121 part-125 part-135 commercial-operation commercial-pilot FAR/AIM Post-Flight Analysis: Near Engine Failure (RPM Gauge Anomaly) <h2 id="intro">Intro</h2> <blockquote> <p><strong>What would you do if your RPM gauge started fluctuating at 1,000 RPM during a full-power climb at night?</strong></p> </blockquote> <p>I recently faced this exact scenario on an IFR flight. Although I chose a precautionary landing over declaring an emergency, the experience revealed some critical gaps in my ADM. Here is a raw debrief of what happened during my night flight and what I learned.</p> <!--more--> <h2 id="about-the-flight">About the Flight</h2> <ul> <li><strong>Date</strong>: Feb 5th, 2026</li> <li><strong>Time</strong>: Night Operation (Sunset 17:30, Takeoff 17:58, Land 18:05)</li> <li><strong>Weather</strong>: VFR (Wind calm, Vis 10+, Sky clear, Temp -10℃, Altimeter 29.92)</li> <li><strong>Flight Plan</strong>: IFR Filed</li> <li><strong>CRAFT</strong>: 7B2 KEYNN MANCH KASH, 3000ft/5000ft 10minute, Bradley Approach 125.35, Squawk 5304</li> <li><strong>PIC</strong>: Me, ASEL 216.4, PIC 139.4, Night PIC 23.5, INST (real 1.9, hood 51.2), XC 111.4</li> </ul> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-05/track-log-flight-aware.png" target="_blank"> <img src="/posts_image/2026-02-05/track-log-flight-aware.png" alt="Flight Detail (FlightAware)" style="height:; width:" /> </a> <figcaption class="caption-text">Flight Detail (FlightAware)</figcaption> </figure> <h2 id="️-the-incident">⚠️ The Incident</h2> <ul> <li><strong>Situation</strong>: Contacted Approach after departure, cleared to climb and maintain 5,000ft</li> <li><strong>RPM gauge anomaly</strong>: While climbing through 2,300’ (approx. 3NM Northeast of 7B2), I noticed an anomaly on the RPM gauge. Despite being at full throttle and mixture, the needle was bouncing erratically between 1,000 and 1,500 RPM.</li> <li><strong>Immediate Assessment</strong>: check fuel tanks, engine gauges, full mixture/throttle, fuel pump ON. No abnormal engine sounds, no smells, airspeed and altitude remained stable.</li> </ul> <h2 id="what-went-well-the-good">What Went Well (The Good)</h2> <ul> <li><strong>Monitoring &amp; Early Detection</strong>: Continuous scanning of engine gauges during the climb allowed for early identification of the erratic RPM.</li> <li><strong>Composure</strong>: I remained calm and avoided panicking.</li> <li><strong>Power Check</strong>: My safety pilot suggested a brief full-power burst while descending from 2,500’ to 1,100’ within a 1.5 NM distance at idle. This was an excellent move to verify the engine’s actual responsiveness.</li> <li><strong>Energy Management</strong>: Maintained a tight pattern after entry for a safe landing.</li> <li><strong>DECIDE Model Application:</strong> <ul> <li><em>Note: Rather than consciously following the model, my thought process naturally flowed through these stages.</em></li> <li><strong>D</strong>etect: Identified the RPM fluctuation at 2,300’ early in the climb.</li> <li><strong>E</strong>stimate: Confirmed engine sound and flight parameters were normal, but decided the unstable gauge made it unsafe to continue the flight.</li> <li><strong>C</strong>hoose: Decided to return to the departure airport (7B2).</li> <li><strong>I</strong>dentify: Planned to cancel IFR with ATC, inform them of the situation, and initiate a turn.</li> <li><strong>D</strong>o: Executed the plan, flew a tight base turn, and landed safely.</li> <li><strong>E</strong>valuate: Conducted this post-flight review as part of the final evaluation.</li> </ul> </li> </ul> <figure class="caption" style="text-align:center;"> <a href="/posts_image/2026-02-05/track-log-foreflight-google-earth.png" target="_blank"> <img src="/posts_image/2026-02-05/track-log-foreflight-google-earth.png" alt="Flight Path Detail (GoogleEarth)" style="height:; width:" /> </a> <figcaption class="caption-text">Flight Path Detail (GoogleEarth)</figcaption> </figure> <h2 id="areas-for-improvement">Areas for Improvement</h2> <h4 id="1-navigation">1. Navigation</h4> <p>I should have executed a “Direct-to 7B2” on the GPS immediately to get a bearing on the HSI. While I tried to identify the airport visually first, the lack of visual cues at night delayed my situational awareness. In an abnormal situation at night with high-workload, relying solely on eyes outside can be deceptive. Always use the “Direct-To” function as a primary source of orientation before attempting to acquire the field visually especially at night.</p> <blockquote> <p><strong>Takeaway</strong>: Utilize System-Based Orientation over Visual Search (<strong>especially at night</strong>)</p> </blockquote> <h4 id="2-radio-communication">2. Radio Communication</h4> <p>I was preoccupied with the thought of canceling my IFR flight plan. Because I was landing at a non-towered airport, I was worried that I might forget to cancel it later, which would unnecessarily trigger a Search and Rescue (SAR) mission. This concern, combined with the stress of the situation, made it difficult to find the right words for ATC. My thoughts became cluttered, and as I tried to speak too quickly, my radio calls became rushed and disorganized. Even when the controller asked, “Verify you are canceling IFR,” a simple “Affirmative” would have been enough. Instead, I gave a long-winded explanation that wasn’t necessary.</p> <ul> <li>Better Approach: Keep it simple and direct. “Approach, 85NA, engine issues, turning direct to Northampton, cancel IFR.”</li> <li>I didn’t need to over-explain. Focus on clear, concise communication.</li> </ul> <blockquote> <p><strong>Takeaway</strong>: Keep it brief when the workload is high.</p> </blockquote> <h4 id="3-checklist-usage">3. Checklist Usage</h4> <p>I failed to perform a formal “Do-and-Verify” of the Engine Roughness or Engine Failure checklist. To be fair, since the engine sounded normal and my flight data was stable, I assumed the engine was fine and didn’t think I needed the checklist for what seemed like just a gauge glitch. Furthermore, being just 3NM from the runway at 2,400ft (well above the 1,100ft TPA), my mental bandwidth was entirely occupied with planning the pattern entry and landing. However, despite these factors, I should have at least verified the “Engine Roughness” checklist to confirm the engine’s health.</p> <blockquote> <p><strong>Takeaway</strong>: Always verify with the checklist, even if the issue seems like a sensor failure.</p> </blockquote> <h4 id="4-post-flight">4. Post Flight</h4> <p>Post-flight Inspection I should have performed a more thorough visual inspection of the engine bay after landing to check for any mechanical causes.</p> <h2 id="self-reflection">Self Reflection</h2> <ul> <li><strong>My Judgment</strong>: I assessed that a formal deviation or emergency declaration wasn’t necessary at the moment. I focused on canceling the IFR flight plan first before initiating the turn.</li> <li><strong>Q1: Was canceling IFR the best choice?</strong> What if the engine had actually failed right after I canceled the flight plan? Instead of being so worried about the IFR cancellation, wouldn’t it have been safer to simply say, “Declaring emergency, engine issue, turning direct 7B2”? By keeping the IFR or declaring an emergency, either way would have given me priority handling and reduced my workload by having ATC monitor my terrain clearance.</li> <li><strong>Q2: What if this happened at an unfamiliar airport?</strong> I was lucky that this happened at my home base where I know the terrain well. If I were over unfamiliar territory at night, canceling IFR would have been a much riskier move. Staying on the IFR plan would have ensured I had radar vectors and terrain clearance until the runway was in sight.</li> </ul> <h2 id="final-thoughts">Final Thoughts</h2> <ul> <li>Through this real-world ADM (Aeronautical Decision-Making) experience, I was able to objectively identify the areas where I still need improvement.</li> <li>While the outcome was safe, it highlighted the critical importance of utilizing all available resources—GPS, ATC, and checklists—during an abnormal situation, especially at night.</li> </ul> Thu, 05 Feb 2026 00:00:00 +0000 https://yaboong.github.io/aviation-experience/2026/02/05/post-flight-analysis-near-engine-failure-rpm-gauge-anomaly/ https://yaboong.github.io/aviation-experience/2026/02/05/post-flight-analysis-near-engine-failure-rpm-gauge-anomaly/ engine-failure RPM-gauge-anomaly ADM DECIDE night-operation IFR aviation-experience No More Confusion: Departure vs. Upwind Leg in the Traffic Pattern <h2 id="intro">Intro</h2> <blockquote> <p>The Confusion Ends: Find out how the new AIM update permanently resolves the debate between the Departure and Upwind Legs.</p> </blockquote> <ul> <li>Source: <a target="_blank" href="https://www.faa.gov/air_traffic/publications/atpubs/aim_html/chap0_info_eoc.html">[AIM] Explanation of Changes</a>, <a target="_blank" href="https://www.faa.gov/air_traffic/publications/atpubs/aim_html/chap4_section_3.html#$paragraph4-3-2">[AIM][4-3-2] Airports with an Operating Control Tower</a></li> </ul> <!--more--> <blockquote> <p>⚠️ This content is for educational and informational purposes only. It does not constitute legal advice or an official FAA guideline. Always refer to the latest FAR/AIM and official regulations for actual flight operations. The author assumes no liability for errors or omissions in the content.</p> </blockquote> <h3 id="tldr">TL;DR;</h3> <ul> <li>Effective: August 7, 2025</li> <li>The <strong>Departure Leg</strong> is the segment flown from the start of the takeoff roll until the initial turn (either crosswind or a departure turn) is executed.</li> <li>The <strong>Upwind Leg</strong> is the segment flown when the pilot does not turn crosswind after the initial departure, but instead continues straight ahead along the extended runway centerline.</li> <li>Going forward, when Air Traffic Control (ATC) instructs you to “extend upwind,” you know the exact procedure: you simply continue flying straight ahead along the extended runway centerline.</li> </ul> <p><br /></p> <h3 id="old-confusing-definition-of-upwind-and-departure-leg-in-aim-before-update">Old Confusing Definition of Upwind and Departure Leg in AIM (Before Update)</h3> <p>I sourced this material from <a target="_blank" href="https://www.faa.gov/documentLibrary/media/Advisory_Circular/AC_90-66C.pdf">AC 90-66C</a>, which appears not to reflect the AIM update of August 7, 2025.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/docs/AIM-before-20250807.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/docs/AIM-before-20250807.png" alt="(BEFORE UPDATE) Confusing Explanation of Departure vs Upwind in AIM" style="height:; width:" /> </a> <figcaption class="caption-text">(BEFORE UPDATE) Confusing Explanation of Departure vs Upwind in AIM</figcaption> </figure> <p>The term “Extend Upwind” caused significant confusion among pilots and CFIs before the AIM definition was clarified. When ATC used the term, they usually intended for the pilot to continue straight ahead along the extended runway centerline (an extended departure leg).</p> <p>However, pilots familiar with older AIM definitions often misinterpreted “Upwind Leg” as flying parallel to the runway but offset and opposite the landing direction, outside the standard traffic pattern circuit.</p> <p>While it is highly unlikely that any pilot actually flew the complete, incorrect Upwind Leg in response to an ATC instruction, this ambiguity undoubtedly created a moment of brief confusion and hesitation in the cockpit as the pilot tried to reconcile the ATC instruction with the manual’s definition. The subsequent AIM update successfully aligned the definition with the common operational practice.</p> <p><br /></p> <h3 id="aim-updates-aug-7th-2025">AIM Updates (Aug 7th, 2025)</h3> <p>Detailed explanation about the updates are well written here <a target="_blank" href="https://www.faa.gov/air_traffic/publications/atpubs/aim_html/chap0_info_eoc.html">[AIM] Explanation of Changes</a>. What I’d like to talk about in this article is <a target="_blank" href="https://www.faa.gov/air_traffic/publications/atpubs/aim_html/chap4_section_3.html#$paragraph4-3-2">[AIM][4-3-2] Airports with an Operating Control Tower</a> this.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/docs/AIM-explanation-of-changes.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/docs/AIM-explanation-of-changes.png" alt="AIM Updates (Aug 7th, 2025)" style="height:; width:" /> </a> <figcaption class="caption-text">AIM Updates (Aug 7th, 2025)</figcaption> </figure> <blockquote> <p>This change realigns the AIM definition and graphic depiction of upwind leg at towered airports with current ATC use and expectation. ATC usage of upwind leg is an extension of departure. The AIM’s current definition of upwind has led to confusion among pilots and controllers. The new proposed graphic depiction of upwind in FIG 4-3-1 as well as the definition in 4-3-2c aligns with common usage at towered airports.</p> </blockquote> <p><br /></p> <h3 id="whats-updated---4-3-2-airports-with-an-operating-control-tower">What’s Updated? -&gt; <a target="_blank" href="https://www.faa.gov/air_traffic/publications/atpubs/aim_html/chap4_section_3.html#$paragraph4-3-2">[4-3-2] Airports with an Operating Control Tower</a></h3> <p>FIG 4-3-1 and Definitions in 4-3-2c are the main updates about Departure and Upwind leg confusion.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/docs/AIM-4-3-2.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/docs/AIM-4-3-2.png" alt="AIM Updates (Aug 7th, 2025)" style="height:; width:" /> </a> <figcaption class="caption-text">AIM Updates (Aug 7th, 2025)</figcaption> </figure> <ul> <li><strong>Departure</strong>: The flight path that begins after takeoff and continues straight ahead along the extended runway centerline. The departure climb continues until reaching a point <u>at least 1/2 mile beyond the departure end of the runway and within 300 feet of the traffic pattern altitude.</u></li> <li><strong>Upwind leg</strong>: A flight path that begins after departure and continues straight ahead along the extended runway centerline. <u>Upwind leg is an extension of departure</u> and is used when issuing control instructions for separation, spacing, or sequencing.</li> </ul> <p><br /></p> <h3 id="summary-of-updates">Summary of Updates</h3> <ul> <li>The <strong>Departure Leg</strong> is the segment flown from the start of the takeoff roll until the initial turn (either crosswind or a departure turn) is executed.</li> <li>The <strong>Upwind Leg</strong> is the segment flown when the pilot does not turn crosswind after the initial departure, but instead continues straight ahead along the extended runway centerline.</li> <li>Going forward, when Air Traffic Control (ATC) instructs you to “extend upwind,” you know the exact procedure: you simply continue flying straight ahead along the extended runway centerline.</li> </ul> Sat, 18 Oct 2025 00:00:00 +0000 https://yaboong.github.io/far/aim/2025/10/18/upwind-leg-departure-leg-AIM-updates/ https://yaboong.github.io/far/aim/2025/10/18/upwind-leg-departure-leg-AIM-updates/ traffic-pattern Aeronautical-Information-Manual AIM FAR/AIM Instrument Rating Checkride Oral Cheat Sheet (Quick IR review) <h2 id="intro">Intro</h2> <ul> <li>There’s a lot to study for your IR checkride oral, and trying to review everything can be inefficient when you’re short on time.</li> <li>So, I’ve summarized and simplified the essential knowledge into a very simple format.</li> <li>This isn’t a detailed, in-depth explanation of each topic.</li> <li>It’s meant for quick review when you want to rapidly go over everything you’ve studied, especially in the week leading up to your exam.</li> </ul> <!--more--> <blockquote> <p>⚠️ This content is for educational and informational purposes only. It does not constitute legal advice or an official FAA guideline. Always refer to the latest FAR/AIM and official regulations for actual flight operations. The author assumes no liability for errors or omissions in the content.</p> </blockquote> <h2 id="qualifications">Qualifications</h2> <ul> <li><strong>requirements</strong>: AME (age/medical/english) TWEET (training-ground&amp;flight/written/endorsement/experience/training-instrument)</li> <li><strong>privilege</strong>: PPL privilege + can fly <strong>*IR required</strong></li> <li><strong>limit</strong>: pro rata share</li> <li><strong>*IR required</strong>: classA / SVFR sunset sunrise / PIC [weather&lt;VFR / pass XC ≥ 50 or night]</li> <li><strong>proficiency vs currency</strong>: skilled &amp; safe vs min legal req</li> <li><strong>unfamiliar</strong>: proficiency↓ → workload↑ → risk↑ → mitigate personal minimums &amp; train &amp; POH</li> <li><strong>SVFR</strong>: clearance to fly in control zone when weather below VFR [ceiling&lt;1000, vis&lt;3]</li> <li><strong>aero exp</strong>: 50 XC PIC, 40 inst (15hrs CFI, including 250nm filed XC - 3app, airway/routing)</li> <li><strong>FTD</strong>: BATD max 10hrs, AATD max 20hrs</li> <li><strong>PIC</strong>: flight review within 24 cal months [1hr-ground/1hr-flight] /w instructor, sub by prac test <ul> <li>(within 24) 2025.09.16 flight → done at least 2023.09</li> </ul> </li> <li><strong>carry pass as PIC</strong>: 3 TOL 90days, 1 after sunset to 1 before sunrise → full stop</li> <li><strong>IFR PIC</strong>: <strong>66HITS</strong>, SIM (no instructor possible but proficiency↓), airplane (safety pilot) <ul> <li>last 6 cal months (=exclude current month) → 9/16 IFR flight? 3/1~8/31 log acceptable</li> </ul> </li> <li><strong>safety pilot</strong>: ppl, adequate vision, dual control</li> <li><strong>IPC</strong>: failed 66HITS + 6 cal month passed → IPC (by CFII, <strong>ACS p29</strong>, some with SIM) <ul> <li>today 9/9 → no 6HITS in 3,4,5,6,7,8 → IFR currency expired</li> <li>grace period (6 months) → if 6HITS in 9,10,11,12,1,2? then IR current, otherwise IPC required March</li> </ul> </li> <li><strong>IFR recency exemptions</strong>: PIC part 121/135</li> <li><strong>logging</strong>: soley ref inst, IAF/IF/FAF required (unless radar vectored to final: ATC or safety pilot), simulated IMC (airplane/SIM) → must MDA/DA, actual IMC → FAF pass okay (proficiency↓)</li> <li><strong>medical</strong>: mine [got my 1st class 2024.06, expires 2029.06.30], 40↓[12/48,12/48,60], 40↑[6/6/12,12/12,24]</li> <li><strong>BasicMed</strong>: regular physician for medical, bypass AME, alternative 3rd med, require [US driver’s, CMEC 48 months, online course 24 months, treatment, FAA med cert after 7/14/2006], limit [max 7 total, 12500lbs↓, 18000MSL↓, 250kts↓, in US]</li> </ul> <h2 id="human-factors--adm">Human Factors &amp; ADM</h2> <ul> <li><strong>IMSAFE, PAVE</strong>: <a href="https://www.faasafety.gov/files/gslac/courses/content/28/212/Personal%20Minimums%20Checklist.pdf">FAA Personal Minimums Checklist</a></li> <li><strong>hypoxia</strong>: oxygen deficiency in body cells, 12000↑, hypoxic(altitude), hypemic(carbon monoxide poisoning/anemia), histotoxic(alcohol/drugs), stagnant(G-force), dizziness/headache</li> <li><strong>carbon monoxide poisoning</strong>: exhaust fumes, [dizziness/headache], solution [heat-off/vents-open/land ASAPoss]</li> <li><strong>hyperventilation</strong>: excessive breathing, [dizziness/headache], solution [talk/loud/paperbag]</li> <li><strong>body system</strong> <ul> <li>vestibular: inner ear detects [motion/balance] → [pitch/roll/yaw]</li> <li>somatosensory: body nerves in [skin/muscles/joints]</li> <li>visual (eyes): rods (night &amp; peripheral), cones (color, requires brighter light)</li> </ul> </li> <li><strong>middle ear &amp; sinus blockage</strong>: congestion blocks pressure equalization climb/descend, symptom[ear pain/facial pressure], solution[avoid flying/yawn/chew gums]</li> <li><strong>illusions</strong>: ICE<em>F</em>L<em>A</em>GS (FA=visual) <ul> <li>inversion: climb→straight → tumbling backward</li> <li>coriolis: head, prolonged turn → trick inner ear → feels tilting/accelerating different direction</li> <li>elevator: strong updraft → feels nose-up</li> <li>false horizon: visual cues misinterpretation</li> <li>leans: slow bank → feels like level flight → actual level flight feels like banking opposite</li> <li>autokinesis: stationary light appears to move</li> <li>graveyard spiral: prolonged turns → feels like level flight → actual level flight feels like turning</li> <li>somatogravic: rapid [acceleration/deceleration] → feels like [nose-up/down]</li> </ul> </li> <li><strong>oxygen (unpressurized cabin)</strong>: above 12.5~14 (crew must use after 30min), above 14 (crew entire time), above 15 (each occupants provided)</li> </ul> <h2 id="regulations--documents">Regulations &amp; Documents</h2> <ul> <li><strong>personal docs req</strong>: PRIM (Pilot cert/Radio license/ID-photo/Medical)</li> <li><strong>aircraft docs req</strong>: ARROW (A: Airworthiness Certificate)</li> <li><strong>maintenance inspections req</strong>: AVIATES (A: AD, S: Supplemental Type Certificate)</li> <li><strong>preflight</strong>: NWKRAFT (Notam/Weather/Known ATC Delay/Runway/Alternate/Fuel/TOL performance)</li> <li><strong>max airspeed</strong>: 250 [below 10000], 200 [under or VFR corridor class B / 2500↓ within 4NM class C D]</li> <li><strong>minimum equip req</strong> <ul> <li>MEL → KOEL → 91.205 → 91.213(d)</li> <li>91.205: <strong>ATOMATOFLAMES</strong> (VFR day), <strong>FLAPS</strong>(VFR night), <strong>GRABCARD</strong>(IFR)</li> <li>for hire over water (unless part 121): flotation device for each, at least 1 pyrotechnic signaling device</li> <li>above FL240: using VOR → DME/RNAV required</li> </ul> </li> <li><strong>ATOMATOFLAMES</strong>: altimeter/tachometer/oil-pressure/manifold-pressure/airspeed/temperature-engine/oil-temperature/fuel-quantity/landing-gear-position-light/anti-collision/magnetic-direction/ELT/safety-belts</li> <li><strong>FLAPS</strong>: fuses or circuit-breaker/landing-light/anti-collision/position(navigation)/source of electricity</li> <li><strong>GRABCARD</strong>: generator or alternator/2way-radio/altimeter(sensitive)/ball(slip-skid)/clock/attitude-ind./rate-of-turn/directional-gyro(heading ind.)</li> </ul> <details><summary><u>Operating With Inoperative Items (91.213)</u></summary> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/MEL.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/MEL.png" alt="Operating With Inoperative Items (91.213)" style="height:; width:" /> </a> <figcaption class="caption-text">Operating With Inoperative Items (91.213)</figcaption> </figure> </details> <h2 id="weather-service--product">Weather Service &amp; Product</h2> <ul> <li><strong>flight category</strong>: LIFR,<strong>IFR</strong>,MVFR,VFR: vis[<strong>1,3</strong>,5], ceiling[<strong>500,1000</strong>,3000]</li> <li><strong>sources</strong>: aviation weather, 1800wxbrief(web&amp;call), fltplan, EFB(ForeFlight), in-flight [FSS, FIS-B(ADS-B), NEXRAD, ATC]</li> <li><strong>ATIS</strong>: airport (wind, vis, sky, temp, dew, altimeter, NOTAMs), update hourly (55min)</li> <li><strong>AWOS</strong>: basic (wind, vis, altimeter, temp, dew, density alt)</li> <li><strong>ASOS</strong>: more advanced than AWOS+(precipitation type, intensity, runway condition)</li> <li><strong>METAR</strong>: routine weather report, airport, hourly(55-59min), valid current hour</li> <li><strong>SPECI</strong>: significant change (vis↓, thunderstorms, wind shifts), update with METAR</li> <li><strong>know how to read</strong>: <ul> <li>SPECI KBAF 071007Z AUTO 36003KT 1/4SM <strong>R20/2200V3000FT</strong> FG <strong>VV002</strong> 19/18 A2980 RMK A02 T01890183</li> <li>KCCA 071015Z AUTO 00000KT 3SM <strong>VCTS</strong> RA SCT033 BKN080 OVC120 21/21 A2986 RMK A02 <strong>LTG DSNT ALQDS</strong></li> <li>KBOS 251911Z 32006KT 5SM <strong>TS</strong> SCT055 BKN070CB OVC090 26/22 A2982 RMK A02 <strong>PK WND</strong> 26027/1855 <strong>RAE10 OCNL LTGICCC E TS E MOV E P0010</strong> T02610217 $</li> </ul> </li> <li><strong>PIREP</strong>: real-time by pilots, UA(routine), UUA(urgent, turbulence, icing) <ul> <li><strong>UA</strong> /OV LAX090015 /TM 1500 /FL050 /TP C172 /SK SCT025-TOP030 /WX FV04SM /TA 10 /TB LGT /IC NEG</li> <li><strong>UUA</strong> /OV MYR180001/TM 1422/ FLDURD/TP CRJ7/WV +15 AT 400 FEET -20 AT 200 FEET</li> </ul> </li> <li><strong>TAF</strong>: by meteorologists, forecast, 5SM radius around station, every 6hours (4 times a day - 00, 06, 12, 18), 24/30 hrs <ul> <li>TAF AMD (amended, supersede previous TAF), TAF COR (corrected)</li> <li>TEMPO(temporary, lasting less than 1hr), FM (rapid change within 1hr), BECMG (gradual change up to 2hrs), PROB (probability, PROB30 → 30% chance)</li> </ul> </li> <li><strong>know how to read</strong>: <ul> <li>BECMG 3000/3001 VRB05KT 9999 <strong>NSW</strong> FEW050 SCT090 QNH2987INS <strong>TX23/2920Z TN11/3010Z</strong></li> <li>042025Z 0420/0523 1901520KT 9999 SCT060 BKN250 QNH2975INS WND 17009KT <strong>AFT 0502</strong> TEMPO 0420/0501 21015G25KT <strong>520005</strong></li> </ul> </li> <li><strong>winds aloft (FB)</strong>: various altitudes, 4 times daily, (no wind 1500↓AGL, no temp 2500↓AGL), for [choosing alt/fuel plan/icing/freezing level/lapse rate/temperature inversion/turbulence], “7525-02”</li> <li><strong>AIRMET (WA)</strong>: non-scheduled inflight advisory, hazardous light aircraft, valid 6hrs, S(mountain obscuration), T(srf wind≥30, turbulence, low-level wind shear), Z(icing, freezing level)</li> <li><strong>SIGMET (WS)</strong>: non-convective severe, for all aircraft, not associated with thunderstorms, valid 4hrs (6 if hurricane), severe turbulence (CAT), severe icing, volcanic ash, dust/sand storms reducing vis to ≤ 3S</li> <li><strong>Convective SIGMET (WST)</strong>: <ul> <li>severe convective weather, hazardous to all aircraft, hourly at H+55, valid for 2hrs (severe or greater turbulence, severe icing, low-level wind shear)</li> <li>convective?: vertical movement of air caused by heating → thunderstorms &amp; lightning</li> <li>issued for: (not for Alaska/Hawaii) <ul> <li>thunder-storm: areas ≥ 40% of 3000sq miles</li> <li>severe thunder-storm: surface wind≥50kts, hail at surface ≥ 3/4 inch in diameter</li> <li>embedded thunder-storm: obscured by cloud layers</li> <li>squall line thunder-storm: at least [60 miles long &amp; affecting at 40% of its length]</li> </ul> </li> </ul> </li> <li><strong>Convective Outlook (AC)</strong>: forecasting tool, broad view of t-storm. expectation, available SPC, 8 day outlook (1,2,3,4-8), issuance [day1(5times), day2(2times), day3(1time)]</li> <li><strong>NEXRAD</strong>: ground-based radars, not real-time (5-20min delay), detects [t-storms, precipitation (heavy rain), wind speed, atmospheric motion]</li> <li><strong>surface analysis chart</strong>: snapshot of current weather, on the ground, every 3hrs, provides[H/L, fronts, isobars, weather symbols (rain/fog)]</li> <li><strong>prognostic chart</strong>: forecast of future weather, 6/12/24/48 hrs ahead, 4 times daily, provides[H/L, fronts, isobars, weather hazards (turb/icing/precipitation)]</li> <li><strong>CVA</strong>: <a href="https://aviationweather.gov/gfa/#cigvis">ceiling &amp; visiblity analysis</a>, graphical weather, quick overview of ceiling &amp; vis</li> </ul> <h2 id="weather-theory">Weather Theory</h2> <ul> <li><strong>abbreviation for this section</strong>: t-storm(thunderstorm), vis(visibility), w.(wind), p.(pressure), turb(turbulence), cl.(cloud), ws(wind shear), alt(altitude)</li> <li><strong>atmosphere</strong>: [nitrogen78/oxygen21/argon0.9/water-vapor0-4]%</li> <li><strong>standard day</strong>: 15℃ (59℉), 29.92”Hg (1013.2hPa or mb)</li> <li><strong>heat exchange</strong>: sun uneven heating → warm(less dense)↑ &amp; cold(denser)↓ → air movement</li> <li><strong>stable air</strong>: resists vertical motion, stratiform, smooth air, steady precipitation, fair-poor vis</li> <li><strong>unstable air</strong>: promotes vertical motion, towering(cumuliform), rough air, showery precipitation, t-storms., good vis</li> <li><strong>lapse rate</strong>: rate at which temp↓ as alt↑, standard 1000’↑ [2℃/1”Hg]↓, steeper(temp↓rapidly as alt↑) → unstable/convective act</li> <li><strong>temp inversion</strong>: temp↑ as alt↑, hot-day&amp;cool-night→ ground cools → still hot above → warm air lid → trapping cooler air → vis↓(smog/haze),wind shear</li> <li><strong>wind shear</strong>: sudden change wind speed/direction in small area <ul> <li>sudden drastic change in IAS, affect lift &amp; control, low-level ws (less time to recover)</li> <li>mainly occurs: t-storms, frontal boundary (cold front), temperature inversion, CAT at high, microbursts</li> </ul> </li> <li><strong>mountain wave</strong>: strong w. blow perpendicular to mountain, severe turb, lenticular/rotor cl.</li> <li><strong>factors affecting wind</strong>: <ul> <li>pressure diff [diff↑→wind↑]</li> <li>coriolis force: earth’s rotation, deflect wind/air right Northern Hem., poles↑equator:0</li> <li>friction btwn air/surface: slows down wind→coriolis force↓→wind flow across isobars at slight angle toward low pressure (surface wind slower &amp; turb. than winds aloft)</li> <li>gravity: vertical air movements (downbursts/microbursts)</li> </ul> </li> <li><strong>isobar</strong>: connect points equal pressure, closer→pressure gradient↑→wind↑</li> <li><strong>high pressure</strong>: air moves[outward/downward/clockwise], typically clear&amp;stable, sinking air warms up→preventing clouds forming</li> <li><strong>low pressure</strong>: air moves[inward/upward/counter-clockwise], generally cloudy&amp;unstable, often rain/snow, rising air cools→moisture condense→[form clouds,icing risk]</li> <li><strong>trough</strong>: brown, elongated area of low p., air↑(can’t sink or spread)→cloud forms&amp;precipitation→often bad weather</li> <li><strong>ridge</strong>: yellow, elongated area of high p., air↓→often good weather</li> <li><strong>turbulence</strong>: unsteady/irregular motion of air <ul> <li>thermal turb: hot days→uneven heating→warm air↑→cumulus clouds &amp; storms</li> <li>mechanical turb: wind blowing over [rough ground/mountains]→choppy air</li> <li>frontal turb: two air masses(cold front)→warm air↑→instability&amp;bumps</li> <li>CAT: invisible in clear sky, often near jet stream (due to strong wind shear)</li> </ul> </li> <li><strong>dew point</strong>: temperature at which air becomes saturated → no longer hold water vapor → condensation begins (water vapor → liquid) <ul> <li>closer temp&amp;dewpoint→humidity↑visible moisture↑(fog/clouds/dew/frost/rain)→ vis↓</li> <li>aircraft surface == dew point → frost</li> <li>temperature &lt; dew point → icing</li> </ul> </li> <li><strong>precipitation</strong>: any form of water that falls from the atmosphere (rain/drizzle/sleet/hail) <ul> <li>sleet: snow(solid, ice crystals)+freezing rain(rapid structural icing)</li> <li>hail: ice pellets produced by strong updrafts in thunderstorms</li> </ul> </li> <li><strong>fronts</strong>: boundaries between two air masses <ul> <li>squall line? → ahead of a cold front</li> <li>cold front moves over mountainous? → icing</li> <li>frontal waves? → slowly moving cold fronts or stationary fronts</li> <li>low level wind shear? → warm front (temperature inversion)</li> </ul> </li> <li><strong>cold front (blue)</strong>: [cold/dense/fast-moving/20-30kts or faster] air forces warm air to rise rapidly → steep frontal surface, generally more intense (after:temperature↓&amp;clr sky) <ul> <li>hazards: rapid weather change(few hours), towering cumulous(nimbus), strong turbulence, gusty winds, heavy precip, squall line t-storms., hail, tornadoes</li> </ul> </li> <li><strong>warm front (red)</strong>: [warm/less dense/slow-moving/10-15kts] air glides up over cooler air mass → gradual frontal slope, generally less intense, produce [wide area of weather/stratiform/ceiling↓/vis↓/rain/drizzle&amp;fog] <ul> <li>hazards: freezing rain → icing (warm rain falls into cold air layer and freeze)</li> </ul> </li> <li><strong>stationary front (red&amp;blue)</strong>: two air masses balanced, neither is advancing, can last for days, mixed cold&amp;warm fronts, hazards[low-intensity precip, poor vis, low cloud]</li> <li><strong>occluded front (purple)</strong>: fast-moving cold front catches up slower-moving warm front → lifting warm air mass entirely off the ground, mixed cold&amp;warm fronts <ul> <li>hazards: strong winds/turbulence/embedded thunderstorms with thick cloud layers]</li> <li>more severe when warm front occlusion (embedded thunderstorms)</li> </ul> </li> <li><strong>clouds</strong>: moist air cooled to its dew point → water vapor condenses into tiny liquid <ul> <li>caused by lifting: convective(air heated&amp;rise), orographic(air rise by mountain), frontal</li> <li>hazards: icing, turb(cumuliform clouds), vis↓, thunderstorms</li> </ul> </li> <li><strong>cloud types</strong>: cumulus(piled), stratus(in layers), cirrus(high-level,20000↑), nimbus (rain-bearing), alto(middle-level,5000-20000) <ul> <li>lenticularus: lens-shaped, formed over mountains in strong winds</li> <li>virga - contains strong downdrafts</li> <li>rotor clouds</li> </ul> </li> <li><strong>thunderstorm</strong>: unstable(steep)lapse rate, sufficient moisture(small temp-dewpoint spread), lifting action(surface heat, orographic, frontal)</li> <li><strong>thunderstorm stages:</strong> cumulus[cloud develop/strong updrafts], mature[most intense/up&amp;down-drafts/rainfall begins/severe turb/wind shear/lighting/hail], dissipating[weakens/mostly downdrafts/rainfall decrease/often anvil top]</li> <li><strong>thunderstorm caution</strong>: keep 20NM distance (hail/rain can be thrown up to 20NM)</li> <li><strong>microburst</strong>: [undetectable/small/strong]downdraft→sudden/powerful winds near ground <ul> <li>losing altitude 6000ft/min, dangerous WS(especially takeoff/landing)</li> <li>wind(150mph=131kts), short-lived, lasting 5-15 min, less than 2.5 miles wide</li> </ul> </li> <li><strong>fog</strong> (PAIRUS): cloud near surface(50’), air cools to its dew point &amp; moisture added <ul> <li>precipitation: rain→moisture evaporates→humidity↑→air saturated, warm rain through cooler air[warm front/temperature inversion]</li> <li>advection: warm/moist air moves over cooler surface (coastal areas), opposite of steam fog, forms quickly, cover large areas</li> <li>ice: extremely cold (below -25°F), Arctic regions</li> <li>radiation: calm/clear night, ground cools air above, dissipate with sunrise</li> <li>upslope: wind pushes moist/stable air to upsloping terrain (mountain obscuration)</li> <li>steam: cold/dry air moves over warm water</li> </ul> </li> <li><strong>fog vs mist</strong>: fog(vis&lt;5/8mile), mist(5/8≤vis&lt;7) is often dissipating form of fog</li> <li><strong>frost</strong>: surface below freezing, water vapor condenses onto that surface <ul> <li>effects: even thin layer→significantly disrupt airflow→lift↓drag↑→stall speed↑</li> <li>hazards: dangerous especially during takeoff, block [pitot tubes, static port]</li> <li>removal: [anti-icing/de-icing] fluid, wipe off, melt in [heated hanger, sun]</li> </ul> </li> <li><strong>freezing rain</strong>: rain→ice (it was rain above but freezes at current altitude -&gt; rain that starts to freeze) <ul> <li>temperature inversion: above warm layer → cold → snow → [snow→rain] at warm layer → [rain→ice] at cold surface → ice pellets at surface means [freezing rain/supercooled water] at higher altitude</li> <li>rain from air 0℃↑ → into air 0℃↓</li> <li>rain freezes on impact</li> <li>temp could be 0℃↑ at higher alt.</li> <li>freezing drizzle→warmer air above</li> </ul> </li> <li><strong>wet snow</strong>: ice→rain (it was snow above but melts at current altitude -&gt; snow that starts to melt) <ul> <li>wet snow at your alt.→ temp&gt;freezing → freezing level above</li> </ul> </li> </ul> <h2 id="icing">Icing</h2> <ul> <li><strong>PA28</strong>: defroster, carb heat, pitot heat</li> <li><strong>intensity</strong>: trace, light, moderate(accumulation 1~3inches/hour), severe</li> <li><strong>low-pressure areas</strong>: rising air → cool down → visible moisture↑ → icing risk↑</li> <li><strong>performance degradation:</strong> <ul> <li>1/2 inch ice → lift↓ by 50%</li> <li>icing→disrupts airflow(lift↓thrust↓drag↑weight↑)→AoA↑→stall speed↑</li> <li>approach speed↑, landing distance↑</li> <li>sandpaper-like icing on leading edge and upper surface → lift↓by30%&amp;drag↑by40%</li> </ul> </li> <li><strong>mitigate</strong>: icing&amp;rolling→use aileron↓rudder↑, tailplane stall→use caution app&amp;land, disengage auto-pilot VS mode, deicing, anti-icing <ul> <li>hand fly, find cloud base/top, leave visible moisture (climb/descend/turn), warmer temperature, lower humidity</li> <li>in some cases, reaching -10℃ could be an option, since visible moisture is less likely to be present</li> </ul> </li> <li><strong>types</strong>: instrument, induction, structural <ul> <li>instrument icing: forms on instruments &amp; sensors → inaccurate readings → pitot-heat</li> <li>induction icing: forms in engine’s air intake system → airflow↓ → disrupts air/fuel mixture → engine performance↓ → engine failure (carb icing) <ul> <li>carburetor icing: forms when [temperatures (-7~21℃, 70℉) / relative humidity above 80%], due to steep temperature drop in carburetor venturi → carb heat</li> </ul> </li> <li>structural icing: forms when visible moisture contacts below freezing aircraft surface, forms on exterior surface (wings/tail/props) → change airfoil shape → risk↑ <ul> <li>clear: large droplet spreads. [smooth/clear/hard/dense]→difficult [detect/remove]</li> <li>rime: small droplets freeze instantly. [milky(rough&amp;white) color, brittle texture] → easier to spot, forms on thinner parts (wing edges)</li> <li>mixed: when [snow/rain] coexists(unstable/mixed clouds), severe performance loss</li> </ul> </li> </ul> </li> <li><strong>+structural icing</strong>: “most likely”→freezing rain (“least” likely→high clouds), mostly form on tail in rain when temperature few degree above freezing, opposite of ICTS</li> <li><strong>SLD (Supercooled Large droplets)</strong> <ul> <li>liquid water droplets that remain in a liquid state even at temp below freezing.</li> <li>occur aloft even without precipitation at surface.</li> <li>visual indication: droplets that splash on impact at temperatures below 5℃</li> </ul> </li> <li><strong>freezing level</strong>: lowest altitude temperature 0°C, must get accurate freezing level before IFR, sources [AWC/winds aloft forecast (FB)/PIREP/weather briefing]</li> <li><strong>FIKI</strong>: [frost/ice/snow] on airplane→not allowed, but deicing or anti-icing equip to protect[wings/windshield/controls/instrument]? → allowed (use caution)</li> <li><strong>icing on airfoils</strong>: shape change→weight↑lift↓(less noticeable at low AoA)→stall speed↑ <ul> <li>small ice→localized stall→roll/spin, likely form first on tailplane, flaps already extended?(never retract)→retract?→stall speed↑</li> </ul> </li> <li><strong>roll upset</strong>: can be caused by localized stall, try [AoA↓/airspeed↑/extend flaps/wings level]</li> <li><strong>tailplane</strong>: tailplane creates tail-down force→nose-up moment→counteracts nose-down moment created by lift, better ice collector than wings</li> <li><strong>tailplane icing (ICTS)</strong>: Ice Contaminated Tailplane Stall, mostly during approach/landing <ul> <li>tailplane icing→tail-down force↓→nose-down↑ ← don’t [deploy flaps or speed↑] <ul> <li>if flaps or speed↑→lift↑→even more nose-down</li> </ul> </li> <li>symptoms: elevator[vibration↑effectiveness↓heavier] &amp; uncommanded nose-down</li> <li>recovery: retract flaps immediately, and then speed↑ to counteract flap retraction, slowly control nose-down pitch, no-flaps approach</li> </ul> </li> <li><strong>plan&amp;predict</strong> (CAFFU): Cloud layers, Air temperature &amp; pressure (more likely in low-pressure areas, cool temperatures), Fronts movement, Freezing level, Use weather briefing (METAR/TAF/AIRMET/SIGMET)</li> <li><strong>anti-icing/deicing</strong>: anti-icing(prevention), deicing(removal) <ul> <li>for airfoil: inflatable deicing boots, thermal anti-ice leading edge, weeping wings</li> <li>for windshield: alcohol anti-ice, electric heating anti-ice, defroster</li> <li>for propeller: alcohol anti-ice, electric heating anti-ice, deicing boots</li> <li>others: pitot heat, carb heat</li> </ul> </li> </ul> <h2 id="instrument--attitude-flying">Instrument &amp; Attitude Flying</h2> <ul> <li><strong>basic skills</strong>: CIA, <strong>errors</strong>: fixation/omission/emphasis</li> <li><strong>set power &amp; attitude, then monitor performance</strong> <ul> <li>(first) control: power [tach/manifold p.], attitude (attitude ind.)</li> <li>(second) performance: pitch [altimeter/ASI/VSI], bank (heading ind/turn cord./mag.comp]</li> </ul> </li> <li><strong>primary &amp; supporting</strong> <ul> <li>pitch: attitude ind., altimeter, airspeed, VSI <ul> <li>bank: attitude ind., heading ind., mag. compass, turn cord.</li> <li>power: airspeed, tachometer, manifold pressure</li> <li>primary instruments: not moving / not changing</li> </ul> </li> <li> <p>table</p> <table> <thead> <tr> <th>Maneuve r</th> <th>Pitch</th> <th>Bank</th> <th>Powe r</th> </tr> </thead> <tbody> <tr> <td>Straight &amp; Level</td> <td>Altimeter</td> <td>Heading</td> <td>Airspeed</td> </tr> <tr> <td>Climbs / Descents (Airspeed)</td> <td>Airspeed</td> <td>Heading</td> <td>VSI</td> </tr> <tr> <td>Climbs / Descents (Vertical Speed)</td> <td>VSI</td> <td>Heading</td> <td>Airspeed</td> </tr> <tr> <td>Turns</td> <td>Altimeter</td> <td>Turn Coordinator</td> <td>Airspeed</td> </tr> <tr> <td>Transition to Climb</td> <td>Attitude</td> <td>Heading</td> <td>VSI/Airspeed</td> </tr> <tr> <td>Transition to Turn</td> <td>Altimeter</td> <td>Attitude</td> <td>Airspeed</td> </tr> <tr> <td>ILS Approach</td> <td>Glide Slope</td> <td>Localizer</td> <td>Airspeed</td> </tr> </tbody> </table> </li> </ul> </li> <li><strong>gyroscopic principles</strong> <ul> <li>rigidity in space: spinning gyro remains fixed in its plane of rotation, providing stability</li> <li>precession: force is applied to gyro, resulting force occurs 90° ahead in the direction of rotation</li> </ul> </li> <li><strong>gyroscopic instruments</strong> <ul> <li><strong>att ind</strong>: bank/pitch, rigidity in space, vacuum-driven or elec, correct in 5 start engine, errors[acceleration?/deceleration?/roll-out?]</li> <li><strong>head ind</strong>: heading, rigidity in space, vacuum (calibrate w mag.com) or elec</li> <li><strong>turn ind</strong>: rate-of-turn &amp; rate-of-roll, precession, turn coordinator (both), turn-and-slip ind (rate-of-turn only)</li> </ul> </li> <li><strong>pitot-static instruments</strong>: pitot tube (dynamic p), static port (ambient static p), pitot mast <ul> <li><strong>altimeter</strong>: measure altitude above specific ref (sea level, Kollsman window), 75ft airport elev, High to Low Look out Below <ul> <li>how: altitude↑→pressure↓→wafer expands→mechanical linkage→indicate</li> </ul> </li> <li><strong>VSI</strong>: shows rate-of-climb in fpm, 6-9sec delay, rate trend immediate <ul> <li>how: diaphragm direct static source, chamber receives static p via calibrated leak, climb/descend→diaphragm p change instantly, chamber change slowly→pressure diff→mechanical linkage→shows rate of climb</li> </ul> </li> <li><strong>ASI</strong>: measures diff between impact(ram) air p (pitot tube) and ambient p (static port) <ul> <li>how: dynamic p (airspeed)=impact p - static p, diaphragm receives ram air p from pitot tube [faster→expand], air p↓ as alt↑ → ASI indicate slower → to correct → apply ambient p from static port → shows dynamic p as airspeed</li> </ul> </li> </ul> </li> <li><strong>types of altitude</strong>: indicated, true (actual/MSL), absolute (AGL), pressure, density <ul> <li>PA: corrected for non-standard p. (FL180↑→29.92), formula</li> <li>DA: PA corrected for non-standard temp, formula</li> <li>[pressure↓/humidity↑/temperature↑] → DA↑ → less dense air → performance↓</li> <li>DA ↑ → [takeoff &amp; landing roll ↑ / rate of climb ↓ / same IAS but TAS ↑ on landing]</li> </ul> </li> <li><strong>types of speed</strong>: IAS, CAS (IAS corrected for instrument &amp; position errors), TAS (CAS corrected for pressure &amp; non-standard temperature), GS (TAS corrected for wind)</li> <li><strong>markings</strong>: white-flaps (Vso~Vfe), green-normal (Vs1~Vno), yellow-caution, red-Vne</li> <li><strong>Vspeeds(PA28)</strong>: Vs0(44),Vs1(51),Vne(160),Vno(126),Va(88-111),Vfe(103),Vy(79),Vx(63),Vg(73,LDmax),Vb(87,Vs1*1.7),landingfinal(63),crosswind(17)</li> <li><strong>pitot-static blockage</strong> <ul> <li>static port: airspeed (alt↑→speed↓, correct only at blocked), altimeter(freeze), VSI(0)</li> <li>using alternate static source → venturi effect → lower pressure <ul> <li>airspeed↑, altimeter↑, VSI (momentarily climb)</li> </ul> </li> <li>pitot tube: use pitot-heat <ul> <li>ram air inlet (x) &amp; drain hole (o) → 0 (zero)</li> <li>ram air inlet (x) &amp; drain hole (x) → like altimeter</li> </ul> </li> </ul> </li> <li><strong>instrument taxi check</strong>: ASI 0, VSI 0, altimeter ±75ft, heading correct, att (bank ±°5 within 5 minutes of engine start, if vacuum), turn cord (turn → ball goes to opposite), compass (swings freely, full of fluid)</li> <li><strong>magnetic compass err</strong> VODM: variation(diff true N mag N, east least west best), oscillation(swinging needle turb or maneuv), deviation(aircraft mag field), magnetic dip (earth’s magnetic field pulling needle down, north affinity, acc decel error ANDS, turning err UNOS)</li> <li><strong>electronic flight inst</strong>: AHRS, ADC, FD, FMS, EFIS</li> </ul> <h2 id="ifr-rules">IFR rules</h2> <ul> <li><strong>IFR flight plan</strong>: FSS[radio/phone/RCO/person], ARTCC, online[1800/fltplan/EFB], 30min prior dep (4hrs before if FL230↑), cancel[towered:auto, non-to:pilot→ATC/FSS, anytime out A or no IMC], form [7233-4 INT, 7233-1 FAA] <ul> <li>your filed plan will be shown on ATC’s side for 2 hours</li> </ul> </li> <li><strong>min fuel</strong>: dest, alt, 45min cruise</li> <li><strong>alternate required</strong>: 1123 (11ETA, ceiling 2000, vis 3SM)</li> <li><strong>alternate airport weather minima</strong>: 600/2 precision, 800/2 non-precision, no IAP?→ceiling &amp; vis desc MEA, app &amp; landing VFR</li> <li><strong>GPS considerations</strong>: non-WAAS (1 GPS app), no baro-VNAV WAAS (plan LNAV both), baro-VNAV WAAS (plan LNAV/VNAV or RNP0.3 both)</li> <li><strong>IFR cruising alt</strong>: →odd, ←even, MC, above FL290, RVSM</li> <li><strong>IFR takeoff minimums</strong>: part91[priv/GA/train] none, part121/135 (1-2eng:1SM, eng&gt;2:1/2SM), even though 91[vis↓&amp;ceiling↓→ODP→if 200f/nm not avail→VCOA]</li> <li><strong>IFR min alt</strong>: not allowed ↓min-altiutde, if none → mountain 2000/4, non-mountain 1000/4</li> <li><strong>required reports</strong>: MARVELOUS VFR C500 == fix/points: M<strong>CFO</strong>H, change: A<strong>E</strong>A, safety: US EU (CFO, E: non-radar)</li> <li><strong>position reports</strong>: IF TPA fix/point/remarks [ID, flight plan type, time, position, altitude, next reporting fix (ETA &amp; name), next succeeding point (name), remarks]</li> <li><strong>other keywords</strong>: cruise clearance, block altitudes, minimum fuel (non emergency)</li> </ul> <h2 id="ifr-flying-procedures">IFR Flying Procedures</h2> <ul> <li><strong>DP</strong>: TO→ENR transition, [DER35’/400’turn/200FPNM], course guidance [10NM straight, 5NM turn], ODP/SID(workload↓, graphic), not required but encouraged [night/MVMC/IMC]</li> <li><strong>Diverse DP</strong>: IAP but no DP, requires airport no obs 200FPNM up to min IFR alt, turn 400’, DVA</li> <li><strong>VCOA</strong>: designed to avoid 3SM from DER, visual climbing turn up to published alt, alternative for &gt;200fpnm, advise ATC</li> <li><strong>clearance</strong>: after 30min CVT ATC, EDCT 5min, how (clearance/ground/pop-up/PDC/CPDLC), hold for release (traffic mgmt, class D only 1 IFR, hold until “released for departure”), “expect 3000 10 min after”→clearance limit for lost comm</li> <li><strong>VFR-on-top</strong>: by pilot request, only FL180↓(no class A), at above MEA, comply IFR regulations, VFR cruising alt, VFR cloud clr &amp; vis req, report reaching VFR-on-top</li> <li><strong>STAR</strong>: ENR→APPR transition, [flow/sequencing] for busy, at least a textual required, “No STARs” remarks, RNAV STARs RNAV1 performance</li> <li><strong>holding</strong>: for [missed/delay/traffic/weather/emergency], clearance items?, reduce speed 3min before, 5T, min of [3°sec/30°bank/25°FDS], speed limit [~6~14/200,230,265], inbound [14000↓1min, 14000↑1.5min], leave [EFC/ETA if lost comm/report]</li> <li><strong>lost comm</strong>: 7600, “in the blind”, Highest MEA (minimum IFR alt., expected, assigned), route orderly AVEF (Assigned, Vectored, Expected, Filed), VFR ASAPrac, light-gun</li> <li><strong>leaving clearance limit</strong> <ul> <li>if c-limit is approach begin fix: start descend &amp; approach as close as possible to EFC (if no EFC → ETA)</li> <li>if c-limit not approach begin fix: leave c-limit at EFC <ul> <li>if no EFC: upon arrival over the c-limit, proceed to a fix where approach begins and commence descent as close to ETA</li> </ul> </li> </ul> </li> <li><strong>PT</strong>: course reversal, descent from IAF, 10NM, max 200kts, NoPT, SHARPTT <ul> <li>NoPT when SHARPTT: straight-in/holding-in-lieu-of/DME Arc/radar vectors/NoPT/time approach/teardrop course reversal</li> </ul> </li> </ul> <h2 id="navigation">Navigation</h2> <ul> <li><strong>VOR</strong> <ul> <li>full deflection 10° (5 dots, 2° deviation for dot), verify morse ID</li> <li><strong>check</strong>: [VOT±4/ground±4/airborne±6/dual-check 4 between two], every 30 days, check FROM 0 / TO 180, sensitivity (at 5th dot within 10°~12°)</li> <li>sign: DEPS (Date / bearing Error / Place / Signature)</li> <li>limit: cone of confusion (1 mile per 10,000ft), reverse sensing, require line-of-sight</li> </ul> </li> <li><strong>DME:</strong> auto tune with (VOR/LOC), slant range error (right above 6076ft shows 1NM), negligible 1NM per every 1000ft (you 5000ft? → negligible when 5NM away)</li> <li><strong>NDB</strong>: low-mid freq band, older form → replacing, how: [sends signal all direction → ADF (automatic direction finder) points needle to NDB station]</li> <li><strong>ILS:</strong> instrument landing system, localizer, glide slope, marker beacons,</li> <li><strong>ILS failure</strong>: <strong>outside FAF (→switch LOC), inside FAF(→Missed)</strong></li> <li><strong>Localizer</strong>: opposite end of rwy, lateral guide, width (3°-6°), threshold 700ft (350ft each from center), usually 5° total width (2.5° each side → 4x sensitive than VOR), coverage[10NM-35°/18NM-10°/4500ft]</li> <li><strong>glide slope</strong>: vertical guide, auto tune /w LOC, interprets 90Hz/150Hz signal (90Hz&gt;150Hz → above), width (1.4°, 0.7 for up/down deach), range (10NM), slope (3°)</li> <li><strong>marker beacons</strong>: provides range information <ul> <li> <table> <thead> <tr> <th>Marker Beacon</th> <th>Color</th> <th>Audio Pattern</th> <th>Distance from RWY</th> <th>Height (AGL)</th> </tr> </thead> <tbody> <tr> <td>Outer Marker (OM)</td> <td>Blue</td> <td>— (slow)</td> <td>4~7miles (21000~37000ft)</td> <td>about 1400ft</td> </tr> <tr> <td>Middle Marker (MM)</td> <td>Amber</td> <td>-.-. (middle)</td> <td>0.5~0.8miles (2600~4200ft)</td> <td>about 200~250ft</td> </tr> <tr> <td>Inner Marker (IM)</td> <td>White</td> <td>….(fast)</td> <td>near threshold (within 300~500ft)</td> <td>below 100ft</td> </tr> </tbody> </table> </li> </ul> </li> <li><strong>GPS keywords</strong> <ul> <li><strong>update every 28 days</strong></li> <li><strong>GNSS</strong>: all satellite system world-wide, including GPS (US GNSS - dep of Defense)</li> <li><strong>GPS</strong>: type of GNSS, operate by US dep of Defense</li> <li><strong>SBAS</strong>: <strong>ground stations</strong> → accuracy 10x↑, [WAAS(US)/EGNOS(EU)/KASS(KOR)]</li> <li><strong>WAAS</strong>: type of SBAS, FAA run, [error margin 3 meters / position accuracy 95%↑]</li> <li><strong>GBAS</strong>: installed near airport, GLS can replace ILS, accuracy↑ than WAAS but coverage↓)</li> <li><strong>RAIM</strong>: detects satellite error → gives warning, min 5 functioning required, WAAS GPS no need RAIM but built for backup</li> <li><strong>TSO-C[129,196]</strong>: non-WAAS GPS, no SBAS correction, alternate require non-GPS</li> <li><strong>PBN</strong> (set of standard), <strong>RNP</strong> (type or RNAV, RNAV with alert, RAIM, WAAS)</li> <li>RNP0.3 (APPR), RNAV1 (DP, STAR), RNAV2 (ENR)</li> <li><strong>approved GPS</strong>: G1000, GNS 430/530, GTN750 (↔ non approved:ForeFlight)</li> </ul> </li> <li><strong>RNAV</strong> (Area Navigation): fly any path using its own equip, without overfly ground-based facilities, types[GNSS,VOR/DME,DME/DME], no GPS RNAV possible</li> <li><strong>RNAV NavSpecs</strong>: lateral navigation accuracy, RNAV1 [DPs/STARs]: less 1NM err 95%, RNAV2 (enroute,T/Q-routes): less 2NM err 95%, RNAV10 (for oceanic)</li> <li><strong>how GPS works</strong> <ul> <li>5 satellites at any location→each satellites sends time-stamped signal→receiver measures travel time of signal→calculate distance to each satellite</li> <li>3 satellites → position (lat/long), 4 satellites → position &amp; altitude</li> <li>RAIM: GPS receiver monitors satellite signal integrity, min 5 (or 4+ altimeter input (baro-aided RAIM)), fault exclusion → min 6 (or 5 + alt input)</li> </ul> </li> <li><strong>PBN</strong>: set of navigation equip standards (particular NavSpecs), not dependent on single tech, procedure don’t need to be changed/renamed just because of new nav hardware</li> <li><strong>RNP</strong>: type of PBN, “statement” of nav equip and service performance <ul> <li>RNAV: no alerting</li> <li>RNP: type of RNAV + [monitoring/alerting] function (RAIM or WAAS)</li> <li>RNP0.3 approach: nav. sys. must maintain accuracy of 0.3NM or better from runway centerline during final approach segment → accuracy&lt;standard?→alert</li> <li>RNP approach minima and equipment: <ul> <li>GLS DA minima → require GBAS</li> <li>LP MDA / LPV DA minima → require RNP achieved by WAAS.</li> <li>LNAV / VNAV DA → achieved by VNAV-approved WAAS, or BARO-VNAV systems.</li> <li>LNAV MDA → achieved by a basic, unaugmented IFR-approved GPS.</li> </ul> </li> <li>RNP Navigation Performance <ul> <li>Final Approach: RNP 0.3 (0.3 NM accuracy 95% of flight time)</li> <li>Terminal &amp; Departure: RNP 1.0 (1 NM accuracy 95% of the flight time)</li> <li>Enroute: RNP 2.0 (2 NM accuracy 95% of the flight time)</li> </ul> </li> </ul> </li> </ul> <details><summary><u>Localizer</u></summary> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/localizer.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/localizer.png" alt="Localizer" style="height:; width:" /> </a> <figcaption class="caption-text">Localizer</figcaption> </figure> </details> <details><summary><u>Glide Slope</u></summary> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/glide-slope.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/glide-slope.png" alt="Glide Slope" style="height:; width:" /> </a> <figcaption class="caption-text">Glide Slope</figcaption> </figure> </details> <details><summary><u>Marker Beacon</u></summary> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/marker-beacon.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/marker-beacon.png" alt="Marker Beacon" style="height:; width:" /> </a> <figcaption class="caption-text">Marker Beacon</figcaption> </figure> </details> <h2 id="approach">Approach</h2> <ul> <li><strong>stabilized approach</strong>: landing config, on profile, airspeed±10, descent rate 500~700 (no more than 1000), optimal RPM</li> <li><strong>chart reading</strong>: TDZE(highest in first 3000’), THRE(threshold elev.), MSA(guarantees 1000’ obs. clear. within 25NM radius, no sig coverage), TCH (threshold crossing height) <ul> <li><strong>signs[T/A/Ana]</strong>: T[non-standard TO mins / DP], A[non-standard IFR alternate min], Ana[alternate minimums not authorized=can’t be used as alternate airport]</li> <li><strong>signs[snowflake]</strong>: <a href="https://aeronav.faa.gov/d-tpp/Cold_Temp_Airports.pdf">Cold Temperature Airport</a> X mark → altitude correction (AGL), <a href="https://www.faa.gov/air_traffic/publications/atpubs/aim_html/chap7_section_3.html#VnDuU1cfGeor">AIM 7-3 correction table</a> (how far below due to cold temperature induced error)</li> </ul> </li> <li><strong>identify FAF</strong>: precision (light-bolt), non-precision (maltese cross)</li> <li><strong>approach category</strong> <ul> <li><strong>Based on final approach speed</strong> (<strong>1.3 times stall speed in landing configuration</strong> at MGLW (maximum gross landing weight))</li> <li>A ≤ 90, B (91-120), C (121-140), D (141-165), E &gt; 165</li> </ul> </li> <li><strong>descend below MDA/DA (91.175)</strong>: normal descent rate, not less than visibility requirement, visual reference requirement</li> <li><strong>visual reference requirements</strong>: runway environment in-sight <ul> <li>[threshold, runway, touchdown zone] markings or lights, REIL, VASI, PAPI</li> <li><strong>ALS</strong> : red terminating bars (ASLF-1) or red side row bars (ALSF-2) → descend below 100ft above TDZE</li> </ul> </li> <li><strong>missed approach</strong> <ul> <li>identify [precision: DA / non-precision: MAP]</li> <li>execute when: “MDA→MAP or DH/DA” with insufficient visual ref, unsafe app, ATC</li> <li>immediately go-missed when: CDI deflects 3/4-scale↑ (horizontal/vertical), RAIM lost, when below MDA/DA (visual contact lost, vis&lt;minimum, not in normal pos.)</li> </ul> </li> <li><strong>approach clearances</strong>: when can you descend to next approach segment? → cleared for app. &amp; established on segment of published app.</li> <li><strong>VDP</strong>: non-precision, ‘V’ symbol on profile, no below MDA prior to reaching VDP <ul> <li>no VDP? → maintain MDA until 91.175 is met → descend</li> <li>VDP on chart? then maintain MDA to VDP (even if 91.175 is met)</li> <li>at VDP? → 91.175 meet? then descend, otherwise go missed</li> <li>formula: (MDA in AGL) / 300 → NM, (MDA in AGL) / 10 → sec</li> </ul> </li> <li><strong>VDA</strong>: glide path from FAF to TCH (for non-precision), typically 3°, advisory only</li> <li><strong>IAP (Instrument Approach Procedures)</strong> <ul> <li><strong>precision</strong>: ILS, MLS, PAR, GLS, TLS → lateral &amp; vertical guidance to DA</li> <li><strong>non-precision</strong>: VOR, LOC, RNAV/RNP to LNAV or LP Minima, LDA, SDF, ASR, NDB → lateral only to MDA, step-down <ul> <li>NDB, VOR, LOC: may use IFR-certified GPS as primary source until the FAF</li> </ul> </li> <li><strong>APV</strong>: prevision-like, to DA, with vertical guidance but doesn’t meet precision standard <ul> <li>LDA with Glide Slope, LPV, LNAV/VNAV</li> </ul> </li> </ul> </li> <li><strong>other types of approach:</strong> circling/visual/contact/radar-vectors/parallel/GLS <ul> <li><strong>circling</strong>: course alignment↔rwy center exceeds 30°, approach name not include rwy (VOR/DME-B), remain 1.3NM (for category A) radius of all runways (B1.5, C1.7, D2.3, E4.5), visual ref lost while circling? (climbing turn to landing rwy → continue turn until established on missed course)</li> <li><strong>visual</strong>: pilot or ATC initiate (pilot can reject), vis≥3SM, ceiling≥1000 (IFR under VMC), must have [airport or traffic to follow] in-sight, pilot responsible visual separation</li> <li><strong>contact</strong>: similar to visual but SVFR, only init by pilot (no ATC), airport with IAP (ground vis≥1sm, remain Coc), obs. clr. pilot’s responsibility</li> <li><strong>radar vectors to final</strong>: at least 2NM outside FAF, until cleared [no turn to final / maintain last assigned altitude]</li> <li><strong>parallel ILS</strong>: provides 1 1/2 miles radar separation between aircraft</li> <li><strong>GLS</strong>: only 3 airports, KEWR, KIAH, Boeing private</li> </ul> </li> <li><strong>LPV vs LNAV/VNAV</strong>: <ul> <li>LPV: requires WAAS, GPS based, no temperature error</li> <li>LNAV/VNAV: non-WAAS ok, GPS + baro-VNAV based → temperature error</li> </ul> </li> </ul> <details><summary><u>Approach Chart</u></summary> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/approach-chart.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/approach-chart.png" alt="Approach Chart" style="height:; width:" /> </a> <figcaption class="caption-text">Approach Chart</figcaption> </figure> ' </details> <details><summary><u>RVR</u></summary> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/rvr.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/rvr.png" alt="RVR" style="height:; width:" /> </a> <figcaption class="caption-text">RVR</figcaption> </figure> </details> <details><summary><u>Approach Lighting System</u></summary> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/aviation/ALS.png" target="_blank"> <img src="/yaboong-blog-static-resources/aviation/ALS.png" alt="Approach Lighting System" style="height:; width:" /> </a> <figcaption class="caption-text">Approach Lighting System</figcaption> </figure> </details> <h2 id="airspace--ifr-low-chart">Airspace &amp; IFR Low Chart</h2> <ul> <li><strong>special user airspace</strong>: MCPRAWN <ul> <li><strong>MOA</strong>: military training zones, permission NOT required, but extreme caution (advised to contact the controlling agency)</li> <li><strong>Controlled firing</strong>: NOT ON CHART (means no one knows where the controlled firing space is), potential hazards (firing will be stopped if an aircraft is detected)</li> <li><strong>Prohibited</strong>: [P-**], security reasons, NOT ALLOWED</li> <li><strong>Restricted</strong>: [R-**], hazardous activities, PERMISSION REQUIRED if active</li> <li><strong>Alerting</strong>: [A-**], high traffic volumes (ex. pilot training). exercise caution.</li> <li><strong>Warning</strong>: [W-**], potential hazards, 3NM off the U.S. coast.</li> <li><strong>NSA</strong> (national security area): temporarily prohibited, not to enter voluntarily, ADIZ (2-way radio, transponder, file IF or DVFR)</li> <li>(+) <strong>TFR</strong>: prohibited without authorization. check NOTAM.</li> <li>(+) <strong>SFRA</strong> (Special Flight Rules Area): Grand Canyon, Washington DC, Hudson River</li> </ul> </li> <li><strong>minimum altitude for IFR</strong> <ul> <li>no applicable minimum altitude? → mountainous (2000↑ highest obs., 4NM horizontal), others (1000↑ highest obs., 4NM horizontal)</li> <li><strong>DA(DH)</strong>: precision, above rwy threshold</li> <li><strong>MDA(MDH)</strong>: non-precision, above wy threshold, visual references</li> <li><strong>MCA</strong>: route &amp; direction, must cross higher than MCA, flying to higher MEA, X flag</li> <li><strong>MTA</strong>: obstacle clearance (vertical/lateral) turns over fixes</li> <li><strong>MRA</strong>: lowest alt for nav signal. R flag</li> <li><strong>MEA</strong>: nav signal reception &amp; obstacle clearance (along airway), MEA GAP</li> <li><strong>МОСА</strong>: obstacle clearance (could be lower than MEA), guarantees VOR signal within 22NM(25SM) of station, * sign</li> <li><strong>MVA</strong>: could be lower than MOCA, assigned by ATC.</li> <li><strong>OROCA</strong>: obstacle clearance within 1° grid square, no signal guaranteed (VFR MEF)</li> <li><strong>MAA</strong>: max authorized alt</li> <li><strong>MSA</strong>: guarantees 1000ft obstacle clearance within 25NM, no sig coverage</li> </ul> </li> <li><strong>airport</strong>: blue/green (IAP), brown (no-IAP), show only if hard-surface rwy 3000ft↑</li> <li><strong>airways</strong>: V(victor, black, VOR), T(tango, blue, GPS)</li> <li><strong>reporting points</strong>: VHF fix [on VOR radial, triangle, required report when not in radar contact], GPS fix [without VOR possible, RNAV navigation waypoint, T-Routes, Q-routes]</li> <li><strong>distance</strong>: plain number(leg), rounded(cumulative), square(total), COP(change over point)</li> <li><strong>off airways navigation</strong>: VOR only &amp; below 18000ft ? → max distance between NAVAIDs = 80NM</li> </ul> <h2 id="wb">W/B</h2> <ul> <li><strong>forward</strong>: CG toward nose (stable) <ul> <li>↑stable(↓maneuverable), stall↑, TOL roll↑, cruise effi↓</li> <li>returns to level flight easily, harder to raise the nose, need↑elevator force</li> </ul> </li> <li><strong>aft</strong>: CG toward tail (maneuverable) <ul> <li>↑maneuverable(↓stable), stall↓, TOL roll↓, cruise effi↑fuel↑</li> <li>less stable, more likely to pitch up, less elevator force is needed</li> </ul> </li> </ul> Sat, 15 Feb 2025 00:00:00 +0000 https://yaboong.github.io/aviation/2025/02/15/instrument-rating-checkride-oral-cheat-sheet-quick-review/ https://yaboong.github.io/aviation/2025/02/15/instrument-rating-checkride-oral-cheat-sheet-quick-review/ aviation-theory aviation 스프링 카프카 Batch Consumer - 의도치 않은 리스너 호출 <h3 id="개요">개요</h3> <ul> <li>스프링 카프카 Batch Consumer 를 사용하는데, 의도하지 않은 @KafkaListener 호출이 발생했다.</li> <li>스프링 카프카 소스를 까본 내용을 정리해보았다.</li> </ul> <!--more--> <p><br /></p> <h3 id="tldr">TL;DR</h3> <ul> <li>spring kafka batch consumer 를 사용할때 누적 메시지 사이즈나 레코드 개수를 기준으로 가져오는 것이 아닌, 일정한 시간간격을 가지고 읽어들이고 싶은 경우에</li> <li>@KafkaListener 메서드의 호출은, 카프카 클러스터를 구성하는 브로커 개수만큼(최대) 발생할 수 있다.</li> <li>백그라운드에서 무한루프를 돌면서 polling 을 하고 있는데, 노드별로 비동기로 fetch 요청을 보내기 때문이다.</li> <li>spring kafka 2.3 버전이후부터 제공되는 ContainerProperties 의 <code class="language-plaintext highlighter-rouge">idleBetweenPolls</code> 값으로 해결할 수 있다.</li> <li>KafkaMessageListenerContainer 내부 콜 시퀀스 다이어그램</li> </ul> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/kafka-message-listener-container-function-call-sequence.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/kafka-message-listener-container-function-call-sequence.png" alt="KafkaMessageListenerContainer funcation call sequence" style="height:; width:" /> </a> <figcaption class="caption-text">KafkaMessageListenerContainer funcation call sequence</figcaption> </figure> <p><br /> <br /></p> <blockquote> <p>아래부터는 파악하는 과정을 적어 보았다. (길어요 ㅠㅠ)</p> </blockquote> <p><br /></p> <h3 id="사용버전">사용버전</h3> <ul> <li>java 11</li> <li>spring boot 2.2.6.RELEASE</li> <li>spring kafka 2.3.7.RELEASE</li> <li>apache kafka client 2.3.1</li> </ul> <p><br /></p> <h3 id="배경">배경</h3> <p>hive 로 적재하는 consumer group 하나가 있고, 같은 topic 에 붙어서 통계수치를 집계하는 다른 consumer group 을 추가해야할 일이 생겼다.</p> <p>10초 간격으로 메시지들을 가져와서 group_no (각 메시지들을 그룹핑 할 수 있는 기준 값) 를 기준으로 집계하여 통계DB 를 업데이트하는 작업이었다.</p> <p><br /></p> <h3 id="하고자-했던-것">하고자 했던 것</h3> <ul> <li>10초에 1회 <code class="language-plaintext highlighter-rouge">@KafkaListener</code> 메서드가 호출되도록 설정한다.</li> <li>받아온 메시지들을 각각 group_no 를 기준으로 grouping 한다.</li> <li>같은 group_no 로 들어온 메시지들이 몇개인지 DB 에 업데이트를 한다.</li> </ul> <p>비교적 간단한 작업이었다. 근데 kafka 는 처음이라 좀 오래 걸렸다. (아직 쪼렙이라 다 처음이다 ^<em>__</em>^)</p> <p><br /></p> <h3 id="하고자-했던-것을-하기위해">하고자 했던 것을 하기위해</h3> <h6 id="consumer-관련-프로퍼티-설정">consumer 관련 프로퍼티 설정</h6> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>spring.kafka.consumer.max-poll-records=6000 spring.kafka.consumer.fetch-min-size=1MB spring.kafka.consumer.fetch-max-wait=10s </code></pre></div></div> <table> <thead> <tr> <th style="text-align: center"><strong><u>key</u></strong></th> <th style="text-align: center">default</th> <th style="text-align: left">description</th> </tr> </thead> <tbody> <tr> <td style="text-align: center"><a href="https://docs.confluent.io/current/installation/configuration/consumer-configs.html#max.poll.records">max.poll.records</a></td> <td style="text-align: center">500개</td> <td style="text-align: left">10초에 한번 가져오도록 구성할 것이므로 충분히 큰 값으로 지정 <br /> –&gt; <strong>6000개로 변경</strong></td> </tr> <tr> <td style="text-align: center"><a href="https://docs.confluent.io/current/installation/configuration/consumer-configs.html#fetch.min.bytes">fetch.min.bytes</a></td> <td style="text-align: center">1byte</td> <td style="text-align: left">consume 할 메시지가 최소 1byte 라도 있으면 fetch 하는 것이 디폴트 <br /> –&gt; <strong>1MB 채워지기 전까지는 fetch 하지 않도록 변경</strong></td> </tr> <tr> <td style="text-align: center"><a href="https://docs.confluent.io/current/installation/configuration/consumer-configs.html#fetch.max.wait.ms">fetch.max.wait.ms</a></td> <td style="text-align: center">500ms</td> <td style="text-align: left">fetch.min.bytes 가 채워지지 않았다면 최대 500ms 동안 block 하는 것이 디폴트 <br />–&gt; <strong>10초로 변경</strong></td> </tr> </tbody> </table> <blockquote> <p>1MB 가 채워지거나, 6000개 레코드가 채워지지 않으면 10초 동안 기다려라</p> </blockquote> <h6 id="code-configuration">code configuration</h6> <ul> <li>AbstractKafkaListenerContainerFactory 의 batchListener 설정을 true 로</li> <li>ContainerProperties.AckMode.BATCH 사용</li> <li>리스너에서 MyLogType 에 바로 매핑시킬 수 있도록 MyLogType 은 org.apache.kafka.common.serialization.Deserializer 를 구현</li> </ul> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Bean</span> <span class="kd">public</span> <span class="nc">ConcurrentKafkaListenerContainerFactory</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">MyLogType</span><span class="o">&gt;</span> <span class="nf">kafkaListenerContainerFactory</span><span class="o">(</span> <span class="nc">ConsumerFactory</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">MyLogType</span><span class="o">&gt;</span> <span class="n">consumerFactory</span> <span class="o">)</span> <span class="o">{</span> <span class="kt">var</span> <span class="n">factory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConcurrentKafkaListenerContainerFactory</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">MyLogType</span><span class="o">&gt;();</span> <span class="n">factory</span><span class="o">.</span><span class="na">setConsumerFactory</span><span class="o">(</span><span class="n">consumerFactory</span><span class="o">);</span> <span class="c1">// deserializer 를 구현한 consumer factory</span> <span class="n">factory</span><span class="o">.</span><span class="na">setBatchListener</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> <span class="n">factory</span><span class="o">.</span><span class="na">setBatchErrorHandler</span><span class="o">(</span><span class="k">new</span> <span class="nc">SeekToCurrentBatchErrorHandler</span><span class="o">());</span> <span class="kt">var</span> <span class="n">containerProperties</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">getContainerProperties</span><span class="o">();</span> <span class="n">containerProperties</span><span class="o">.</span><span class="na">setAckMode</span><span class="o">(</span><span class="nc">ContainerProperties</span><span class="o">.</span><span class="na">AckMode</span><span class="o">.</span><span class="na">BATCH</span><span class="o">);</span> <span class="k">return</span> <span class="n">factory</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <h6 id="listener">Listener</h6> <p>여러개 레코드들을 ConsumerRecords 에 담아서 돌려주면, value 만 뽑아내서 적당히 필터링하고, group_no 별로 카운트한뒤에, 집계된 수치만큼 DB 에 업데이트 하면 됨!</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@KafkaListener</span><span class="o">(</span><span class="n">topics</span> <span class="o">=</span> <span class="s">"${spring.kafka.template.default-topic}"</span><span class="o">)</span> <span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">onMessage</span><span class="o">(</span><span class="nc">ConsumerRecords</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">MyLogType</span><span class="o">&gt;</span> <span class="n">consumerRecords</span><span class="o">)</span> <span class="o">{</span> <span class="nc">StreamSupport</span><span class="o">.</span><span class="na">stream</span><span class="o">(</span><span class="n">consumerRecords</span><span class="o">.</span><span class="na">spliterator</span><span class="o">(),</span> <span class="kc">false</span><span class="o">)</span> <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">ConsumerRecord:</span><span class="o">:</span><span class="n">value</span><span class="o">)</span> <span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="nl">Objects:</span><span class="o">:</span><span class="n">nonNull</span><span class="o">)</span> <span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="nl">MyLogType:</span><span class="o">:</span><span class="n">isCountable</span><span class="o">)</span> <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="n">groupingBy</span><span class="o">(</span><span class="nl">MyLogType:</span><span class="o">:</span><span class="n">getGroupNo</span><span class="o">,</span> <span class="n">counting</span><span class="o">()))</span> <span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="nl">repository:</span><span class="o">:</span><span class="n">updateCount</span><span class="o">);</span> <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"{}_consumed:{}"</span><span class="o">,</span> <span class="n">rand</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span> <span class="n">consumerRecords</span><span class="o">.</span><span class="na">count</span><span class="o">());</span> <span class="k">return</span> <span class="n">consumerRecords</span><span class="o">.</span><span class="na">count</span><span class="o">();</span> <span class="o">}</span> </code></pre></div></div> <p><br /></p> <blockquote> <p>10초에 한번씩 listener 가 호출되어 배치처럼 돌겠지?</p> </blockquote> <p><br /></p> <h3 id="잘되려나---내로잘">잘되려나? - 내로잘</h3> <p>일단 deserialize exception 발생하는건 잠시 무시하고, <code class="language-plaintext highlighter-rouge">kafka-producer-perf-test</code> 를 이용해서 10초에 한번씩 호출되는지 확인해본다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kafka-producer-perf-test.sh <span class="nt">--topic</span> log-topic-local <span class="nt">--num-records</span> 15000 <span class="nt">--record-size</span> 100 <span class="nt">--throughput</span> 500 <span class="nt">--producer-props</span> bootstrap.servers<span class="o">=</span>localhost:9092 </code></pre></div></div> <p>–&gt; 1 record 에 100byte 짜리 메시지를 1초에 500개씩 총 15000개 보내라</p> <ul> <li>kafka-producer-perf-test 는 30초동안 실행됨</li> <li>1초에 500개씩 produce, 1초당 생산되는 데이터는 500 * 100 = 50000byte = 500KB</li> <li>10초 동안 보내도 <ul> <li>500KB * 10초 = 5000KB 로 fetch-min-size 인 1MB 가 안되고,</li> <li>500개/s * 10초 = 5000개 로 max-poll-records 인 6000 이 안됨</li> </ul> </li> <li>즉, 10초에 한번씩 5000개 언저리의 메시지를 fetch 해올 것으로 예상</li> <li>리스너 메서드는 10초에 한번씩 1회 호출되며, 5000개 언저리의 카운트를 출력할 것으로 예상</li> </ul> <p><br /></p> <h3 id="결과">결과</h3> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/broker-1ea-perf-test.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/broker-1ea-perf-test.png" alt="kafka-producer-perf-test 결과" style="height:; width:" /> </a> <figcaption class="caption-text">kafka-producer-perf-test 결과</figcaption> </figure> <p>첫 호출건과 마지막 호출건은 5000개 가 다 채워지지 않을 수 있다. 약간 밀리기는 하지만 어쩔수 없고, 의도했던 10초에 한번씩 Listener 메서드가 호출되어 배치처럼 동작하는 것을 확인할 수 있다.</p> <p><br /></p> <h3 id="개발용-카프카-붙여서해보자">개발용 카프카 붙여서해보자</h3> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/broker-3ea-cluster-perf-test.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/broker-3ea-cluster-perf-test.png" alt="kafka-producer-perf-test 결과" style="height:; width:" /> </a> <figcaption class="caption-text">kafka-producer-perf-test 결과</figcaption> </figure> <blockquote> <p>얼레??? 10초 간격으로 호출되기는 했지만 Listener 메서드가 3번씩 호출된다.</p> </blockquote> <p><br /></p> <h3 id="뭐지-추측">뭐지? 추측</h3> <p>잘됐던 테스트는 로컬에 있는 카프카를 사용했다. 클러스터를 구성하지 않은 1대의 브로커만 있는 카프카였고, 개발용 카프카는 3대의 노드로 구성된 카프카 클러스터이다. 노드 개수와 리스너 호출수 사이에 상관관계가 있는 것 같다!</p> <p><br /></p> <h3 id="그냥-넘어갈까">그냥 넘어갈까?</h3> <p>가정이 맞다면 7대로 구성된 운영환경 카프카 클러스터에서는 10초마다 7번씩 리스너 호출이 발생할 수 있고, consumer 는 1대가 아니라 여러대를 둘 것이기 때문에, 실제로 DB 업데이트 쿼리가 호출되는 횟수는 10초마다 <code class="language-plaintext highlighter-rouge">node수 * consumer수</code> 만큼 호출될것이다.</p> <p>현재는 producer 에서 던지는 메시지가 round-robin 으로 브로커에 전달된다. producer 에서 key 를 줘서 던지면 같은 key 를 가지는 메시지는 같은 파티션으로 가기 때문에, 같은 group_no 가 여러개의 파티션으로 분산되는 지금보다 DB관점에서는 부하가 덜해지겠지만 완전한 해결방법은 아니다.</p> <p>또한, group_no 마다 들어오는 메시지수가 일정하지 않고, 어떤 것은 엄청 많고 어떤것은 거의 없고 그렇다. 그래서 특정 partition 으로 메시지가 쏠릴수 있다.</p> <blockquote> <p>문제가 된다.</p> </blockquote> <p><br /></p> <h3 id="왜이러지-확인">왜이러지? 확인</h3> <p>먼저 @KafkaListener 를 호출하는 곳을 찾아야한다.</p> <h4 id="1-kafkamessagelistenercontainerpollandinvoke">1. KafkaMessageListenerContainer.pollAndInvoke()</h4> <p>KafkaMessageListenerContainer 로 가면 run() 메서드가 있는데, 여기에서 무한루프 while 문이 실행되어 설정값에 설정한 대로 주기적으로 poll 해오는 곳이다. while 문 내에서 실행되는 pollAndInvoke() 안에서 중요한 모든 일들이 일어난다고 봐도 된다. (아래)</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/step-1-kafka-listener.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/step-1-kafka-listener.png" alt="KafkaMessageListenerContainer.run()" style="height:; width:" /> </a> <figcaption class="caption-text">KafkaMessageListenerContainer.run()</figcaption> </figure> <p>(아래) pollAndInvoke() 메서드 내부를 들여다 보면, doPoll() 메서드로 records 를 가져와서 invokeListener(records) 를 통해서 넘겨주면 @KafkaListener 가 호출된다. 이부분이 무한루프 안에서 연쇄적으로 호출되어서 리스너 호출이 브로커 개수만큼 일어나는 것 같다.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/step-2-poll-and-invoke.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/step-2-poll-and-invoke.png" alt="KafkaMessageListenerContainer.pollAndInvoke()" style="height:; width:" /> </a> <figcaption class="caption-text">KafkaMessageListenerContainer.pollAndInvoke()</figcaption> </figure> <p>invokeListener() 는 구동시점에 @KafkaListener 로 등록된 메서드를 호출하는 것 외에 딱히 하는건 없으니, doPoll() 안에서 무슨일이 벌어지는지 확인할 필요가 있다.</p> <h4 id="2-kafkamessagelistenercontainerdopoll">2. KafkaMessageListenerContainer.doPoll()</h4> <p>subBatchPerPartition 설정은 디폴트가 false 이므로 else 로 가게 되어, KafkaConsumer 의 poll() 을 호출한다. (poll timeout 은 별도 설정하지 않으면 ConsumerProperties.DEFAULT_POLL_TIMEOUT 인 5000ms 가 적용된다)</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/step-3-doPoll.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/step-3-doPoll.png" alt="KafkaMessageListenerContainer.doPoll()" style="height:; width:" /> </a> <figcaption class="caption-text">KafkaMessageListenerContainer.doPoll()</figcaption> </figure> <h4 id="3-kafkaconsumerpoll">3. KafkaConsumer.poll()</h4> <p>이 안에서는 poll timeout 으로 지정한 시간만큼 루프를 돌면서 pollForFetches(timer) 를 호출한다.<br /> (등록한 인터셉터로 records 를 넘기는 것도 볼 수 있다)</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/step-4-poll.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/step-4-poll.png" alt="KafkaConsumer.poll()" style="height:; width:" /> </a> <figcaption class="caption-text">KafkaConsumer.poll()</figcaption> </figure> <h4 id="4-kafkaconsumerpollforfetchestimer---핵심"><span style="color:red">4. KafkaConsumer.pollForFetches(timer) - 핵심</span></h4> <p><strong><mark>리스너가 왜 노드 개수만큼 호출되는지에 대한 원인을 찾아볼 수 있는 함수</mark></strong>이다. 이 메서드가 하는 일은 동기적으로 생각하면 간단하다. fetch 요청을 보내고, fetch 된 데이터가 있으면 반환하는 게 전부다.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/step-5-poll-for-fetches.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/step-5-poll-for-fetches.png" alt="KafkaConsumer.pollForFetches(timer)" style="height:; width:" /> </a> <figcaption class="caption-text">KafkaConsumer.pollForFetches(timer)</figcaption> </figure> <p>그런데 비동기로 구성되어 있어서 좀 헷갈린다. 비동기적으로 동작하기 위해서 내부적으로 2개의 큐를 이용한다. ConcurrentLinkedQueue 타입의 <mark>completedFetches</mark> 와 ConcurrentMap 타입의 <mark>unsent</mark> 이다.</p> <p><mark>completedFetches</mark> 는 fetch 가 완료된 데이터를 가지는 큐이고, <mark>unsent</mark> 는 fetch 요청을 담는 큐이다. 큐를 사용해서 비동기로 fetch 요청을 보내고, 받아온 데이터를 반환하는 방식은 아래와 같다.</p> <table> <thead> <tr> <th style="text-align: left"><strong><u>호출부</u></strong></th> <th style="text-align: left">설명</th> </tr> </thead> <tbody> <tr> <td style="text-align: left"><span style="color:#e342f5; background-color:#313131">fetcher.fetchedRecords()</span></td> <td style="text-align: left"><mark>completedFetches</mark> 큐에 레코드가 있으면 반환한다. 어디선가 이 큐에 데이터를 계속 채우고, 여기서는 데이터가 있으면 설정한 개수만큼 가져와서 리턴하기만 한다.</td> </tr> <tr> <td style="text-align: left"><span style="color:#f5f542; background-color:#313131">fetcher.sendFetches()</span></td> <td style="text-align: left">fetch 요청을 노드별로 <mark>unsent</mark> 큐에 담는다. 이 메서드에는 RequestFuture 타입을 반환하는데, 이 Future 의 onSuccess 콜백 메서드에서 fetch 가 완료된 레코드들을 <mark>completedFetches</mark> 큐에 넣는다.</td> </tr> <tr> <td style="text-align: left"><span style="color:#66f542; background-color:#313131">client.poll()</span></td> <td style="text-align: left">ConsumerNetworkClient.poll() 메서드 내부에서, unsent 큐에 담긴 fetch 요청을 실제로 네트워크를 태워서 보낸다.</td> </tr> </tbody> </table> <p>(코드가 순차적으로 실행이 되기는 하지만, 무한루프 내에서 계속 호출되기 때문에 어떤 큐에 언제 데이터가 들어가고, 큐에 데이터가 있을 경우 어떤 동작을 하는지 큐를 중심으로 코드의 흐름을 파악해야한다.)</p> <h4 id="중요한-부분"><span style="color:red">중요한 부분</span></h4> <blockquote> <p><mark>unsent</mark> 큐에 <span style="color:red"><strong>fetch 요청을 담을때에 노드별로 담고, 실제로 fetch 요청을 보낼때에도 노드별로 요청을 보낸다</strong></span>는 것이다.</p> </blockquote> <p>노드가 3개라고 치면, 3개의 요청이 <mark>unsent</mark> 큐에 담기고, 3개의 요청을 보내고, <mark>completedFetches</mark> 큐에 3개 노드에서 fetch 해 온 레코드들이 채워질때마다 return 을 하게되어 총 3번의 return 을 하게 된다. return 하면 <strong>doPoll()</strong> 을 호출했던 곳으로 쭉쭉쭉 리턴하게 되는데, 다음에는 invokeListener(records) 메서드 호출이 기다리고 있고, 여기서 @KafkaListener 메서드 호출이 총 3번 발생한 것이다.</p> <p><br /></p> <h4 id="해결방법">해결방법</h4> <p>spring kafka 2.3 이상 버전이라면 ContainerProperties 에 <mark>idleBetweenPolls</mark> 값을 주어서 해결할 수 있다.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/container-properties-idle-between-polls.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/container-properties-idle-between-polls.png" alt="ContainerProperties.idleBetweenPolls" style="height:; width:" /> </a> <figcaption class="caption-text">ContainerProperties.idleBetweenPolls</figcaption> </figure> <p>run() 메서드 무한루프 내에서 실행되는 <mark>pollAndInvoke()</mark> 메서드는 <mark>doPoll()</mark> 을 호출하기 전에 <code class="language-plaintext highlighter-rouge">idleBetweenPollIfNecessary()</code> 라는 함수를 먼저 호출한다.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/poll-and-invoke-idle-between-polls.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/poll-and-invoke-idle-between-polls.png" alt="pollAndInvoke()" style="height:; width:" /> </a> <figcaption class="caption-text">pollAndInvoke()</figcaption> </figure> <p><code class="language-plaintext highlighter-rouge">idleBetweenPollIfNecessary()</code> 는 이렇게 생겼다.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/idle-between-poll-if-necessary.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/idle-between-poll-if-necessary.png" alt="idleBetweenPollIfNecessary()" style="height:; width:" /> </a> <figcaption class="caption-text">idleBetweenPollIfNecessary()</figcaption> </figure> <p><mark>idleBetweenPolls</mark> 값이 0보다 크면 설정한 값만큼 스레드를 sleep 하여 루프의 진행을 멈추도록 한다. 이 메서드는 doPoll() 메서드 이전에 호출이 되기 때문에 completedFetches 큐에 데이터가 채워질때마다 리턴하는 것을 block 함으로써, fetch 시간동안 기다려서 노드별로 fetch 해온 데이터를 completedFetches 큐에 모두 쌓은다음, idleBetweenPolls 시간이 종료되면 한번에 return 하게 된다.</p> <p>주의할 점은</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">idleBetweenPolls</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">min</span><span class="o">(</span> <span class="n">idleBetweenPolls</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">maxPollInterval</span> <span class="o">-</span> <span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="o">-</span> <span class="k">this</span><span class="o">.</span><span class="na">lastPoll</span><span class="o">)</span> <span class="o">-</span> <span class="mi">5000</span> <span class="o">)</span> </code></pre></div></div> <p>코드에 의해 항상 내가 설정한 <mark>idleBetweenPolls</mark> 값대로 먹히는게 아니다. <mark>max.poll.interval</mark> 은 기본값이 30000ms 인데 25초보다 작은 경우에만 의도한대로 동작하고, 25초보다 크게 설정할 경우 <mark>max.poll.interval</mark> 도 수정해주어야 한다.</p> <p><br /></p> <h4 id="call-sequence-diagram">Call Sequence Diagram</h4> <p>지금까지 설명한 과정을 시퀀스 다이어그램으로 표현하면 아래와 같다.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/kafka/kafka-message-listener-container-function-call-sequence.png" target="_blank"> <img src="/yaboong-blog-static-resources/kafka/kafka-message-listener-container-function-call-sequence.png" alt="KafkaMessageListenerContainer funcation call sequence" style="height:; width:" /> </a> <figcaption class="caption-text">KafkaMessageListenerContainer funcation call sequence</figcaption> </figure> <p>지금까지 과정을 다시한번 돌아볼겸 시퀀스 다이어그램 기준으로 다시 설명해보면</p> <ul> <li>fetch 요청을 보낼때 (비동기) <ul> <li>직접 발송하지 않고 unsent queue 에 담기만 한다. <ul> <li>ConcurrentMap&lt;Node, ConcurrentLinkedQueue<ClientRequest>&gt; unsent</ClientRequest></li> </ul> </li> <li>응답을 받으면 callback 리스너에서 completedFetches 에 응답결과를 추가 <ul> <li>ConcurrentLinkedQueue<CompletedFetch> completedFetches</CompletedFetch></li> </ul> </li> </ul> </li> <li>실제 fetch 요청 전송 <ul> <li>ConsumerNetworkClient.poll() 내부에서 trySend() 호출시 node 별로 요청을 전송</li> </ul> </li> <li>그래서 원인은 <ul> <li>pollAndInvoke() 를 호출하는 while 문은 앱 구동중에 무한루프를 돌고 있음</li> <li>이때 fetch 요청은 설정값대로 1MB 나 6000 레코드가 채워지기 전까지 10초를 기다림</li> <li>그 와중에도 pollAndInvoke() 호출이 있는 루프는 계속 돌고있음</li> <li>fetch-max-wait 이 지나서 fetch 가 끝나면, 콜백리스너에서 completedFetches 에 각 노드별에서 돌려 받은 레코드를 파티션별로 add</li> <li>노드가 7개라면, 7개의 요청을 보냈을 것이고, 최대 7번 콜백리스너가 호출될 수 있음</li> <li>이 와중에도 루프는 계속 돌고 있는데, 돌다가 fetcher.fetchRecords() 결과로 records 가 있는 것을 보고 바로 반환을 하고, <ul> <li>(fetchRecords() 에서 compeletedFetches 큐에서 꺼내옴)</li> </ul> </li> <li>pollAndInvoke() 로 돌아와서 다음 코드를 진행하는데, records 가 있으므로 invoke() 메서드를 호출하게 되고, 결국 노드 갯수만큼 호출함</li> </ul> </li> </ul> Sun, 07 Jun 2020 00:00:00 +0000 https://yaboong.github.io/spring/2020/06/07/kafka-batch-consumer-unintended-listener-invoking/ https://yaboong.github.io/spring/2020/06/07/kafka-batch-consumer-unintended-listener-invoking/ spring kafka spring 스프링 - 생성자 주입을 사용해야 하는 이유, 필드인젝션이 좋지 않은 이유 <h3 id="개요">개요</h3> <ul> <li>Dependency Injection (의존관계 주입) 이란 <ul> <li>Setter Based Injection (수정자를 통한 주입)</li> <li>Constructor based Injection (생성자를 통한 주입)</li> </ul> </li> <li>스프링에서 사용할 수 있는 DI 방법 세가지</li> <li>생성자 주입을 이용한 순환참조 방지</li> <li>생성자 주입이 테스트 코드 작성하기 좋은 이유</li> </ul> <!--more--> <h3 id="서론">서론</h3> <p>의존관계 주입을 받을때는 아무생각없이 당연하게 <mark>@Autowired</mark> 를 사용한 필드주입 방식을 사용해왔다. 그런데 어느날 갑자기(?) 인텔리제이에서 경고메시지를 보여준다는 것을 보게 되었다. 항상 경고는 표시되고 있었겠지만 무시하다가 갑자기 궁금해졌다. 필드인젝션을 사용하고 있는 <mark>@Autowired</mark> 에 하이라이트 표시가 되면서 나오는 경고메시지는</p> <blockquote> <p>Field injection is not recommended … <strong>Always</strong> use constructor based dependency injection in your beans</p> </blockquote> <p>왜~~~~~~~~~~~~~~~???? 심지어 <mark>Always</mark> 라네</p> <p><br /></p> <h3 id="dependency-injection-의존관계-주입">Dependency Injection (의존관계 주입)</h3> <p>이유를 알기 위해서는 DI 에 대한 이해가 필요하다. DI 는 스프링에서만 사용되는 용어가 아니라 객체지향 프로그래밍에서는 어디에서나 통용되는 개념이다.</p> <h6 id="강한-결합">강한 결합</h6> <p>객체 내부에서 다른 객체를 생성하는 것은 <mark>강한 결합도</mark>를 가지는 구조이다. A 클래스 내부에서 B 라는 객체를 직접 생성하고 있다면, B 객체를 C 객체로 바꾸고 싶은 경우에 A 클래스도 수정해야 하는 방식이기 때문에 강한 결합이다.</p> <h6 id="느슨한-결합">느슨한 결합</h6> <p>객체를 주입 받는다는 것은 외부에서 생성된 객체를 인터페이스를 통해서 넘겨받는 것이다. 이렇게 하면 결합도를 낮출 수 있고, <mark>런타임시에 의존관계가 결정</mark>되기 때문에 유연한 구조를 가진다.</p> <p>SOLID 원칙에서 O 에 해당하는 <strong>Open Closed Principle</strong> 을 지키기 위해서 디자인 패턴 중 전략패턴을 사용하게 되는데, 생성자 주입을 사용하게 되면 전략패턴을 사용하게 된다.</p> <p><br /></p> <h3 id="setter-based-injection-수정자를-통한-주입">Setter Based Injection (수정자를 통한 주입)</h3> <p>의존관계 주입에는 크게 <mark>생성자 주입, 수정자 주입</mark> 두가지 방법이 있다.</p> <p>코드를 한번 보자. 클래스나 인터페이스 이름만 Controller, Service, ServiceImpl 로 지정했지 스프링과는 상관이 없는 순수 자바로만 짜여진 코드이다.</p> <p>먼저 수정자를 이용한 의존관계 주입을 보자.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Controller</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">Service</span> <span class="n">service</span><span class="o">;</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setService</span><span class="o">(</span><span class="nc">Service</span> <span class="n">service</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">service</span> <span class="o">=</span> <span class="n">service</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">callService</span><span class="o">()</span> <span class="o">{</span> <span class="n">service</span><span class="o">.</span><span class="na">doSomething</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Service</span> <span class="o">{</span> <span class="kt">void</span> <span class="nf">doSomething</span><span class="o">();</span> <span class="o">}</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ServiceImpl</span> <span class="kd">implements</span> <span class="nc">Service</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">doSomething</span><span class="o">()</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"ServiceImpl is doing something"</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Main</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Controller</span> <span class="n">controller</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Controller</span><span class="o">();</span> <span class="c1">// 어떤 구현체이든, 구현체가 어떤방법으로 구현되든 Service 인터페이스를 구현하기만 하면 된다.</span> <span class="n">controller</span><span class="o">.</span><span class="na">setService</span><span class="o">(</span><span class="k">new</span> <span class="nc">ServiceImpl1</span><span class="o">());</span> <span class="n">controller</span><span class="o">.</span><span class="na">setService</span><span class="o">(</span><span class="k">new</span> <span class="nc">ServiceImpl2</span><span class="o">());</span> <span class="n">controller</span><span class="o">.</span><span class="na">setService</span><span class="o">(</span><span class="k">new</span> <span class="nc">Service</span><span class="o">()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">doSomething</span><span class="o">()</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Anonymous class is doing something"</span><span class="o">);</span> <span class="o">}</span> <span class="o">});</span> <span class="n">controller</span><span class="o">.</span><span class="na">setService</span><span class="o">(</span> <span class="o">()</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Lambda implementation is doing something"</span><span class="o">)</span> <span class="o">);</span> <span class="c1">// 어떻게든 구현체를 주입하고 호출하면 된다.</span> <span class="n">controller</span><span class="o">.</span><span class="na">callService</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>(참고) 익명클래스나 람다로 구현할 수 있었던 것은 Service 인터페이스가 함수형 인터페이스이기 때문이다.</p> <ul> <li><mark>Controller</mark> 클래스의 callService() 메소드는 <mark>Service 타입의 객체에 의존</mark>하고 있다.</li> <li>Service 는 인터페이스이고, 인터페이스는 인스턴스화 할 수 없으므로 인터페이스의 구현체가 필요하다.</li> <li>Service 인터페이스를 구현하기만 했다면 어떤 타입의 객체라도 Controller 에서 사용할 수 있는데 (다형성) Controller 는 이 구현체의 내부 동작을 <strong>아무 것도 알지 못하고 알 필요도 없다.</strong></li> <li>main 함수에서 Controller 클래스를 사용하는 것을 보면, 수정자 메소드인 setService() 에 Service 인터페이스의 구현체만 넘겨주면 된다.</li> </ul> <p><strong>어떤 구현체이든, 구현체가 어떤방법으로 구현되든, Service 인터페이스를 구현하기만 하면 된다.</strong></p> <blockquote> <p>신박하다?</p> </blockquote> <p>수정자 주입으로 의존관계 주입은 런타임시에 할 수 있도록 <mark>낮은 결합도</mark>를 가지게 구현되었다. 하지만 문제는 수정자를 통해서 Service 의 구현체를 주입해주지 않아도 Controller 객체는 생성가능하다. Controller 객체가 생성가능하다는 것은 내부에 있는 callService() 메소드도 호출 가능하다는 것인데, callService() 메소드는 service.doSomething() 을 호출하고 있으므로</p> <blockquote> <p>NullPointerException 이 발생한다.</p> </blockquote> <blockquote> <p>주입이 필요한 객체가 주입이 되지 않아도 얼마든지 객체를 생성할 수 있다는 것이 문제다.</p> </blockquote> <p>이 문제를 해결 할 수 있는 방법이 생성자 주입이다.</p> <p><br /></p> <h3 id="constructor-based-injection-생성자를-통한-주입">Constructor based Injection (생성자를 통한 주입)</h3> <p>Controller 에 setter 를 없애고, 생성자를 이용해서 주입한다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Controller</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">Service</span> <span class="n">service</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">Controller</span><span class="o">(</span><span class="nc">Service</span> <span class="n">service</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">service</span> <span class="o">=</span> <span class="n">service</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">callService</span><span class="o">()</span> <span class="o">{</span> <span class="n">service</span><span class="o">.</span><span class="na">doSomething</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>이렇게 생성자 주입을 해주면 사용하는 쪽은 아래와 같이 바뀐다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Main</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Controller controller = new Controller(); // 컴파일 에러</span> <span class="nc">Controller</span> <span class="n">controller1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Controller</span><span class="o">(</span><span class="k">new</span> <span class="nc">ServiceImpl</span><span class="o">());</span> <span class="nc">Controller</span> <span class="n">controller2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Controller</span><span class="o">(</span> <span class="o">()</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Lambda implementation is doing something"</span><span class="o">)</span> <span class="o">);</span> <span class="nc">Controller</span> <span class="n">controller3</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Controller</span><span class="o">(</span><span class="k">new</span> <span class="nc">Service</span><span class="o">()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">doSomething</span><span class="o">()</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Anonymous class is doing something"</span><span class="o">);</span> <span class="o">}</span> <span class="o">});</span> <span class="n">controller1</span><span class="o">.</span><span class="na">callService</span><span class="o">();</span> <span class="n">controller2</span><span class="o">.</span><span class="na">callService</span><span class="o">();</span> <span class="n">controller3</span><span class="o">.</span><span class="na">callService</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>이를 통해 두가지 이득과 한가지 보너스 이득이 생긴다.</p> <ol> <li>null 을 주입하지 않는 한 <mark>NullPointerException 은 발생하지 않는다.</mark></li> <li><mark>의존관계 주입을 하지 않은 경우</mark>에는 Controller <mark>객체를 생성할 수 없다.</mark> 즉, 의존관계에 대한 내용을 외부로 노출시킴으로써 컴파일 타임에 오류를 잡아낼 수 있다.</li> </ol> <p>보너스 이득은 final 을 사용할 수 있다는 것이다. final 로 선언된 레퍼런스타입 변수는 반드시 선언과 함께 초기화가 되어야 하므로 setter 주입시에는 의존관계 주입을 받을 필드에 final 을 선언할 수 없다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Controller</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Service</span> <span class="n">service</span><span class="o">;</span> <span class="c1">// final 추가</span> <span class="kd">public</span> <span class="nf">Controller</span><span class="o">(</span><span class="nc">Service</span> <span class="n">service</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">service</span> <span class="o">=</span> <span class="n">service</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">callService</span><span class="o">()</span> <span class="o">{</span> <span class="n">service</span><span class="o">.</span><span class="na">doSomething</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>final 의 장점은 누군가가 Controller 내부에서 service 객체를 바꿔치기 할 수 없다는 점이다.</p> <p>스프링에서 필드주입은 수정자를 통한 주입과 유사한 방식으로 이루어진다. 이제 슬슬 생성자 주입의 장점이 보이기 시작한다.</p> <p><br /></p> <h3 id="스프링에서의-di-방법-세가지">스프링에서의 DI 방법 세가지</h3> <p>스프링에서는 수정자 주입, 생성자 주입과 더불어 필드 주입이란걸 할 수 있다. 필드 주입은 수정자를 통한 주입과 유사한 방식으로 이루어지기 때문에, <mark>수정자를 통한 주입의 단점은 Field Injection 을 사용할 때의 단점을 그대로 가진다.</mark></p> <p>더불어, 수정자 주입은 스프링 컨테이너가 아닌 외부에서 수정자를 호출해서 주입할 수 있는 방법이라도 열려있지만, 필드주입은 스프링 컨테이너 말고는 외부에서 주입할 수 있는 방법이 없다.</p> <p>아래는 각 DI 방법에 대한 간단한 예제다. 뒤에서도 쓰기 위해서 예제를 Student, Course 관련된 내용으로 변경했다.</p> <p><strong>Field Injection</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">StudentServiceImpl</span> <span class="kd">implements</span> <span class="nc">StudentService</span> <span class="o">{</span> <span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">CourseService</span> <span class="n">courseService</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">studentMethod</span><span class="o">()</span> <span class="o">{</span> <span class="n">courseService</span><span class="o">.</span><span class="na">courseMethod</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><strong>Setter based Injection</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">StudentServiceImpl</span> <span class="kd">implements</span> <span class="nc">StudentService</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">CourseService</span> <span class="n">courseService</span><span class="o">;</span> <span class="nd">@Autowired</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setCourseService</span><span class="o">(</span><span class="nc">CourseService</span> <span class="n">courseService</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">courseService</span> <span class="o">=</span> <span class="n">courseService</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">studentMethod</span><span class="o">()</span> <span class="o">{</span> <span class="n">courseService</span><span class="o">.</span><span class="na">courseMethod</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><strong>Constructor based Injection</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">StudentServiceImpl</span> <span class="kd">implements</span> <span class="nc">StudentService</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CourseService</span> <span class="n">courseService</span><span class="o">;</span> <span class="nd">@Autowired</span> <span class="kd">public</span> <span class="nf">StudentServiceImpl</span><span class="o">(</span><span class="nc">CourseService</span> <span class="n">courseService</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">courseService</span> <span class="o">=</span> <span class="n">courseService</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">studentMethod</span><span class="o">()</span> <span class="o">{</span> <span class="n">courseService</span><span class="o">.</span><span class="na">courseMethod</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>인텔리제이에서 보여주는 경고메시지는 위 두 예제 중 아래에 있는 Constructor based Injection 을 사용하라는 것이다.</p> <p>지금까지 살펴본 생성자 주입의 장점은</p> <ul> <li><mark>NullPointerException</mark> 을 방지할 수 있다.</li> <li>주입받을 필드를 <mark>final</mark> 로 선언 가능하다.</li> </ul> <p>정도인데 또 다른 장점을 소개하고자 한다. 이는 스프링에서만 유용한 방법인 것 같다.</p> <p><br /></p> <h3 id="생성자-주입을-이용한-순환참조-방지">생성자 주입을 이용한 순환참조 방지</h3> <p>개발하다보면 여러 서비스들 간에 의존관계가 생기게 되는 경우가 있다. 이 예제에서는 CourseService 에서 StudentService 에 의존하고, StudentService 가 CourseService 에 의존하는 경우를 볼 것이다.</p> <p><strong>Field Injection 의 경우</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CourseService</span> <span class="o">{</span> <span class="kt">void</span> <span class="nf">courseMethod</span><span class="o">();</span> <span class="o">}</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">CourseServiceImpl</span> <span class="kd">implements</span> <span class="nc">CourseService</span> <span class="o">{</span> <span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">StudentService</span> <span class="n">studentService</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">courseMethod</span><span class="o">()</span> <span class="o">{</span> <span class="n">studentService</span><span class="o">.</span><span class="na">studentMethod</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">StudentService</span> <span class="o">{</span> <span class="kt">void</span> <span class="nf">studentMethod</span><span class="o">();</span> <span class="o">}</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">StudentServiceImpl</span> <span class="kd">implements</span> <span class="nc">StudentService</span> <span class="o">{</span> <span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">CourseService</span> <span class="n">courseService</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">studentMethod</span><span class="o">()</span> <span class="o">{</span> <span class="n">courseService</span><span class="o">.</span><span class="na">courseMethod</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>이 상황은 StudentServiceImple 의 studentMethod() 는 CourseServiceImpl 의 courseMethod() 를 호출하고, CourseServiceImpl 의 courseMethod() 는 StudentServiceImple 의 studentMethod() 를 호출하고 있는 상황이다. 서로서로 주거니 받거니 호출을 반복하면서 끊임없이 호출하다가 결국 <mark>StackOverflowError</mark> 를 발생시키고 죽는다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2019-08-28 00:14:56.042 ERROR 46104 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause java.lang.StackOverflowError: null at com.yaboong.alterbridge.tmp.CourseServiceImpl.courseMethod(CourseServiceImpl.java:26) ~[classes/:na] at com.yaboong.alterbridge.tmp.StudentServiceImpl.studentMethod(StudentServiceImpl.java:25) ~[classes/:na] at com.yaboong.alterbridge.tmp.CourseServiceImpl.courseMethod(CourseServiceImpl.java:26) ~[classes/:na] at com.yaboong.alterbridge.tmp.StudentServiceImpl.studentMethod(StudentServiceImpl.java:25) ~[classes/:na] at com.yaboong.alterbridge.tmp.CourseServiceImpl.courseMethod(CourseServiceImpl.java:26) ~[classes/:na] … … … </code></pre></div></div> <p>이게 순환참조의 문제인데, 실제 코드가 호출이 되기 전까지는 아무것도 알지 못한다. 스프링 애플리케이션 구동도 너무나 잘된다. 여기서 궁금했던게 하나 있다.</p> <blockquote> <p>왜 빈 생성이 잘 되는거지…?</p> </blockquote> <p>수정자 주입이나 필드 주입시에 스프링 <mark>ApplicationContext</mark> 를 통해서 현재 로딩된 빈 목록을 출력하면 사이클 호출 로직을 가진 두개의 빈이 모두 떠있는 것을 확인할 수 있었다. 아니 사이클 호출을 하고 있는데 빈이 어떻게 생성될 수 있는거지? 생성은 안하고 빈 목록만 가지고 있다가 lazy 로딩하는 방식인건가? 근데 따로 lazy init 옵션을 주지 않으면 lazy 로딩은 적용 되지 않는다던데…?</p> <p>여기저기 물어보니 한분이 명쾌한 답변을 주셨는데 ‘아 멍청이’ 하는 생각이 들었다. (혹시 보고 계신다면 다시한번 감사드립니다 ㅋ ㅋ) <mark>객체생성시점에서 순환참조가 일어나는 것</mark>과 <mark>객체생성 후 비즈니스 로직상에서 순환참조가 일어나는 것</mark>은 완전히 다른 이야기인데, 하나로 묶어서 생각하고 있었기 때문에 이런 이상한 질문에 빠졌던 것이다.</p> <blockquote> <p>필드 주입이나, 수정자 주입은 객체 생성시점에는 순환참조가 일어나는지 아닌지 발견할 수 있는 방법이 없다.</p> </blockquote> <p><strong>Constructor based Injection 의 경우</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">CourseServiceImpl</span> <span class="kd">implements</span> <span class="nc">CourseService</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">StudentService</span> <span class="n">studentService</span><span class="o">;</span> <span class="nd">@Autowired</span> <span class="kd">public</span> <span class="nf">CourseServiceImpl</span><span class="o">(</span><span class="nc">StudentService</span> <span class="n">studentService</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">studentService</span> <span class="o">=</span> <span class="n">studentService</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">courseMethod</span><span class="o">()</span> <span class="o">{</span> <span class="n">studentService</span><span class="o">.</span><span class="na">studentMethod</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">StudentServiceImpl</span> <span class="kd">implements</span> <span class="nc">StudentService</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CourseService</span> <span class="n">courseService</span><span class="o">;</span> <span class="nd">@Autowired</span> <span class="kd">public</span> <span class="nf">StudentServiceImpl</span><span class="o">(</span><span class="nc">CourseService</span> <span class="n">courseService</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">courseService</span> <span class="o">=</span> <span class="n">courseService</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">studentMethod</span><span class="o">()</span> <span class="o">{</span> <span class="n">courseService</span><span class="o">.</span><span class="na">courseMethod</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>이 경우에도 애플리케이션이 구동이 잘 될까? 실행해보면 아래와 같은 로그가 찍히면서 앱 구동이 실패한다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*************************** APPLICATION FAILED TO START *************************** Description: The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | courseServiceImpl defined in file [/Users/yaboong/.../CourseServiceImpl.class] ↑ ↓ | studentServiceImpl defined in file [/Users/yaboong/.../StudentServiceImpl.class] └─────┘ </code></pre></div></div> <p>빈 생성시 아래와 같은 로직이 수행되면서 어떤 시점에 스프링이 그것을 캐치해서 순환참조라고 알려주는 것 같다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="nc">CourseServiceImpl</span><span class="o">(</span><span class="k">new</span> <span class="nc">StudentServiceImpl</span><span class="o">(</span><span class="k">new</span> <span class="nc">CourseServiceImpl</span><span class="o">(</span><span class="k">new</span> <span class="o">...)))</span> </code></pre></div></div> <p>이처럼 생성자 주입을 사용하면 객체 간 순환참조를 하고 있는 경우에 스프링 애플리케이션이 구동되지 않는다.</p> <blockquote> <p>컨테이너가 빈을 생성하는 시점에서 객체생성에 사이클관계가 생기기 때문이다!</p> </blockquote> <p>수정자 주입을 사용하면 아주 잘 구동되고 순환참조를 하고 있는 부분에 대한 호출이 이루어질 경우 StackOverflowError 를 뱉기 때문에, 오류를 뱉을 수 밖에 없는 로직을 품고 애플리케이션이 구동되는 것이다.</p> <p>마지막으로, 생성자 주입을 사용하면 단위테스트 작성하기가 좋아진다.</p> <p><br /></p> <h3 id="테스트-코드-작성하기-좋다">테스트 코드 작성하기 좋다</h3> <p>아직 테스트 코드를 열심히 짜보거나 하지는 않았지만, 요즘 테스트 코드의 중요성을 깨닫고 공부를 하고 있는 중이다. (참 일찍도 깨달았다 미련한 것)</p> <p><mark>CourserServiceImpl</mark> 이 가진 메소드들에 대해서 단위테스트를 수행하고 싶은 경우, field injection 을 사용해서 작성된 클래스라면 단위테스트시 의존관계를 가지는 객체를 생성해서 주입할 수가 없다. <strong>할 수 있는 방법이 없다!</strong> 스프링의 IoC 컨테이너가 다 생성해서 주입해 주는 방식이고 외부로 노출되어 있는 것이 하나도 없기 때문이다. 그래서 의존관계를 가지고 있는 메소드의 단위테스트를 작성하면 (courseMethod() 같은) <mark>NullPointerException</mark> 이 발생한다.</p> <p>하지만, constructor based injection 을 사용해 작성된 클래스라면 <mark>CourseServiceImpl</mark> 객체를 생성할 때 원하는 구현체를 넘겨주면 되고, 구현체를 넘겨주지 않은 경우에는 객체생성 자체가 불가능하기 때문에 테스트하기도 편하다.</p> <p><br /></p> <h3 id="요약">요약</h3> <p><mark>생성자 주입방식</mark>은 아래와 같은 장점을 가진다</p> <ul> <li>의존관계 설정이 되지 않으면 객체생성 불가 -&gt; 컴파일 타임에 인지 가능, NPE 방지</li> <li>의존성 주입이 필요한 필드를 final 로 선언가능 -&gt; Immutable</li> <li>(스프링에서) 순환참조 감지가능 -&gt; 순환참조시 앱구동 실패</li> <li>테스트 코드 작성 용이</li> </ul> <p><mark>필드 인젝션</mark>은 아래와 같은 장점을 가진다</p> <ul> <li>편하다는 것 말고는 없다</li> </ul> <p><br /></p> <h3 id="주저리">주저리</h3> <p>생성자 주입 방식의 장점으로… 주입받는 객체가 많아지는 경우 생성자가 길어지기 때문에 위기감을 느껴서 리팩토링을 하게 된다… SRP(Single Responsibility Principle) 이 깨진것을 파악할 수 있다.. 와 같은 이야기를 하는 곳도 있는데.. 뭔가.. 손꼽히는 장점이라고 하기에는 좀.. 우기는 것 같은 느낌이 있어서 그냥 내가 다른 자료들 찾아보면서 장점이라고 생각되는 것들 위주로 정리해봤다.</p> <p><br /></p> <h3 id="마무리">마무리</h3> <p>이제 생성자 주입을 써야할 이유가 생겼다 ~~~</p> <p><br /></p> <h3 id="참고한-자료">참고한 자료</h3> <ul> <li><a target="_blank" href="https://stackoverflow.com/questions/16426323/injecting-autowired-private-field-during-testing">[StackOverflow] Injecting @Autowired private field during testing</a></li> <li><a target="_blank" href="https://stackoverflow.com/questions/40620000/spring-autowire-on-properties-vs-constructor">[StackOverflow] Spring @Autowire on Properties vs Constructor</a></li> <li><a target="_blank" href="http://olivergierke.de/2013/11/why-field-injection-is-evil/">[Oliver Gierke] Why field injection is evil (이사람 스프링 컨트리뷰터임)</a></li> </ul> Thu, 29 Aug 2019 00:00:00 +0000 https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/ https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/ spring spring 자바 제네릭 이해하기 Part 1 <h3 id="개요">개요</h3> <ul> <li>제네릭이란?</li> <li>제네릭을 사용하는 이유</li> <li>제네릭을 사용할 수 없는 경우</li> <li>제네릭 메서드란?</li> <li>제네릭 타입 제한하기 (Bounded Type Parameter)</li> </ul> <!--more--> <p><br /></p> <h3 id="서론">서론</h3> <p>사실 제네릭을 공부할 생각은 없었다. 이미 잘 쓰고있고 잘 이해하고 있다고 착각(?) 했기 때문이다. 자바로 개발을 하면서 JDK 소스를 볼일이 한번씩 생기는데, 자바를 개발한 사람은 이걸 어떻게 만들었나 훑어보다가 ‘봐도 모르겠네~’ 하고 넘어가는게 습관처럼 되어 버렸다. 그러다가 JPA 를 사용하면서 Optional 을 사용하게 됐고, Optional 을 사용하다보니 람다와 스트림 API 를 사용하게 됐고, 그러다가 만난녀석이</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">ifPresent</span><span class="o">(</span><span class="nc">Consumer</span><span class="o">&lt;?</span> <span class="kd">super</span> <span class="no">T</span><span class="o">&gt;</span> <span class="n">consumer</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">value</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="n">consumer</span><span class="o">.</span><span class="na">accept</span><span class="o">(</span><span class="n">value</span><span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <p>이놈이다.</p> <p><code class="language-plaintext highlighter-rouge">Consumer&lt;? super T&gt;</code> 는 대체 뭐하는 놈일까 궁금해졌다. 항상 그냥 지나치다가 이번에는 좀 짚고 넘어가야겠다 싶어서 다시 공부해보았다. 이번 포스팅에서는 저놈이 뭐하는 놈인지 설명하지는 않고, 저놈이 뭐 하는 놈인지 이해하기 위한 배경지식을 쌓는 포스팅이다.</p> <p><br /></p> <h3 id="제네릭이란">제네릭이란</h3> <p>JDK1.5 에 처음 도입되었다.</p> <ul> <li>Generics add stability to your code by making more of your bugs detectable at compile time. – Oracle Javadoc</li> <li>제네릭(Generic)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다. – 생활코딩</li> <li>지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능이다. – 자바의 정석</li> </ul> <p>대충 읽어보면 뭔가 타입에 대한 정보를 동적으로 넘겨줄 수 있고, 런타임시에 발생할 수 있는 오류를 컴파일 타임에 발견할 수 있도록 하는 것 같다. 일단 이정도 이해하고 넘어가고 예제를 보면서 하나씩 뜯어보자.</p> <p><br /></p> <h3 id="제네릭을-사용하지-않는-경우-문제점">제네릭을 사용하지 않는 경우 문제점</h3> <p>자바에서 자주 사용하게 되는 ArrayList<E> 를 모방하여, 아주 간단하고 제네릭을 사용하지 않는 SimpleArrayList 를 만들어보자. 제네릭을 사용하지 않아도 여러가지 타입을 받아 저장할 수 있는 ArrayList 를 만들 수 있다. 모든 클래스는 Object 클래스를 상속받기 때문에 Object 타입으로 받으면 그 어떤 타입이라도 받을 수 있기 때문이다.</E></p> <p>제네릭을 사용하지 않고도 어떤 타입이든 5개 요소를 담을 수 있는 ArrayList 를 만들면 아래와 같다.</p> <p>(설명의 편의를 위해 capacity 가 다 찼을 때 array 를 resizing 하거나 하는 로직은 모두 빼고 보도록 하자. 혹시 궁금하다면… <a target="_blank" href="https://yaboong.github.io/data-structures/2018/02/08/array-and-java-array-list/">여기</a>와 <a target="_blank" href="https://yaboong.github.io/data-structures/2018/02/09/array-advanced-1-stack/">여기</a>를 참고하기 바란다.)</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SimpleArrayList</span> <span class="o">{</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">size</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">Object</span><span class="o">[]</span> <span class="n">elementData</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Object</span><span class="o">[</span><span class="mi">5</span><span class="o">];</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">add</span><span class="o">(</span><span class="nc">Object</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="n">elementData</span><span class="o">[</span><span class="n">size</span><span class="o">++]</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">get</span><span class="o">(</span><span class="kt">int</span> <span class="n">idx</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">elementData</span><span class="o">[</span><span class="n">idx</span><span class="o">];</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>이제 이걸 사용해보자.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SimpleArrayListTest</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="nc">SimpleArrayList</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleArrayList</span><span class="o">();</span> <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">50</span><span class="o">);</span> <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">100</span><span class="o">);</span> <span class="nc">Integer</span> <span class="n">value1</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Integer</span><span class="o">)</span> <span class="n">list</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="nc">Integer</span> <span class="n">value2</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Integer</span><span class="o">)</span> <span class="n">list</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">value1</span> <span class="o">+</span> <span class="n">value2</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>컴파일도 잘 되고 잘 동작하는 것을 확인할 수 있다.</p> <p>add() 메소드는 파라미터로 Object 를 받기 때문에 어떤 데이터타입도 모두 받을 수 있다. 그러므로 list.get() 부분에서 형변환만 잘 시켜주면 어떤 데이터 타입이든 저장할 수 있다. add(50) 에 들어가게될 50이 스트링으로 들어가게 됐다고 가정해보면 코드는 아래와 같이 될 것이다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SimpleArrayListTest</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="nc">SimpleArrayList</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleArrayList</span><span class="o">();</span> <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s">"50"</span><span class="o">);</span> <span class="c1">// 달라진부분</span> <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s">"100"</span><span class="o">);</span> <span class="c1">// 달라진부분</span> <span class="nc">Integer</span> <span class="n">value1</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Integer</span><span class="o">)</span> <span class="n">list</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="nc">Integer</span> <span class="n">value2</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Integer</span><span class="o">)</span> <span class="n">list</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">value1</span> <span class="o">+</span> <span class="n">value2</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>add() 메서드는 Object 타입은 모두 받을 수 있으므로 String, Integer 모두 인자로 줄 수 있다. get() 메서드도 Object 타입을 반환하기 때문에 <code class="language-plaintext highlighter-rouge">Integer value1 = (Integer) list.get(0);</code> 이라는 코드에는 문법적으로 아무런 문제가 없다.</p> <p>실제로도 컴파일이 잘 되는데, 실행하면 런타임에 아래와 같은 오류가 발생하게 된다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at com.example.java.generics.basic.SimpleArrayListTest.main(SimpleArrayListTest.java:11) </code></pre></div></div> <p>잘못된 타입캐스팅이 이루어졌다는 오류메시지이다. String 을 넣어놓고서 Integer 로 형변환했기 때문이다.</p> <p>(제네릭 없는 자바를 사용해보지는 않았지만) 위와같은 방식으로 사용하는 경우, 어떤 타입으로 형변환 할 수 있는지 조차 모호한 경우도 많기 때문에 잠재적인 오류를 가지고 있는 매우 좋지 않은 방식이다.</p> <p><br /></p> <blockquote> <p>하지만 컴파일 시점에서는 어떤 오류도 발생하지 않는다는 것이 문제다!</p> </blockquote> <p><br /></p> <p>이런 문제를 해결하기 위해 Integer 타입을 가질 수 있는 SimpleArrayList 를 만들고, String 타입을 가질 수 있는 SimpleArrayList 를 만들 수 있다.</p> <h5 id="simplearraylistforintegerjava">SimpleArrayListForInteger.java</h5> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SimpleArrayListForInteger</span> <span class="o">{</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">size</span><span class="o">;</span> <span class="kd">private</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">elementData</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="mi">5</span><span class="o">];</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">add</span><span class="o">(</span><span class="kt">int</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="n">elementData</span><span class="o">[</span><span class="n">size</span><span class="o">++]</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">get</span><span class="o">(</span><span class="kt">int</span> <span class="n">idx</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">elementData</span><span class="o">[</span><span class="n">idx</span><span class="o">];</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <h5 id="simplearraylistforstringjava">SimpleArrayListForString.java</h5> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SimpleArrayListForString</span> <span class="o">{</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">size</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">elementData</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="mi">5</span><span class="o">];</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">add</span><span class="o">(</span><span class="nc">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="n">elementData</span><span class="o">[</span><span class="n">size</span><span class="o">++]</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="nc">String</span> <span class="nf">get</span><span class="o">(</span><span class="kt">int</span> <span class="n">idx</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">elementData</span><span class="o">[</span><span class="n">idx</span><span class="o">];</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>코드의 중복이 생기기 시작한다. 같은 역할을 하는 메소드 add(), get() 이지만 두 군데에 생긴다. 메서드 파라미터 타입과 반환타입이 서로 달라서 인터페이스나 상속을 통해 해결할 수도 없다. 아니면 SimpleArrayList 생성시 변수명을 intList, stringList 처럼 변수명을 다르게 해서 표현할 수도 있을 것 같지만 그래도 좋은 해결방법은 아닌 것 같다.</p> <p><br /></p> <h3 id="제네릭을-사용해서-문제해결">제네릭을 사용해서 문제해결</h3> <h5 id="genericarraylistjava">GenericArrayList.java</h5> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">GenericArrayList</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">Object</span><span class="o">[]</span> <span class="n">elementData</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Object</span><span class="o">[</span><span class="mi">5</span><span class="o">];</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">size</span><span class="o">;</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">add</span><span class="o">(</span><span class="no">T</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="n">elementData</span><span class="o">[</span><span class="n">size</span><span class="o">++]</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="no">T</span> <span class="nf">get</span><span class="o">(</span><span class="kt">int</span> <span class="n">idx</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="o">(</span><span class="no">T</span><span class="o">)</span> <span class="n">elementData</span><span class="o">[</span><span class="n">idx</span><span class="o">];</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>&lt;T&gt; 로 표현한 것이 제네릭이다. GenericArrayList 는 객체를 생성할때 타입을 지정하면, 생성되는 오브젝트 안에서는 T 의 위치에 지정한 타입이 대체되어서 들어가는 것 처럼 컴파일러가 인식한다. 좀 더 정확하게 말하면, Raw 타입 으로 사용하는데 컴파일러에 의해 필요한 곳에 형변환 코드가 추가된다. (List&lt;String&gt; 을 List 로만 쓰는 것이 Raw 타입으로 사용하는 것이다)</p> <p>사용은 아래처럼 할 수 있다. 형변환이 필요없다는 것, 지정한 타입과 다른 타입의 참조변수를 선언하면 컴파일타임에 오류가 발생한다는 것이 중요포인트다.</p> <h6 id="testjava">Test.java</h6> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Test</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="nc">GenericArrayList</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">intList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GenericArrayList</span><span class="o">&lt;&gt;();</span> <span class="n">intList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span> <span class="n">intList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span> <span class="kt">int</span> <span class="n">intValue1</span> <span class="o">=</span> <span class="n">intList</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="c1">// 형변환이 필요없다</span> <span class="kt">int</span> <span class="n">intValue2</span> <span class="o">=</span> <span class="n">intList</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span> <span class="c1">// 형변환이 필요없다</span> <span class="c1">// String strValue = intList.get(0); // 컴파일에러</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>위 Test.java 파일을 컴파일하고, 컴파일한 Test.class 파일을 역컴파일하면 아래와 같은 결과를 볼 수 있다.</p> <h6 id="디컴파일-한-결과-testjava---testclass---decompile">디컴파일 한 결과 (Test.java -&gt; Test.class -&gt; decompile)</h6> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Test</span> <span class="o">{</span> <span class="nc">Test</span><span class="o">()</span> <span class="o">{</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">var0</span><span class="o">)</span> <span class="o">{</span> <span class="nc">GenericArrayList</span> <span class="n">var1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GenericArrayList</span><span class="o">();</span> <span class="c1">// 제네릭이 사라졌다</span> <span class="n">var1</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span> <span class="n">var1</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span> <span class="kt">int</span> <span class="n">var2</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Integer</span><span class="o">)</span><span class="n">var1</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="c1">// 형변환이 추가되었다</span> <span class="kt">int</span> <span class="n">var3</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Integer</span><span class="o">)</span><span class="n">var1</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span> <span class="c1">// 형변환이 추가되었다</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>GenericArrayList&lt;Integer&gt; 로 생성했던 타입파라미터가 사라지고, Raw 타입으로만 사용하는데, 값을 꺼내 쓰는 곳에 형변환 코드가 추가되었다. 제네릭을 사용하면 컴파일러가 형변환을 알아서 진행한다는 것을 확인했다.</p> <p><br /></p> <h3 id="한정적-타입-매개변수-bounded-type-parameter">한정적 타입 매개변수 (Bounded Type Parameter)</h3> <p>제네릭으로 사용될 타입 파라미터의 범위를 제한할 수 있는 방법이 있다.</p> <p>위에서 만든 GenericArrayList 가 Number 의 서브클래스만 타입으로 가지도록 하고 싶은 경우 아래와 같이 제네릭의 타입을 제한할 수 있다. (인터페이스나 클래스나 추상클래스나 모두 extends 를 사용한다)</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">GenericArrayList</span><span class="o">&lt;</span><span class="no">T</span> <span class="kd">extends</span> <span class="nc">Number</span><span class="o">&gt;</span> </code></pre></div></div> <p>위와 같이 정의했다면 GenericArrayList 에는 String 을 담을 수 없다.</p> <p>Number 의 상위클래스만 타입으로 가지도록 하고 싶은 경우 (적절한 예시는 아니지만) 아래와 같이 제네릭의 타입을 제한할 수 있다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">GenericArrayList</span><span class="o">&lt;</span><span class="no">T</span> <span class="kd">super</span> <span class="nc">Number</span><span class="o">&gt;</span> </code></pre></div></div> <p>바운디드 타입 파라미터가 사용되는 가장 흔한 예시는 Comparable 을 적용하는 경우다. <mark>T extends Comparable&lt;T&gt;</mark> 와 같이 정의하면 Comparable 인터페이스의 서브클래스들만 타입으로 사용하겠다는 것이다. Comparable 인터페이스를 구현하기 위해서는 compareTo() 메소드를 반드시 정의해야하기 때문에 Comparable 인터페이스를 구현한 클래스들은 비교가 가능한 타입이 된다.</p> <p>비교하는 로직이 들어간 클래스에는 비교가 가능한 타입들을 다루는 것이 맞을 것이다. 이를 강제하도록 할 수 있는게 바운디드 타입 파라미터이다.</p> <p><br /></p> <h3 id="제네릭을-사용할-수-없는-경우">제네릭을 사용할 수 없는 경우</h3> <p>GenericArrayList 를 정의할 때, 다른 부분에는 모두 T 를 사용했는데, 배열을 생성하는 부분에서는 T 를 사용하지 않고 Object 를 사용했고 get() 호출시 T 타입으로 형변환 하는 코드를 삽입했다.</p> <p>GenericArrayList 가 가지는 elementData 도 <mark>new T[5]</mark> 와 같이 생성하면 get() 메서드에서 (T) 로 형변환 하는 작업을 안해도 될텐데 왜 한걸까?</p> <p>그 이유는 new 연산자 때문이다. new 연산자는 heap 영역에 충분한 공간이 있는지 확인한 후 메모리를 확보하는 역할을 한다. 충분한 공간이 있는지 확인하려면 타입을 알아야한다. 그런데 컴파일 시점에 타입 T 가 무엇인지 알 수 없기 때문에 <mark>new T[5]</mark> 와 같이 <strong>제네릭으로 배열을 생성할 수는 없다.</strong></p> <p><strong>static 변수에도 제네릭을 사용할 수 없다.</strong> static 변수는 인스턴스에 종속되지 않는 클래스변수로써 모든 인스턴스가 공통된 저장공간을 공유하게 되는 변수이다.</p> <p>static 변수에 제네릭을 사용하려면, GenericArrayList&lt;Integer&gt; 에서는 Integer 타입으로, GenericArrayList&lt;String&gt; 에서는 String 타입으로 사용될 수 있어야 하는데 하나의 공유변수가, 생성되는 인스턴스에 따라 타입이 바뀐다는 개념 자체가 말이 안되는 것이다. 그래서 static 변수에는 제네릭을 사용할 수 없다.</p> <p>하지만, (아래에서 살펴보겠지만) <strong>static 메서드에는 제네릭을 사용할 수 있다.</strong></p> <p><br /></p> <h3 id="제네릭-메서드">제네릭 메서드</h3> <p>static 메서드에는 제네릭을 사용할 수 있다고 했는데 왜 그런 것일까? 이 질문에 대답하기 전에 제네릭 메서드가 무엇인지 먼저 살펴보자.</p> <p><br /></p> <h6 id="제네릭-메서드란">제네릭 메서드란</h6> <p>제네릭 메서드를 정의할때는 리턴타입이 무엇인지와는 상관없이 내가 제네릭 메서드라는 것을 컴파일러에게 알려줘야한다. 그러기 위해서 리턴타입을 정의하기 전에 제네릭 타입에 대한 정의를 반드시 적어야 한다.</p> <p>그리고 중요한 점이 제네릭 클래스가 아닌 일반 클래스 내부에도 제네릭 메서드를 정의할 수 있다. 그 말은, <mark>클래스에 지정된 타입 파라미터와 제네릭 메서드에 정의된 타입 파라미터는 상관이 없다</mark>는 것이다. 즉, 제네릭 클래스에 &lt;T&gt; 를 사용하고, 같은 클래스의 제네릭 메서드에도 &lt;T&gt; 로 같은 이름을 가진 타입파라미터를 사용하더라도 둘은 전혀 상관이 없다는 것을 의미한다.</p> <p><br /></p> <h6 id="static-메서드-with-제네릭">static 메서드 with 제네릭</h6> <p>바로 위에서, static 변수에는 제네릭을 사용할 수 없지만 static 메서드에는 제네릭을 사용할 수 있다고 했는데 왜 그런 것일까?</p> <p>앞서 말한 것 처럼 static 변수의 경우에 제네릭을 사용하면 여러 인스턴스에서 어떤 타입으로 공유되어야 할지 지정할 수가 없어서 사용할 수 없다. static 변수는 값 자체가 공유되기 때문이다. 값 자체가 공유되려면 타입에 대한 정보도 있어야 한다.</p> <p>하지만, static 메서드의 경우 메서드의 틀만 공유된다고 생각하면 된다. 그리고 그 틀 안에서 지역변수처럼 타입 파라미터가 다양하게 오가는 형태로 사용될 수 있는 것이다.</p> <p>이는 static 메서드가 아닌 인스턴스 메서드의 경우에도 마찬가지다. 클래스에 정의된 타입 파라미터와는 전혀 별개로 제네릭 메서드는 자신만의 타입파라미터를 가진다.</p> <p><br /></p> <h6 id="static-메서드-with-제네릭-주의사항">static 메서드 with 제네릭 주의사항</h6> <p>착각하면 안되는 것이, 아래와 같은 static 메서드는 허용되지 않는다. 아래 코드에는 두 가지 오류가 있는데, 첫번째는 param 의 타입인 T 를 알 수 없다는 것이고, 두번째는 T 의 타입을 알 수 없기 때문에 charAt() 메서드 호출이 불가능 하다는 것이다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">printFirstChar</span><span class="o">(</span><span class="no">T</span> <span class="n">param</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">param</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="mi">0</span><span class="o">));</span> <span class="o">}</span> </code></pre></div></div> <p>허용되지 않는 가장 중요한 이유는 제네릭 메서드가 아니기 때문이다. 리턴 타입 앞에 제네릭에 대한 선언이 없다.</p> <p>클래스에 표시하는 &lt;T&gt; 는 인스턴스 변수라고 생각하면 된다. 인스턴스가 생성될때마다 지정되는 것이기 때문이다. 그러므로, static 메서드에서 인스턴스 변수로 여겨지는 타입 파라미터를 사용하고 있으므로 컴파일 에러가 발생한다.</p> <p>static 메서드에서 제네릭을 사용하려면 아래처럼 제네릭 메서드로 정의해야 한다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span> <span class="kd">extends</span> <span class="nc">CharSequence</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="nf">printFirstChar</span><span class="o">(</span><span class="no">T</span> <span class="n">param</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">param</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="mi">0</span><span class="o">));</span> <span class="o">}</span> </code></pre></div></div> <p>제네릭 메서드 선언시 &lt;T&gt; 만 사용해도 상관없다. 위 예시의 경우 charAt() 메서드를 호출하기 위해서 CharSequence 의 서브타입만 가능하다는 제약을 넣은 것이다.</p> <p>printFirstChar() 제네릭 메서드를 GenericArrayList 에 정의해 주었다면 호출은 아래와 같이 하면 된다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">GenericArrayList</span><span class="o">.&lt;</span><span class="nc">String</span><span class="o">&gt;</span><span class="n">printFirstChar</span><span class="o">(</span><span class="s">"YABOONG"</span><span class="o">);</span> </code></pre></div></div> <p>그런데 여기서 “YABOONG” 을 통해 인자의 타입이 String 인 것을 컴파일러가 추론할 수 있으므로 &lt;String&gt; 은 생략가능하다. 대부분의 경우 타입추론이 가능하므로 아래와 같이 타입은 생략하고 호출할 수 있다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">GenericArrayList</span><span class="o">.</span><span class="na">printFirstChar</span><span class="o">(</span><span class="s">"YABOONG"</span><span class="o">);</span> </code></pre></div></div> <p><br /></p> <h6 id="tip">TIP</h6> <p><mark>printFirstChar(T param)</mark> 에서 param 변수의 타입은 T 로 아직 지정되지 않았음에도 <mark>param.charAt(0)</mark> 처럼 charAt() 메서드 호출이 가능한 이유는 <mark>&lt;T extends CharSequence&gt;</mark> 를 통해 CharSequence 인터페이스를 하위 클래스 타입만 받도록 제한했기 때문이다. CharSequence 인터페이스의 하위 클래스 타입이 되려면 charAt() 을 포함하여 CharSequence 에 정의된 메서드들을 반드시 구현해야 한다.</p> <p><br /></p> <h3 id="요약">요약</h3> <p>제네릭을 사용하는 이유는 아래와 같다.</p> <ul> <li>형변환이 필요없고, 타입안정성이 보장된다.</li> <li>코드의 재사용성이 높아진다.</li> </ul> <p><br /></p> <h3 id="참고한-자료">참고한 자료</h3> <ul> <li><a target="_blank" href="https://www.udemy.com/introduction-to-generics-in-java/">[Udemy] Introduction to Collections &amp; Generics in Java</a></li> <li><a target="_blank" href="https://www.tutorialspoint.com/java/java_generics.htm">[Tutorials Point] Java Generics</a></li> <li><a target="_blank" href="https://www.tutorialspoint.com/java_generics/java_generics_no_static.htm">[Tutorials Point] Java Generics - No Static field</a></li> <li><a target="_blank" href="https://docs.oracle.com/javase/tutorial/java/generics/index.html">[Oracle Javadoc] Generics</a></li> <li><a target="_blank" href="https://docs.oracle.com/javase/tutorial/java/generics/why.html">[Oracle Javadoc] Why Use Generics?</a></li> <li><a target="_blank" href="http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&amp;mallGb=KOR&amp;barcode=9788994492032&amp;orderClick=LAG&amp;Kc=">[도서] 자바의 정석</a></li> </ul> Sat, 19 Jan 2019 00:00:00 +0000 https://yaboong.github.io/java/2019/01/19/java-generics-1/ https://yaboong.github.io/java/2019/01/19/java-generics-1/ java generics java 스프링 - Spring Cloud Config 예제 <h3 id="개요">개요</h3> <ul> <li><a target="_blank" href="https://cloud.spring.io/spring-cloud-config/">Spring Cloud Config</a></li> <li>스프링 설정파일을 외부로 분리할 수 있는 스프링 클라우드 Config 에 대한 핸즈온 (Hands-On) 예제 <!--more--></li> </ul> <p><br /></p> <h3 id="spring-cloud-config-란">Spring Cloud Config 란</h3> <p>아래는 <a target="_blank" href="https://cloud.spring.io/spring-cloud-config/">Spring Cloud Config</a> 에 들어가면 나오는 설명 요약이다.</p> <ul> <li>Spring Cloud Config 는 분산 시스템에서 설정파일을 외부로 분리하는 것을 지원한다.</li> <li>Spring Cloud Config 를 사용하면 외부 속성을 중앙에서 관리할 수 있다.</li> <li>스프링 애플리케이션은 물론, 다양한 애플리케이션에서 동일하게 설정파일을 사용할 수 있다.</li> <li>설정파일 구성의 기본은 git 을 사용한다.</li> </ul> <p>필자의 수준으로 요약하면 스프링 프로젝트의 설정파일을 외부로 <mark>분리하여 다양한 환경에서 사용하도록 할 수 있고, 설정이 변경되었을 때 애플리케이션의 재배포 없이 적용가능하다</mark> 정도로 받아들였다.</p> <p>MSA 에서는 수많은 애플리케이션들이 생겨나게 되는데, 수많은 애플리케이션들의 설정파일을 한곳에서 중앙집중관리를 할 수 있도록 해주는 것이 장점인 것 같다.</p> <p><br /></p> <h3 id="tldr-too-long-didnt-read">TL;DR (Too Long; Didn’t Read)</h3> <p>바로 예제 돌려보고 시작하길 원하는 사람은 아래 Repository 를 참고하면 된다.</p> <ul> <li><a target="_blank" href="https://github.com/yaboong/spring-cloud-config-server">[Github] spring-cloud-config-server</a></li> <li><a target="_blank" href="https://github.com/yaboong/spring-cloud-config-client">[Github] spring-cloud-config-client</a></li> </ul> <p><br /></p> <h6 id="잡생각">잡생각</h6> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MSA(Micro Service Architecture: 마이크로 서비스 아키텍처) 공부중에 Spring Cloud Netflix 와 Spring Cloud Config 알게됨 MSA 뭐 있나? 그냥 쪼개면 되는거아냐? 하고 무식한 생각을 했던 자신에 대한 반성과 함께... 참 무지하다 갈길이 멀다... Spring Cloud Netflix 도 하나씩 사용해봐야겠다. 애플리케이션을 잘게 쪼개면서 생기는 다양한 문제들 을 해결하기 위한 도구로 (로그관리, 모니터링, 부하분산, 써킷브레이킹, 상태파악 등등 갈길이 멀다..) 자바진영에서는 Spring Cloud Netflix 를 많이 사용하는 것 같다. MSA 를 공부하다보니... 최근(?)에 화두가 되는 * CI / CD / Docker / Kubernetes * ELK(ElasticSearch Logstash Kibana) * TDD * Spring Cloud / Spring Boot 등등 다양한 기술들이 왜 그렇게 화두가 되는지 이제 아주 아주 아~~주 조금 알 것 같다. </code></pre></div></div> <p><br /></p> <h3 id="살펴볼-내용">살펴볼 내용</h3> <p>총 3가지를 만들 것이다.</p> <ol> <li>Github Repository 에 설정파일 저장</li> <li>Spring Cloud Config Server (이하 Config Server) - 설정파일 배달부</li> <li>Spring Cloud Config Client (이하 Config Client) - 설정파일 사용자</li> </ol> <p>클라이언트 앱이 기동할 때 사용하는 설정파일은 Config Server 에서 돌려준 설정파일을 사용할 것인데, Config Server 에서 돌려줄 설정파일이 Github Repository 에 세팅해 둔 설정파일이 되도록 할 것이다.</p> <p><br /></p> <h3 id="1-github-repository-에-yaml-파일-저장">1. Github Repository 에 YAML 파일 저장</h3> <p>Github Repository 를 하나 파고 아래 두 파일을 만들어 저장한다. YAML 파일이름의 경우 ${ApplicationName}-${EnvironmentName}.yml 로 해주는 것이 디폴트이다. 왜 그렇게 하는지는 아래 <mark>3. Spring Cloud Config Client</mark> 세팅에서 설명한다.</p> <h6 id="yaboong-devyml">yaboong-dev.yml</h6> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">who</span><span class="pi">:</span> <span class="na">am</span><span class="pi">:</span> <span class="na">i</span><span class="pi">:</span> <span class="s">dev-yaboong</span> </code></pre></div></div> <h6 id="yaboong-liveyml">yaboong-live.yml</h6> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">who</span><span class="pi">:</span> <span class="na">am</span><span class="pi">:</span> <span class="na">i</span><span class="pi">:</span> <span class="s">live-yaboong</span> </code></pre></div></div> <p><br /></p> <h3 id="2-spring-cloud-config-server">2. Spring Cloud Config Server</h3> <h5 id="2-1-dependency-추가">2-1. Dependency 추가</h5> <p>클라이언트에게 설정파일에 대한 요청을 반환해 때, 앞서 Github Repository 에 만들어 둔 설정파일을 반환하는 서버를 만들 차례다. 스프링 부트 프로젝트를 하나 생성하고 dependency 를 아래와 같이 설정해준다.</p> <div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">dependencies</span> <span class="o">{</span> <span class="n">implementation</span><span class="o">(</span><span class="s1">'org.springframework.cloud:spring-cloud-config-server'</span><span class="o">)</span> <span class="o">}</span> </code></pre></div></div> <p><br /></p> <h5 id="2-2-spring-cloud-config-server-설정파일-설정">2-2. Spring Cloud Config Server 설정파일 설정</h5> <p><mark>src/main/resources/application.yml</mark> 파일을 아래와 같이 설정해준다. 스프링 부트 프로젝트를 생성하면 자동으로 생성되는 application.properties 파일을 사용해도 되지만 YAML 파일이 타이핑을 덜 해도 돼서 좋다.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spring</span><span class="pi">:</span> <span class="na">cloud</span><span class="pi">:</span> <span class="na">config</span><span class="pi">:</span> <span class="na">server</span><span class="pi">:</span> <span class="na">git</span><span class="pi">:</span> <span class="na">uri</span><span class="pi">:</span> <span class="s">https://github.com/yaboong/spring-cloud-config-repository</span> </code></pre></div></div> <p>uri 에는 1번 단계에서 생성한 Github Repository 의 uri 를 적어준다. 환경변수로 SPRING_PROFILES_ACTIVE 를 별도 지정하지 않았다면 디폴트 설정파일인 application.yml 을 읽어 실행하게 된다.</p> <p>(참고로 SPRING_PROFILES_ACTIVE 를 test 로 지정하고 실행하면 스프링 부트 앱 기동시 application-test.yml 파일을 찾아 실행한다)</p> <p><br /></p> <h5 id="2-3-yourapplicationjava">2-3. YourApplication.java</h5> <p>스프링 부트 프로젝트를 생성하면 자동 생성되는 main() 함수가 담긴 클래스에 아래와 같이 <mark>@EnableConfigServer</mark> 어노테이션을 추가해준다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@SpringBootApplication</span> <span class="nd">@EnableConfigServer</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigServerApplication</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="nc">SpringApplication</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">ConfigServerApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><br /></p> <h5 id="2-4-실행">2-4. 실행</h5> <p>실행로그 중 Github 에 만들어둔 yaboong-dev.yml, yaboong-live.yml 파일을 가져와서 어딘가에 Add 해 두는 것을 볼 수 있다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Adding property <span class="nb">source</span>: file:/var/folders/mz/7nk42g591rzd1v3x18ln5jpm0000gn/T/config-repo-8358493613395248430/yaboong-dev.yml Adding property <span class="nb">source</span>: file:/var/folders/mz/7nk42g591rzd1v3x18ln5jpm0000gn/T/config-repo-8358493613395248430/yaboong-live.yml </code></pre></div></div> <p><br /></p> <h5 id="2-5-설정파일-가져오기">2-5. 설정파일 가져오기</h5> <p><strong>yaboong-dev.yml</strong></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> GET http://localhost:8080/yaboong/dev </code></pre></div></div> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yaboong"</span><span class="p">,</span><span class="w"> </span><span class="nl">"profiles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"dev"</span><span class="w"> </span><span class="p">],</span><span class="w"> </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ad12fd3da589a62c58c095b4c84a87345e8972e9"</span><span class="p">,</span><span class="w"> </span><span class="nl">"state"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nl">"propertySources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://github.com/yaboong/spring-cloud-config-repository/yaboong-dev.yml"</span><span class="p">,</span><span class="w"> </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"who.am.i"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dev-yaboong"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><strong>yaboong-live.yml</strong></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> GET http://localhost:8080/yaboong/live </code></pre></div></div> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yaboong"</span><span class="p">,</span><span class="w"> </span><span class="nl">"profiles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"live"</span><span class="w"> </span><span class="p">],</span><span class="w"> </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4d0b729cd29631d27f4ef68146c39beddd21572d"</span><span class="p">,</span><span class="w"> </span><span class="nl">"state"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nl">"propertySources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://github.com/yaboong/spring-cloud-config-repository/yaboong-live.yml"</span><span class="p">,</span><span class="w"> </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"who.am.i"</span><span class="p">:</span><span class="w"> </span><span class="s2">"live-yaboong"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>JSON 형태로 반환하기 때문에 다른 프레임워크나 언어로 된 애플리케이션에서도 가져다 쓸 수 있다. 이것으로 Github Repository 의 설정파일을 반환해주는 서버설정은 끝이다.</p> <p><br /></p> <h3 id="3-spring-cloud-config-client">3. Spring Cloud Config Client</h3> <p>이제 설정파일을 Config Server 로부터 받아와서 애플리케이션을 빌드할 클라이언트를 만들차례다. Config Server 가 8080 포트를 사용중이므로 Config Client 는 8081 포트를 사용하도록 설정하겠다.</p> <h5 id="3-1-dependency-추가">3-1. Dependency 추가</h5> <div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">dependencies</span> <span class="o">{</span> <span class="n">implementation</span><span class="o">(</span><span class="s1">'org.springframework.boot:spring-boot-starter-web'</span><span class="o">)</span> <span class="n">implementation</span><span class="o">(</span><span class="s1">'org.springframework.cloud:spring-cloud-starter-config'</span><span class="o">)</span> <span class="n">implementation</span><span class="o">(</span><span class="s1">'org.springframework.boot:spring-boot-starter-actuator'</span><span class="o">)</span> <span class="o">}</span> </code></pre></div></div> <p>actuator 를 반드시 추가해주어야 한다.</p> <p><br /></p> <h5 id="3-2-spring-cloud-config-클라이언트-설정파일-설정">3-2. Spring Cloud Config 클라이언트 설정파일 설정</h5> <p>서버의 경우 application.yml 만 사용했지만, 클라이언트의 경우 application.yml 이 아닌 다른 설정파일을 사용할 것이다. bootstrap.yml 파일의 경우 스프링부트 앱 기동시 application.yml 보다 먼저 로드되므로 <mark>src/main/resources/bootstrap.yml</mark> 파일의 설정정보를 아래와 같이 수정해준다.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">server</span><span class="pi">:</span> <span class="na">port</span><span class="pi">:</span> <span class="m">8081</span> <span class="na">spring</span><span class="pi">:</span> <span class="na">application</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="s">yaboong</span> <span class="na">cloud</span><span class="pi">:</span> <span class="na">config</span><span class="pi">:</span> <span class="na">uri</span><span class="pi">:</span> <span class="s">http://localhost:8080</span> </code></pre></div></div> <p><mark>server.port</mark> 값은 8081 에 Config Client 를 띄울것이라는 설정이고, Spring Cloud Config Client 와는 전혀 무관한 설정이다. 그냥 Config Server 가 8080 포트를 사용중이므로 충돌을 피하기 위함이다.</p> <p><mark>spring.application.name</mark> 값은 1번 단계에서 설정파일의 이름을 ${ApplicationName}-${EnvironmentName}.yml 으로 하는 것이 디폴트라고 한 것과 상관있다.</p> <p>$SPRING_PROFILES_ACTIVE 값을 dev 로 설정한 경우, 위 설정을 바탕으로한다면 <mark>http://localhost:8080/yaboong/dev</mark> 의 설정을 읽어와 애플리케이션을 실행하게 된다.</p> <p><mark>spring.application.name</mark> 값을 yaboong 이 아닌 yb 로 한다면, http://localhost:8080 에 떠있는 Config Server 가 바라보고 있는 Github Repository 에 yb-dev.yml 파일이 있어야 한다.</p> <p><br /></p> <h5 id="3-3-설정파일을-내용을-동적으로-반영할-컨트롤러-작성">3-3. 설정파일을 내용을 동적으로 반영할 컨트롤러 작성</h5> <p>아래와 같이 컨트롤러를 하나 만들어 준다. Github Repository 에 만들어둔 설정파일에서 who.am.i 값을 읽어와 반환하는 Rest Controller 이다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span> <span class="nd">@RefreshScope</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigClientController</span> <span class="o">{</span> <span class="nd">@Value</span><span class="o">(</span><span class="s">"${who.am.i}"</span><span class="o">)</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">identity</span><span class="o">;</span> <span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/test"</span><span class="o">)</span> <span class="kd">public</span> <span class="nc">String</span> <span class="nf">test</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">identity</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>포인트는 <mark>@RefreshScope</mark> 어노테이션이다. 이 어노테이션이 붙은 클래스에는 <strong>특정행동</strong> 수행시 변경된 설정파일의 설정이 애플리케이션의 재배포과정 없이 실시간으로 반영된다.</p> <p><br /></p> <h5 id="3-4-설정파일-변경내용-반영을-위한-설정추가">3-4. 설정파일 변경내용 반영을 위한 설정추가</h5> <p>Github Repository 의 설정파일을 변경하고 Push 하면 바로 설정이 반영되면 좋겠지만 그렇게 되지는 않는다. 직전 단계 (3-3) 에서 <strong>특정행동</strong> 수행시 라고 했는데, 설정파일이 변경되면 변경사항을 반영하기위해 <mark>Config Client</mark> 에 POST 요청을 하나 날려줘야 한다.</p> <p>일단 아래와 같이 application.yml 파일을 설정해두고 애플리케이션을 실행시키고 어떻게 되는지 살펴보자.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">management</span><span class="pi">:</span> <span class="na">endpoints</span><span class="pi">:</span> <span class="na">web</span><span class="pi">:</span> <span class="na">exposure</span><span class="pi">:</span> <span class="na">include</span><span class="pi">:</span> <span class="s">refresh</span> </code></pre></div></div> <p>위와 같이 설정해두면 http://localhost:8081/actuator/refresh 로 POST 요청을 보내면 설정파일을 새로 읽어들여서 애플리케이션이 재기동된다.</p> <p>일단 클라이언트앱을 실행시켜보자. 실행시킬 때 SPRING_PROFILES_ACTIVE 값을 live 로 주고 실행시켜보자. Github Repository 에 있는 yaboong-live.yml 설정파일을 읽어와 애플리케이션을 실행하게 될 것이다.</p> <p>Intellij 에서 실행한다면, 우측상단의 Run 버튼 왼쪽의 YourApplication 을 클릭하고 Edit Configuration 으로 가서 Active profiles 값을 live 로 설정해주면 된다.</p> <p>CLI 에서 실행한다면, build.gradle 파일을 아래와 같이 추가설정을 해주고 (환경변수로 지정해도 된다)</p> <div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bootRun</span> <span class="o">{</span> <span class="n">systemProperties</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">properties</span> <span class="o">}</span> </code></pre></div></div> <p>프로젝트 디렉토리로 가서 아래와 같이 실행시켜도 된다.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gradle bootRun <span class="nt">-Dspring</span>.profiles.active<span class="o">=</span>live </code></pre></div></div> <p><br /></p> <h5 id="3-5-실행">3-5. 실행</h5> <p>실행로그 중 첫 세줄이 중요하다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Fetching config from server at : http://localhost:8080 Located environment: <span class="nv">name</span><span class="o">=</span>yaboong, <span class="nv">profiles</span><span class="o">=[</span>live], <span class="nv">label</span><span class="o">=</span>null, <span class="nv">version</span><span class="o">=</span>4d0b729cd29631d27f4ef68146c39beddd21572d, <span class="nv">state</span><span class="o">=</span>null Located property <span class="nb">source</span>: CompositePropertySource <span class="o">{</span><span class="nv">name</span><span class="o">=</span><span class="s1">'configService'</span>, <span class="nv">propertySources</span><span class="o">=[</span>MapPropertySource <span class="o">{</span><span class="nv">name</span><span class="o">=</span><span class="s1">'configClient'</span><span class="o">}</span>, MapPropertySource <span class="o">{</span><span class="nv">name</span><span class="o">=</span><span class="s1">'https://github.com/yaboong/spring-cloud-config-repository/yaboong-live.yml'</span><span class="o">}]}</span> </code></pre></div></div> <ul> <li>Config Server 로 떠있는 http://localhost:8080 에서 설정을 가져온다.</li> <li>가져올때는 http://localhost:8080/yaboong/live 로 요청을 보내 가져왔을 것이다.</li> <li>실제로 가져온 파일은 https://github.com/yaboong/spring-cloud-config-repository/yaboong-live.yml 이다.</li> </ul> <p>아래와 같이 /test 로 GET 요청을 보내면, Github Repository 에 있는 yaboong-live.yml 파일의 who.am.i 필드값을 반환하는 것을 확인할 수 있다. (좀 더 정확히 말하면 Config Server 에 캐시해둔 yaboong-live.yml 파일이 되겠다)</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> GET http://localhost:8081/test live-yaboong </code></pre></div></div> <p><br /></p> <h5 id="3-6-github-repository-의-설정파일-변경">3-6. Github Repository 의 설정파일 변경</h5> <p>yaboong-live.yml 파일의 내용을 변경하고 클라이언트 애플리케이션에 반영되는 것을 보자.</p> <p>파일 내용을 간단하게 아래와 같이 수정하고 push 한다.</p> <p><strong>yaboong-live.yml</strong></p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">who</span><span class="pi">:</span> <span class="na">am</span><span class="pi">:</span> <span class="na">i</span><span class="pi">:</span> <span class="s">live-yaboong, it's working!</span> </code></pre></div></div> <p>이제 클라이언트 애플리케이션의 /actuator/refresh 로 POST 요청을 보내서 변경된 설정파일이 적용되도록 해보자.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> POST http://localhost:8081/actuator/refresh </code></pre></div></div> <p>클라이언트 애플리케이션의 실행로그를 보고 있다면, POST 요청을 받자마자 설정파일을 Config Server 로부터 새로 받아와서 애플리케이션이 재시작 되는 것을 볼 수 있다. 변경된 설정의 경우 <mark>@RefreshScope</mark> 어노테이션이 적용되어 있는 클래스에만 적용된다.</p> <p>이제 다시 /test 로 요청을 보내서 새로운 설정을 읽어왔는지 확인해보면</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> GET http://localhost:8081/test live-yaboong, it<span class="s1">'s working! </span></code></pre></div></div> <p><br /></p> <p>이렇게 Spring Cloud Config 에 대한 헬로월드는 마무리… 신기하당</p> <p><br /></p> <h3 id="참고한-자료">참고한 자료</h3> <ul> <li><a target="_blank" href="https://cloud.spring.io/spring-cloud-config/">[spring.io] Spring Cloud Config</a></li> <li><a target="_blank" href="http://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.1.0.M3/">[cloud.spring.io] spring-cloud-config document</a></li> </ul> Sun, 25 Nov 2018 00:00:00 +0000 https://yaboong.github.io/spring-cloud/2018/11/25/spring-cloud-config/ https://yaboong.github.io/spring-cloud/2018/11/25/spring-cloud-config/ spring-cloud spring-cloud-config spring-cloud 디자인패턴 - 프록시 패턴 <h3 id="개요">개요</h3> <ul> <li>프록시 패턴에 대해 알아본다.</li> <li><a target="_blank" href="https://www.coursera.org/learn/design-patterns">Coursera 의 디자인패턴 강의</a> 를 기반으로 작성했다. <!--more--></li> </ul> <p><br /></p> <h3 id="프록시-패턴">프록시 패턴</h3> <p>주체가 되는 클래스는 민감한 정보를 가지고 있거나, 인스턴스화 하기에는 resource intensive (자원 집약적인.. 뭔가 무거운 느낌이라고 생각하면 됨) 한 클래스일 수 있다. 프록시 클래스가 Wrapper 의 역할을 함으로써, 클라이언트에서는 프록시 클래스를 통해 간접적으로 주체 클래스를 사용하는 방식이다.</p> <blockquote> <p>간단하게 말해서, 왕고한테 물어보기 전에 사수한테 먼저 물어보는 방식이다.</p> </blockquote> <p>짬찌가 클라이언트라면, 짬찌의 맞고참이 프록시고, 최고참이 주체클래스인 셈이다.</p> <p><br /></p> <h3 id="프록시가-사용되는-세가지-방법">프록시가 사용되는 세가지 방법</h3> <h6 id="1-virtual-proxy">1. Virtual Proxy</h6> <p>주체 클래스가 리소스 집약적인 경우이다. 예를들어, 주체 클래스가 해상도가 아주 높은 이미지를 처리해야 하는 경우 인스턴스화 할때 많은 메모리를 사용하게 되는데, 이런 이미지들에 동시에 많은 접근이 이루어진다면 시스템에 부하가 많이 가게 될 것이다. 프록시 클래스에서 자잘한 작업들을 처리하고 리소스가 많이 요구되는 작업들이 필요할 때에만 주체 클래스를 사용하도록 구현할 수 있다.</p> <h6 id="2-protection-proxy">2. Protection Proxy</h6> <p>주체 클래스에 대한 접근을 제어하기 위한 경우이다. 프록시 클래스에서 클라이언트가 주체 클래스에 대한 접근을 허용할지 말지 결정하도록 할 수 있다. 어떤 접근권한을 가지고 있는지에 따라 그에 맞는 주체 클래스의 메소드를 호출하도록 구현할 수 있다.</p> <h6 id="3-remote-proxy">3. Remote Proxy</h6> <p>프록시 클래스는 로컬에 두고, 주체 클래스는 Remote 로 존재하는 경우이다. Google Docs 같은 것이 대표적인 예시이다. 브라우저는 브라우저대로 필요한 자원을 로컬에 가지고 있고, 또다른 일부 자원은 Google 서버에 있는 형태이다.</p> <p><br /></p> <h3 id="프록시-패턴-overview">프록시 패턴 Overview</h3> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/diagram/proxy-pattern-1.png" target="_blank"> <img src="/yaboong-blog-static-resources/diagram/proxy-pattern-1.png" alt="Proxy" style="height:; width:" /> </a> <figcaption class="caption-text">Proxy and Real Subject</figcaption> </figure> <p>위 그림과 같이 프록시 클래스는 주체 클래스를 감싸면서 클라이언트의 요청을 주체 클래스에게 위임하거나 리다이렉트한다. 프록시 클래스는 주체 클래스 경량화된 버전으로 사용되기 때문에 항상 모든 요청을 위임하는 것은 아니고, 보다 실질적인 (or 독립적인) 요청들을 주체 클래스에 위임하는 것이다.</p> <p>주체 클래스에게 요청을 위임하기 위해서 프록시는 주체 클래스와 같은 인터페이스를 구현함으로써 Polymorphism (다형성) 을 가지게 된다. 즉, 클라이언트에서는 프록시와 주체 클래스의 인터페이스 타입으로 접근하여 사용할 수 있게 된다.</p> <p><br /></p> <h3 id="프록시-패턴-사용예제">프록시 패턴 사용예제</h3> <h6 id="scenario">«Scenario»</h6> <p>글로벌 유통 및 창고를 갖춘 커머스를 운영한다고 가정하자. 주문을 처리하기 위해서는 주문을 전달할 창고를 지정해야한다. 이때 주문서에 재고가 없는 창고로 주문 전달되지 않도록 어떤 창고에 주문을 보낼지 결정할 방법이 필요하다. 주문의 전체 처리를 적절한 창고로 라우트하는 시스템을 구성하여, 재고가 없는 창고에는 주문을 넣지 않도록 하고 싶다.</p> <p>이 경우는 Protection Proxy 로써 프록시 패턴이 사용되는 경우라고 할 수 있다. Warehouse(창고) 로 전달되는 요청을 프록시에서 걸러서 보냄으로써 Warehouse 에 처리할 수 없는 요청이 가지 않도록 막을 수 있다.</p> <figure class="caption" style="text-align:center;"> <a href="/yaboong-blog-static-resources/diagram/proxy-pattern-2.png" target="_blank"> <img src="/yaboong-blog-static-resources/diagram/proxy-pattern-2.png" alt="Proxy" style="height:; width:" /> </a> <figcaption class="caption-text">Proxy and Real Subject</figcaption> </figure> <p><br /></p> <h6 id="1-iorder-interface">1. IOrder «Interface»</h6> <p>클라이언트 소프트웨어가 시스템과 상호작용할 인터페이스를 정의하는 것이 가장 첫 단계다. 이 인터페이스는 <mark>OrderFulfillment</mark>, <mark>Warehouse</mark> 클래스가 구현한다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">IOrder</span> <span class="o">{</span> <span class="kt">boolean</span> <span class="nf">fulfillOrder</span><span class="o">(</span><span class="nc">Order</span> <span class="n">order</span><span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <p><br /></p> <h6 id="2-warehouse---주체클래스">2. Warehouse - 주체클래스</h6> <p>두번째 단계는 주체 클래스를 구현하는 것이다. 이 주체 클래스는 실질적으로 주문을 처리하는 구현 메소드를 가지며, 프록시 클래스에서 주문이 가능한지 확인할때 사용할 메소드를 가지고 있다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Warehouse</span> <span class="kd">implements</span> <span class="nc">IOrder</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">Hashtable</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">stock</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">address</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">fulfillOrder</span><span class="o">(</span><span class="nc">Order</span> <span class="n">order</span><span class="o">)</span> <span class="o">{</span> <span class="k">for</span> <span class="o">(</span><span class="nc">Item</span> <span class="nl">item:</span> <span class="n">order</span><span class="o">.</span><span class="na">getItemList</span><span class="o">())</span> <span class="o">{</span> <span class="nc">Integer</span> <span class="n">sku</span> <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="na">getSku</span><span class="o">();</span> <span class="k">this</span><span class="o">.</span><span class="na">stock</span><span class="o">.</span><span class="na">replace</span><span class="o">(</span><span class="n">sku</span><span class="o">,</span> <span class="n">stock</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">sku</span><span class="o">)</span> <span class="o">-</span> <span class="mi">1</span><span class="o">);</span> <span class="cm">/* 포장, 배송 등 기타 작업들이 추가적으로 이루어질 수 있음 */</span> <span class="n">processOne</span><span class="o">();</span> <span class="n">processTwo</span><span class="o">();</span> <span class="n">processThree</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">currentInventory</span><span class="o">(</span><span class="nc">Item</span> <span class="n">item</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">stock</span><span class="o">.</span><span class="na">getOrDefault</span><span class="o">(</span><span class="n">stock</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">item</span><span class="o">.</span><span class="na">getSku</span><span class="o">()),</span> <span class="mi">0</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><br /></p> <h6 id="3-orderfulfillment---프록시-클래스">3. OrderFulfillment - 프록시 클래스</h6> <p>마지막으로 프록시 클래스를 구현한다. 프록시 클래스에서는 주문이 가능한지 확인하는 모든 작업이 이루어지며, 주문을 이행할 수 있는 경우에만 주체 클래스에 요청을 위임한다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderFulfillment</span> <span class="kd">implements</span> <span class="nc">IOrder</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Warehouse</span><span class="o">&gt;</span> <span class="n">warehouses</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">fulfillOrder</span><span class="o">(</span><span class="nc">Order</span> <span class="n">order</span><span class="o">)</span> <span class="o">{</span> <span class="k">for</span> <span class="o">(</span><span class="nc">Item</span> <span class="nl">item:</span> <span class="n">order</span><span class="o">.</span><span class="na">getItemList</span><span class="o">())</span> <span class="o">{</span> <span class="k">for</span> <span class="o">(</span><span class="nc">Warehouse</span> <span class="nl">warehouse:</span> <span class="n">warehouses</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">warehouse</span><span class="o">.</span><span class="na">currentInventory</span><span class="o">(</span><span class="n">item</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="n">warehouse</span><span class="o">.</span><span class="na">fulfillOrder</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>이렇게 프록시 클래스로 인해 주문 유효성 검사, 주문의 이행 두 부분으로 분리할 수 있다. 주체 클래스에서는 유효성 검증 작업을 할 필요가 없으며, 주체 클래스의 각 인스턴스인 창고들에서는 재고가 없을 경우에 대한 처리를 신경쓸 필요가 없다.</p> <p><br /></p> <h3 id="정리--요약">정리 &amp; 요약</h3> <p>프록시 클래스가 주체 클래스를 감싸는 것을 통해,</p> <ul> <li>Polymorphism(다형성)을 가지도록 디자인함으로써 클라이언트가 하나의 인터페이스 접근할 수 있으며,</li> <li>리소스 집약적인 객체가 실제로 필요해질 때까지 라이트한 버전의 프록시 클래스로 전처리 등 필요한 작업을 진행할 수 있고, <strong>–Virtual Proxy</strong></li> <li>클라이언트가 주체 클래스에 접근하는 것에 대한 제한이나 어떤 클라이언트인지에 따라 서로 다른 방식으로 요청이 처리되도록 할 수 있다. <strong>–Protection Proxy</strong></li> <li>또한, 동일한 물리적 또는 가상 공간에 있지 않은 시스템을 로컬에 있는 것 처럼 표현할 수 있다. <strong>–Remote Proxy</strong></li> </ul> <p><br /></p> <h3 id="참고한-자료">참고한 자료</h3> <ul> <li><a target="_blank" href="https://www.coursera.org/lecture/design-patterns/2-1-8-proxy-pattern-9Vb0W">[Coursera - University of Alberta] Design Patterns 2.1.8 – Proxy Pattern</a></li> </ul> Wed, 17 Oct 2018 00:00:00 +0000 https://yaboong.github.io/design-pattern/2018/10/17/proxy-pattern/ https://yaboong.github.io/design-pattern/2018/10/17/proxy-pattern/ design-pattern java oop design-pattern