web-dev-qa-db-ja.com

例外をラップするための設計戦略

私は、フレームワーク/ライブラリにRepositoryのタイプを実装しています。

public interface FooRepository {

    boolean contains(String id);

    Foo fetch(String id);

    void commit(Foo foo):
}

使用しているストレージメディアに応じて、さまざまな方法で実装できます。例えば

public class FileFooRepositiory implements FooRepository {

    public boolean contains(String id) {
        return fileExistsInFileSystem(id);
    }

    public Foo fetch(final String id) {
        return parseFooFromFile(id);
    }

    public void commit(final Foo foo) {
        writeFooToFile(foo);
    }
}

しかし、

public class SQLFooRepositiory implements FooRepository {

    public SQLFooRepository(Connection connection) {
        // ...
    }

    public boolean contains(String id) {
        return fooExistsInDatabase(id);
    }

    public Foo fetch(final String id) {
        return parseFooFromDatabase(id);
    }

    public void commit(final Foo foo) {
        writeFooToDatabase(foo);
    }
}

FooオブジェクトをMap<String, Foo>に格納する実装や、外部ストレージシステム(appfabric、redisなど)を使用する実装も存在します。

ただし、これらの各実装が本来の動作を実行できない場合があります。それらはそれぞれ異なるタイプのExceptionをスローします。 SQLExceptionの場合、起こり得るほとんどのことは、この1つの例外を使用します。ファイルベースの場合、これはある種のIOException、具体的にはFileNotFoundExceptionAccessDeniedExceptionなどになります。

私の質問はこれです。FooRepositoryインターフェースの規約を変更して、throws Exception句を使用せずに、これらのタイプのExceptionsをスローできるようにするにはどうすればよいですか。私の最初の考えは、extend RuntimeExceptionで実際の例外をラップするRepositoryExceptionを持つことでした

try {
    // ...
}
catch (SQLException sqle) {
    throw new RepositoryException(sqle);
}

しかし、これが私が使用すべき適切な戦略であるかどうかはわかりません。

4
Zymus

あなたの解決策は正しいです:リポジトリは、一般的なRepositoryExceptionタイプをスローして、問題が発生したことを通知する必要があります。実際の例外をそのRepositoryExceptionでラップして、ロギングが必要なデータにアクセスできるようにし、コンシューマーが一般的な例外をトラップして、どこかで処理できるようにします。実際の例外をラップすると、具体的なリポジトリの実装の詳細がコンシューマから隠されます。

例外を区別する唯一の理由(たとえば、インターフェースから特定の例外を返す)は、1つの例外タイプの処理を他の例外タイプとは異なるものにする必要がある場合です。この場合、リポジトリからの例外を別の方法で処理する理由を実際に考えることはできないので、RepositoryExceptionを使用してください。

4
JDT

私はあなたの戦略は正しいと思います(そして「最高」に関しては意見が異なる場合があることを受け入れます)。

クライアントは、基盤となるリポジトリがファイルかDBサーバーかを気にする必要はありません。コミットが失敗したことを気にします-最初にそれを知って処理する必要があります。その後、必要に応じて(ユーザーの介入や詳細なログ記録が必要など)、根本的な問題に対処してください。その失敗を表す例外をスローする場合、RepositoryExceptionは正しいことです。ここで1つの例外で十分であれば、その1つだけを使用してください。エラーがより複雑な場合は、より多くの例外を含めますが、慎重に行ってください。

スローされる例外は、クライアントにリレーしたいエラー状態を表すことをお勧めします-根本的な原因だけでなく、そうであれば「セマンティックエラー」。内部例外(Java getCause)は、発生した障害の根本的な原因を表すのに適しています。

基本的に、例外は「リポジトリが利用できないため、これを実行できませんでした」であり、内部の例外は、「ユーザーが許可を得ていないため、ファイルが見つかりませんでした」です。

例外を定義すると、クライアントコードがエラーを修正するのに役立つエラーの説明に必要な詳細を提供することと、キャッチして処理する負担になるほど冗長にならないこととの間に細かい境界線が生じます。

1
Niall

行ったことがある。

私がしたことは次のとおりです:

FooRepositoryを実装するファイルリポジトリクラス:

_    try{            
       // try to store
    } catch (IOException e) {
        throw new CouldntStoreFooException(e.getLocalizedMessage());
    } 

    try{            
       // try to retrieve
    } catch (IOException e) {
        throw new CouldntRetrieveFooException(e.getLocalizedMessage());
    } 
_

FooRepositoryを実装するデータベースリポジトリクラス:

_    try{            
       // try to store
    } catch (SQLException e) {
        throw new CouldntStoreFooException(e.getLocalizedMessage());
    } etc

    try{            
       // try to retrieve
    } catch (SQLException e) {
        throw new CouldntRetrieveFooException(e.getLocalizedMessage());
    }
_

こうすることで、インターフェイスシグネチャはCouldntRetrieveFooExceptionまたはCouldntRetrieveFooExceptionのみを認識しますが、例外を取得してログを表示すると、元の問題(e.getLocalizedMessage())コンストラクタ内のメッセージとして。

例外は何もラップする必要はありません、ただ:

_public class CouldntStoreFooException extends Exception {

    public CouldntStoreFooException(String reason) {
        super("Problem retrieving foo "+reason);
    }

}
_

...

_public class CouldntRetrieveFooException extends Exception {

    public CouldntRetrieveFooException(String reason) {
        super("Problem retrieving foo "+reason);
    }

}
_
0

あなたのアプローチは正しく、リポジトリクライアントがリポジトリ例外を区別できるようにする必要がある場合、おそらくさまざまな種類のエラーを表示するために、リポジトリ例外のツリーを作成することもできます(常にRuntimeExceptionを継承) Spring does

public class IntegrityConstraintViolationextends RepositoryException {

}

public class Client1 {
  public void consumeRepository(){
    try {
      getRepository.fetch
    } 
    catch (IntegrityConstraintViolatione1){
      // do Something special if there is a problem with integrity violation
    }
    catch (RepositoryException e2){
      // do something in any other case
    }
  }
}

この場合、基盤となるリポジトリ実装に整合性制約違反を通知する機能が必要です。たとえば、JDBCドライバーはIntegrityConstraintViolationをスローします

try {
    // ...
}
catch (SQLIntegrityConstraintViolationException sqle) {
    throw new IntegrityConstraintViolation(sqle);
}

これの利点は、常に独自の例外階層ツリーで作業できることです。

0
gabrielgiussi