web-dev-qa-db-ja.com

複数のクライアントに対して同じソフトウェアの異なるカスタマイズバージョンを維持する方法

ニーズの異なる複数のクライアントがいます。私たちのソフトウェアはある程度モジュール化されていますが、すべてのモジュールのビジネスロジックをあちこちで調整する必要があることはほぼ確実です。変更はおそらく小さすぎて、モジュールをクライアントごとに個別の(物理)モジュールに分割することを正当化できません。ビルドの問題、リンクの混乱が心配です。ただし、これらの変更は大きすぎて多すぎるため、一部の構成ファイルでスイッチを使用して構成することはできません。これは、展開中に問題が発生し、特にティンカタイプの管理者に多くのサポートの問題が発生するためです。

ビルドシステムで、クライアントごとに1つずつ、複数のビルドを作成して、問題の単一の物理モジュールの特殊なバージョンに変更が含まれるようにしたいと考えています。だから私はいくつかの質問があります:

ビルドシステムに複数のビルドを作成させることをお勧めしますか?特にsvnをさまざまなカスタマイズをソース管理に保存するにはどうすればよいですか?

48
Falcon

必要なのは機能分岐のようなコード編成です。特定のシナリオでは、これをクライアント固有のトランク分岐と呼ぶ必要があります。これは、新しいものを開発する(またはバグを解決する)ときに機能分岐も使用する可能性が高いためです。 )。

http://svnbook.red-bean.com/en/1.5/svn.branchmerge.commonpatterns.html

アイデアは、新しい機能のブランチがマージされたコードトランクを持つことです。つまり、クライアント固有でないすべての機能です。

次に、クライアント固有のブランチもあり、同じ機能のブランチを必要に応じてマージします(必要に応じてチェリーピックします)。

これらのクライアントブランチは、一時的でも短期間でもありませんが、機能ブランチに似ています。それらは長期間維持され、主にマージされるだけです。クライアント固有の機能ブランチの開発はできるだけ少なくする必要があります。

クライアント固有のブランチはトランクへの並列ブランチであり、トランク自体がアクティブであり、どこにでも全体としてマージされることさえありません。

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´
8
Robert Koritnik

SCMブランチではこれを行わないでください。共通コードを、ライブラリーまたはプロジェクトのスケルトンアーティファクトを生成する個別のプロジェクトにします。各顧客プロジェクトは個別のプロジェクトであり、依存関係として共通のプロジェクトに依存します。

これは、共通の基本アプリがSpringなどの依存関係注入フレームワークを使用している場合に最も簡単です。カスタム機能が必要なときに、各顧客プロジェクトのオブジェクトのさまざまな置換バリアントを簡単に注入できます。 DIフレームワークがまだない場合でも、DIフレームワークを追加してこのようにすることは、最も苦痛のない選択かもしれません。

39
Alb

すべてのクライアントのソフトウェアを1つのブランチに格納します。異なるクライアントに対して行われた変更を分岐させる必要はありません。ほとんどの場合、ソフトウェアをすべてのクライアントにとって最高の品質にする必要があります。コアインフラストラクチャのバグ修正は、不要なマージオーバーヘッドなしに全員に影響を与えるはずです。

共通コードをモジュール化し、異なるファイルで異なるコードを実装するか、異なる定義で保護します。ビルドシステムにクライアントごとに特定のターゲットを設定します。各ターゲットは、1つのクライアントに関連するコードのみを含むバージョンをコンパイルします。たとえば、コードがCの場合、さまざまなクライアントの機能を "#ifdef"または言語がビルド構成管理に使用しているメカニズムで保護し、機能の量に対応する一連の定義を準備することができます。クライアントが支払いました。

ifdefsが気に入らない場合は、「インターフェースと実装」、「ファンクター」、「オブジェクトファイル」、またはさまざまなものを1か所に格納するために言語が提供するツールを使用してください。

クライアントにソースを配布する場合は、ビルドスクリプトに特別な「ソース配布ターゲット」を含めることをお勧めします。このようなターゲットを呼び出すと、ソフトウェアの特別なバージョンのsourcesが作成され、別のフォルダーにコピーされて、出荷できるようになり、コンパイルされません。

13
P Shved

多くの人が述べているように、コードを適切に因数分解して、一般的なコードに基づいてカスタマイズ —これにより、保守性が大幅に向上します。オブジェクト指向の言語/システムを使用しているかどうかに関係なく、これは可能です(ただし、Cで行うことは、本当にオブジェクト指向のものよりもかなり困難です)。これは、継承とカプセル化が解決するのに役立つタイプの問題です。

8
Michael Trausch

非常に慎重に

機能の分岐はオプションですが、やや重いと思います。また、深い変更を簡単に行えるため、制御下に置かれていなければ、アプリケーションが完全に分岐する可能性があります。理想的には、コアコードベースを可能な限り一般的で汎用的なものに保つために、カスタマイズを可能な限りプッシュアップする必要があります。

大幅な変更とリファクタリングを行わずにコードベースに適用できるかどうかはわかりませんが、次のようにします。基本的な機能は同じである同様のプロジェクトがありましたが、顧客ごとに非常に具体的な機能セットが必要でした。モジュールとコンテナーのセットを作成し、構成(IlaCの構​​成)を使用してアセンブルしました。

次に、顧客ごとに、基本的に構成とビルドスクリプトを含むプロジェクトを作成し、サイト用に完全に構​​成されたインストールを作成しました。ときどき、このクライアント用にカスタム作成されたコンポーネントもいくつか配置します。しかし、これはまれであり、可能な場合は常により一般的な形式にして、他のプロジェクトがそれらを使用できるようにプッシュダウンすることを試みます。

その結果、必要なレベルのカスタマイズが行われ、カスタマイズされたインストールスクリプトが得られたので、顧客のサイトにアクセスしたときに、システムを常に変更しているように見えず、非常に大きなボーナスが追加されました。ビルドに直接フックされる回帰テストを作成できるようにします。このようにして、顧客固有のバグが発生した場合はいつでも、システムがデプロイされたときにシステムをアサートし、そのレベルでもTDDを実行できるテストを作成できます。

要するに:

  1. フラットなプロジェクト構造を持つ、高度にモジュール化されたシステム。
  2. 構成プロファイルごとにプロジェクトを作成します(顧客ですが、複数のプロファイルを共有できます)
  3. 必要な機能セットを別の製品として組み立て、そのように扱います。

適切に実行された場合、製品のアセンブリにはいくつかの構成ファイルを除くすべてが含まれているはずです。

しばらくこれを使用した後、私は、主に使用されるシステムまたは必須のシステムをコアユニットとして組み立て、顧客のアセンブリにこのメタパッケージを使用するメタパッケージを作成することになりました。数年後、私は顧客ツールを作成するために非常に迅速に組み立てることができる大きなツールボックスを手に入れました。私は現在 Spring Roo を調査しており、ある日、顧客との最初のインタビューでシステムの最初のドラフトを正しく作成できることを期待して、アイデアをもう少しプッシュできないかどうかを確認しています...あなたはそれをユーザー駆動開発と呼ぶことができると思います;-)。

これが役に立てば幸い

5
Newtopian

変更はおそらく小さすぎて、モジュールをクライアントごとに個別の(物理)モジュールに分割することを正当化できません。ビルドの問題、リンクの混乱が心配です。

IMO、小さすぎてはいけません。可能であれば、どこでも戦略パターンを使用してクライアント固有のコードを除外します。これにより、分岐する必要のあるコードの量が減り、すべてのクライアントの一般的なコードを同期させるために必要なマージが減ります。また、テストを簡素化します。デフォルトの戦略を使用して一般的なコードをテストし、クライアント固有のクラスを個別にテストできます。

モジュールAでポリシーX1を使用するためにモジュールBでポリシーX2を使用する必要があるようにモジュールがコーディングされている場合は、X1とX2を1つのポリシークラスに結合できるようにリファクタリングを検討してください。

3
kevin cline

プレーンCで記述している場合、これはかなり醜い方法です。

  • 共通コード(例:ユニット "frangulator.c")

  • クライアント固有のコード、小さく、各クライアントにのみ使用される部分。

  • メインユニットコードで、#ifdefと#includeを使用して次のようなことを行います

#ifdef CLIENT = CLIENTA 
#include "frangulator_client_a.c" 
#endif 

これを、クライアント固有のカスタマイズが必要なすべてのコードユニットで繰り返しパターンとして使用します。

これは非常に見苦しく、他のいくつかの問題を引き起こしますが、それも単純であり、クライアント固有のファイルを互いに簡単に比較できます。

また、クライアント固有のすべての部分が常に(それぞれのファイルで)明確に表示され、メインコードファイルとファイルのクライアント固有の部分との間に明確な関係があることも意味します。

本当に賢い場合は、メイクファイルを設定して正しいクライアント定義を作成できます。

クライアントを作る

client_a用にビルドし、「make clientb」はclient_bなどを作成します。

(およびターゲットが指定されていない "make"は、警告または使用法の説明を発行できます。)

以前に同様のアイデアを使用したことがあります。セットアップには少し時間がかかりますが、非常に効果的です。私の場合、1つのソースツリーで約120の異なる製品を構築しました。

1
quickly_now

SCMを使用してブランチを維持できます。マスターブランチをクライアントのカスタムコードからそのまま/クリーンな状態に保ちます。このブランチで主な開発を行います。アプリケーションのカスタマイズされたバージョンごとに、個別のブランチを維持します。優れたSCMツールは、ブランチのマージで非常にうまく機能します(Gitが思い浮かびます)。マスターブランチの更新は、カスタマイズされたブランチにマージする必要がありますが、クライアント固有のコードを独自のブランチに残すことができます。


ただし、可能であれば、モジュール式で構成可能な方法でシステムを設計してください。これらのカスタムブランチの欠点は、コア/マスターから離れすぎていることです。

1
Htbaa

Gitでは、私が行う方法は、すべての共通コードを持つマスターブランチと、各クライアントのブランチを持つことです。コアコードに変更が加えられた場合は常に、クライアント固有のすべてのブランチをマスターの上にリベースするだけで、現在のベースラインの上に適用されるクライアントの一連の移動パッチが得られます。

クライアントの変更を行っているときに、他のブランチに含める必要のあるバグに気付いた場合は、それをマスターまたは修正が必要な他のブランチにチェリーピックできます(異なるクライアントブランチがコードを共有している場合でも) 、おそらく両方ともマスターからの共通ブランチから分岐する必要があります)。

0
Cercerilla