# 루트 예외가 세우는 두 규칙
  MongoPersistenceException.java:6 /**
  MongoPersistenceException.java:7  * Root of the provider-stable MongoDB error hierarchy (design §15).
  MongoPersistenceException.java:8  *
  MongoPersistenceException.java:9  * <p>Two rules shape this class. First, the message is derived from {@link MongoFailureContext},
  MongoPersistenceException.java:10  * which can only hold bounded, data-free values — so no caller can accidentally put a document or a
  MongoPersistenceException.java:11  * query parameter into an exception message. Second, no constructor accepts a {@link Throwable}
  MongoPersistenceException.java:12  * cause: attaching the driver exception would re-expose everything the failure context deliberately
  MongoPersistenceException.java:13  * dropped, through {@code getCause()} and through every stack trace printer. The driver's
  MongoPersistenceException.java:14  * information survives as the error labels and server code already carried by the context.
  MongoPersistenceException.java:15  */
  MongoPersistenceException.java:16 public abstract class MongoPersistenceException extends RuntimeException {
  MongoPersistenceException.java:17 
  MongoPersistenceException.java:18   @Serial private static final long serialVersionUID = 1L;
  MongoPersistenceException.java:19 
  MongoPersistenceException.java:20   private final transient MongoFailureContext failureContext;
  MongoPersistenceException.java:21 
  MongoPersistenceException.java:22   protected MongoPersistenceException(String summary, MongoFailureContext failureContext) {
  MongoPersistenceException.java:23     super(
  MongoPersistenceException.java:24         summary + " [" + Objects.requireNonNull(failureContext, "failureContext").describe() + "]");
  MongoPersistenceException.java:25     this.failureContext = failureContext;
  MongoPersistenceException.java:26   }
  MongoPersistenceException.java:27 
  MongoPersistenceException.java:28   /** The bounded metadata for this failure. Never contains document or query data. */
  MongoPersistenceException.java:29   public MongoFailureContext failureContext() {
  MongoPersistenceException.java:30     return failureContext;
  MongoPersistenceException.java:31   }

# 그 규칙이 적용되는 범위
  MongoPersistenceException 을 상속하는 main 타입 : 22 개
    MongoBulkPartialFailureException.java
    MongoChangeHistoryLostException.java
    MongoConnectionException.java
    MongoCursorException.java
    MongoDataSchemaUnsupportedException.java
    MongoDocumentNotFoundException.java
    MongoDocumentTooLargeException.java
    MongoDuplicateKeyException.java
    MongoEncryptionException.java
    MongoOperationRejectedException.java
    MongoOptimisticConflictException.java
    MongoReadConcernException.java
    MongoResumeException.java
    MongoSchemaValidationException.java
    MongoServerSelectionException.java
    MongoShardRoutingException.java
    MongoTimeoutException.java
    MongoTransactionCommitUnknownException.java
    MongoTransactionTransientException.java
    MongoUnclassifiedFailureException.java
    MongoWriteConcernException.java
    MongoWriteConflictException.java

# 이 계층에서 원인을 받거나 설정하는 자리 전수
  Throwable 을 인자로 받는 생성자 줄 : 1 개
  initCause 를 부르는 줄 : 1 개
    MongoTimeoutException.java:25  public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) {
    MongoTimeoutException.java:27  initCause(cause);
  [대조] 그 검색이 훑은 파일 : 351 개
  [대조] 옆 계층 persistence-jpa 에서 같은 검색 : 19 줄

# 그 하나의 타입
  MongoTimeoutException.java:5 /**
  MongoTimeoutException.java:6  * The operation exceeded its deadline (design §15).
  MongoTimeoutException.java:7  *
  MongoTimeoutException.java:8  * <p>A timeout says when the client stopped waiting, not what the server did. A write that timed
  MongoTimeoutException.java:9  * out after being sent is {@code WRITE_RESULT_UNKNOWN} and must be reconciled; only a timeout that
  MongoTimeoutException.java:10  * fired before the command left is safe to replay.
  MongoTimeoutException.java:11  */
  MongoTimeoutException.java:12 public final class MongoTimeoutException extends MongoPersistenceException {
  MongoTimeoutException.java:13 
  MongoTimeoutException.java:14   @Serial private static final long serialVersionUID = 1L;
  MongoTimeoutException.java:15 
  MongoTimeoutException.java:16   public MongoTimeoutException(MongoFailureContext failureContext) {
  MongoTimeoutException.java:17     super("the MongoDB operation exceeded its deadline", failureContext);
  MongoTimeoutException.java:18   }
  MongoTimeoutException.java:19 
  MongoTimeoutException.java:20   /**
  MongoTimeoutException.java:21    * Creates the exception from a client-side timeout.
  MongoTimeoutException.java:22    *
  MongoTimeoutException.java:23    * @param cause the underlying timeout, kept so a reactive stack trace still names its source
  MongoTimeoutException.java:24    */
  MongoTimeoutException.java:25   public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) {
  MongoTimeoutException.java:26     super("the MongoDB operation exceeded its deadline", failureContext);
  MongoTimeoutException.java:27     initCause(cause);
  MongoTimeoutException.java:28   }
  MongoTimeoutException.java:29 }

# 2 인자 생성자를 부르는 자리
    MongoTimeoutException.java:16  public MongoTimeoutException(MongoFailureContext failureContext) {
    MongoTimeoutException.java:25  public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) {
    DefaultMongoFailureTranslator.java:104  case TIMEOUT -> new MongoTimeoutException(failureContext);
    DefaultReactiveMongoExecutor.java:152  new dev.caskeleton.adapter.outbound.mongo.api.error.MongoTimeoutException(

  DefaultReactiveMongoExecutor.java:138       MongoOperationObservation observation,
  DefaultReactiveMongoExecutor.java:139       Throwable failure) {
  DefaultReactiveMongoExecutor.java:140     if (failure instanceof MongoPersistenceException alreadyTranslated) {
  DefaultReactiveMongoExecutor.java:141       if (observation != null) {
  DefaultReactiveMongoExecutor.java:142         observation.failure(alreadyTranslated.failureContext());
  DefaultReactiveMongoExecutor.java:143       }
  DefaultReactiveMongoExecutor.java:144       return alreadyTranslated;
  DefaultReactiveMongoExecutor.java:145     }
  DefaultReactiveMongoExecutor.java:146     if (failure instanceof java.util.concurrent.TimeoutException reactorTimeout) {
  DefaultReactiveMongoExecutor.java:147       // Reactor's own timeout, not the driver's. It used to escape as a raw
  DefaultReactiveMongoExecutor.java:148       // java.util.concurrent.TimeoutException — a type no caller of this platform is told about,
  DefaultReactiveMongoExecutor.java:149       // and one that carries no operation, no outcome and no observation. The failure was also
  DefaultReactiveMongoExecutor.java:150       // never recorded, because this method returned before reaching the observer.
  DefaultReactiveMongoExecutor.java:151       MongoPersistenceException timedOut =
  DefaultReactiveMongoExecutor.java:152           new dev.caskeleton.adapter.outbound.mongo.api.error.MongoTimeoutException(
  DefaultReactiveMongoExecutor.java:153               dev.caskeleton.adapter.outbound.mongo.api.error.MongoFailureContext.timedOut(
  DefaultReactiveMongoExecutor.java:154                   context.operationName(), operationType, context.timeout()),
  DefaultReactiveMongoExecutor.java:155               reactorTimeout);
  DefaultReactiveMongoExecutor.java:156       if (observation != null) {
  DefaultReactiveMongoExecutor.java:157         observation.failure(timedOut.failureContext());
  DefaultReactiveMongoExecutor.java:158       }
  DefaultReactiveMongoExecutor.java:159       return timedOut;
  DefaultReactiveMongoExecutor.java:160     }
  DefaultReactiveMongoExecutor.java:161     MongoException driverFailure = asDriverFailure(failure);
  DefaultReactiveMongoExecutor.java:162     if (driverFailure == null) {
  DefaultReactiveMongoExecutor.java:163       return failure;
  DefaultReactiveMongoExecutor.java:164     }
  DefaultReactiveMongoExecutor.java:165     MongoPersistenceException translated =
  DefaultReactiveMongoExecutor.java:166         translator.translate(
  DefaultReactiveMongoExecutor.java:167             context, operationType, Duration.ZERO, MongoDriverFailureView.from(driverFailure), 1);
  DefaultReactiveMongoExecutor.java:168     if (observation != null) {
  DefaultReactiveMongoExecutor.java:169       observation.failure(translated.failureContext().withTraceId(observation.traceId()));
  DefaultReactiveMongoExecutor.java:170     }
  DefaultReactiveMongoExecutor.java:171     return translated;
  DefaultReactiveMongoExecutor.java:172   }

# 생성자를 지나지 않고 드라이버 예외가 붙는 자리
  이 계층에서 addSuppressed 를 부르는 줄 : 3 개
    SpringMongoTransactionSessionFactory.java:88  startFailed.addSuppressed(closeFailed);
    SpringMongoTransactionSessionFactory.java:194  inFlight.addSuppressed(failure);
    SpringMongoTransactionSessionFactory.java:200  cleanupFailure.addSuppressed(failure);

  SpringMongoTransactionSessionFactory.java:159     public void abort() {
  SpringMongoTransactionSessionFactory.java:160       if (!committed && session.hasActiveTransaction()) {
  SpringMongoTransactionSessionFactory.java:161         session.abortTransaction();
  SpringMongoTransactionSessionFactory.java:162       }
  SpringMongoTransactionSessionFactory.java:163     }
  SpringMongoTransactionSessionFactory.java:164 
  SpringMongoTransactionSessionFactory.java:165     @Override
  SpringMongoTransactionSessionFactory.java:166     public void close() {
  SpringMongoTransactionSessionFactory.java:167       try {
  SpringMongoTransactionSessionFactory.java:168         abort();
  SpringMongoTransactionSessionFactory.java:169       } catch (RuntimeException abortFailed) {
  SpringMongoTransactionSessionFactory.java:170         recordCleanupFailure(abortFailed);
  SpringMongoTransactionSessionFactory.java:171       } finally {
  SpringMongoTransactionSessionFactory.java:172         try {
  SpringMongoTransactionSessionFactory.java:173           session.close();
  SpringMongoTransactionSessionFactory.java:174         } catch (RuntimeException closeFailed) {
  SpringMongoTransactionSessionFactory.java:175           recordCleanupFailure(closeFailed);
  SpringMongoTransactionSessionFactory.java:176         }

  SpringMongoTransactionSessionFactory.java:185     /**
  SpringMongoTransactionSessionFactory.java:186      * Keeps a cleanup failure without letting it become the outcome.
  SpringMongoTransactionSessionFactory.java:187      *
  SpringMongoTransactionSessionFactory.java:188      * <p>Attached to the failure already in flight when there is one, so the stack the caller sees
  SpringMongoTransactionSessionFactory.java:189      * still names what actually went wrong, with the failed abort alongside it rather than in place
  SpringMongoTransactionSessionFactory.java:190      * of it.
  SpringMongoTransactionSessionFactory.java:191      */
  SpringMongoTransactionSessionFactory.java:192     private void recordCleanupFailure(RuntimeException failure) {
  SpringMongoTransactionSessionFactory.java:193       if (inFlight != null) {
  SpringMongoTransactionSessionFactory.java:194         inFlight.addSuppressed(failure);
  SpringMongoTransactionSessionFactory.java:195         return;
  SpringMongoTransactionSessionFactory.java:196       }
  SpringMongoTransactionSessionFactory.java:197       if (cleanupFailure == null) {
  SpringMongoTransactionSessionFactory.java:198         cleanupFailure = failure;
  SpringMongoTransactionSessionFactory.java:199       } else {

  SpringMongoTransactionSessionFactory.java:212     private RuntimeException translate(Throwable failure, MongoFailurePhase phase) {
  SpringMongoTransactionSessionFactory.java:213       RuntimeException translated = classify(failure, phase);
  SpringMongoTransactionSessionFactory.java:214       inFlight = translated;
  SpringMongoTransactionSessionFactory.java:215       return translated;
  SpringMongoTransactionSessionFactory.java:216     }
  SpringMongoTransactionSessionFactory.java:217 
  SpringMongoTransactionSessionFactory.java:218     private RuntimeException translateCommit(Throwable failure) {
  SpringMongoTransactionSessionFactory.java:219       RuntimeException translated = classify(failure, MongoFailurePhase.TRANSACTION_COMMIT);
  SpringMongoTransactionSessionFactory.java:220       inFlight = translated;
  SpringMongoTransactionSessionFactory.java:221       return translated;
  SpringMongoTransactionSessionFactory.java:222     }
  SpringMongoTransactionSessionFactory.java:223 
  SpringMongoTransactionSessionFactory.java:224     private RuntimeException classify(Throwable failure, MongoFailurePhase phase) {
  SpringMongoTransactionSessionFactory.java:225       MongoDriverFailureView view = MongoFailureExtractor.viewOf(failure).orElse(null);
  SpringMongoTransactionSessionFactory.java:226       if (view == null) {
  SpringMongoTransactionSessionFactory.java:227         // Not a Mongo failure, or a cause chain that cannot be walked. Terminal is the fail-closed
  SpringMongoTransactionSessionFactory.java:228         // verdict when the alternative is replaying work.
  SpringMongoTransactionSessionFactory.java:229         return failure instanceof RuntimeException runtime
  SpringMongoTransactionSessionFactory.java:230             ? runtime
  SpringMongoTransactionSessionFactory.java:231             : new IllegalStateException("unclassifiable Mongo failure", failure);
  SpringMongoTransactionSessionFactory.java:232       }
  SpringMongoTransactionSessionFactory.java:233       // The phase is passed through: the same socket failure is a body replay before the commit
  SpringMongoTransactionSessionFactory.java:234       // and a commit-unknown during it, and the classifier could not tell them apart.
  SpringMongoTransactionSessionFactory.java:235       MongoRetryScope scope =
  SpringMongoTransactionSessionFactory.java:236           classifier
  SpringMongoTransactionSessionFactory.java:237               .classify(
  SpringMongoTransactionSessionFactory.java:238                   phase == MongoFailurePhase.TRANSACTION_COMMIT
  SpringMongoTransactionSessionFactory.java:239                       ? MongoOperationType.TRANSACTION_COMMIT
  SpringMongoTransactionSessionFactory.java:240                       : MongoOperationType.UPDATE,
  SpringMongoTransactionSessionFactory.java:241                   phase,
  SpringMongoTransactionSessionFactory.java:242                   view)
  SpringMongoTransactionSessionFactory.java:243               .retryScope();
  SpringMongoTransactionSessionFactory.java:244       String serverCode = view.hasServerCode() ? Integer.toString(view.serverCode()) : "";
  SpringMongoTransactionSessionFactory.java:245       // The context is derived from the scope, not built first and then labelled. Building a
  SpringMongoTransactionSessionFactory.java:246       // commit-unknown context and wrapping it in whichever exception the classifier chose is how
  SpringMongoTransactionSessionFactory.java:247       // a "replay the body" exception came to carry "unknown commit, not retryable, ambiguous".
  SpringMongoTransactionSessionFactory.java:248       if (scope == MongoRetryScope.COMMIT_ONLY) {
  SpringMongoTransactionSessionFactory.java:249         return new MongoTransactionCommitUnknownException(
  SpringMongoTransactionSessionFactory.java:250             MongoFailureContext.commitUnknown(SESSION_OPERATION, serverCode, Duration.ZERO),
  SpringMongoTransactionSessionFactory.java:251             "read the transaction record, version or idempotency key");
  SpringMongoTransactionSessionFactory.java:252       }
  SpringMongoTransactionSessionFactory.java:253       if (scope == MongoRetryScope.WHOLE_TRANSACTION) {
  SpringMongoTransactionSessionFactory.java:254         return new MongoTransactionTransientException(
  SpringMongoTransactionSessionFactory.java:255             MongoFailureContext.transientTransaction(SESSION_OPERATION, serverCode, Duration.ZERO));
  SpringMongoTransactionSessionFactory.java:256       }

# 그 규칙을 검사하는 시험
  이 계층의 시험에서 getCause 를 단언하는 줄 : 1 개
    MongoFailureContextTest.java:65  assertThat(exception.getCause()).isNull();
  [대조] test 소스 세트에서는 : 9 줄
  [대조] 시험 소스 세트를 모두 훑으면 : 9 줄
  [대조] 이 계층의 하위 타입 가운데 final 로 닫힌 것 : 22 개

  MongoFailureContextTest.java:56 
  MongoFailureContextTest.java:57   @Test
  MongoFailureContextTest.java:58   void exceptionsDoNotExposeADriverCause() {
  MongoFailureContextTest.java:59     MongoPersistenceException exception =
  MongoFailureContextTest.java:60         new MongoTransactionCommitUnknownException(
  MongoFailureContextTest.java:61             MongoFailureContext.commitUnknown(
  MongoFailureContextTest.java:62                 new MongoOperationName("order.reserve"), "251", Duration.ZERO),
  MongoFailureContextTest.java:63             "read the transaction record");
  MongoFailureContextTest.java:64 
  MongoFailureContextTest.java:65     assertThat(exception.getCause()).isNull();
  MongoFailureContextTest.java:66     assertThat(exception.category()).isEqualTo(MongoFailureCategory.TRANSACTION_COMMIT_UNKNOWN);
  MongoFailureContextTest.java:67   }
  MongoTimeoutException 이 나오는 시험 줄 : 0 개
  [대조] MongoTransactionCommitUnknownException 은 : 4 줄
