여러 클라이언트에 대해 동일한 소프트웨어의 서로 다른 사용자 정의 버전을 유지하는 방법


46

우리는 다른 요구를 가진 여러 고객을 보유하고 있습니다. 우리의 소프트웨어는 어느 정도 모듈화되어 있지만, 모든 모듈의 비즈니스 로직을 각 고객마다 조금씩 조정해야한다는 것은 거의 확실합니다. 변경 사항은 너무 작아서 각 클라이언트에 대해 모듈을 개별 (물리적) 모듈로 나누는 것을 정당화하기에는 너무 작습니다. 빌드 관련 문제, 연결 혼란이 걱정됩니다. 그러나 이러한 변경 사항은 구성 파일의 스위치로 구성하기에 너무 크고 너무 많아 배포시 문제가 발생하고 특히 팅커 유형 관리자의 경우 많은 지원 문제가 발생할 수 있습니다.

빌드 시스템이 각 클라이언트마다 하나씩 여러 빌드를 작성하도록하고 싶습니다. 여기서 단일 물리적 모듈의 특수 버전에 변경 사항이 포함됩니다. 그래서 몇 가지 질문이 있습니다.

빌드 시스템이 여러 빌드를 작성하도록 하시겠습니까? 소스 제어, 특히 svn에 다른 사용자 정의를 어떻게 저장해야합니까?


4
당신을 #ifdef위해 작동 합니까 ?
ohho

6
전 처리기 지시문은 매우 복잡해져 코드를 읽기 어렵고 디버깅하기가 어려워 질 수 있습니다.
팔콘

1
더 나은 답변을 얻으려면 실제 플랫폼 및 응용 프로그램 유형 (데스크톱 / 웹)에 대한 세부 정보를 포함해야합니다. 데스크탑 C ++ 앱에서의 커스터마이징은 PHP 웹 앱과 완전히 다릅니다.
GrandmasterB

2
@Falcon : 2011 년에 많은 의문점이있는 답변을 선택했기 때문에 SVN을 제안한 방식으로 경험 한 적이 있다면 알려주시겠습니까? 내 이의 제기가 근거가 없습니까?
Doc Brown

2
@Doc Brown : 분기와 병합은 지루하고 복잡했습니다. 당시에는 동작이나 구성을 변경 한 클라이언트 특정 플러그인 또는 "패치"가 포함 된 플러그인 시스템을 사용했습니다. 약간의 오버 헤드가 있지만 Dependendy Injection으로 관리 할 수 ​​있습니다.
팔콘

답변:


7

기능 분기 와 같은 코드 구성 이 필요 합니다. 특정 시나리오에서는 새 항목을 개발하거나 버그를 해결하는 동안 기능 분기를 사용하기 때문에 이를 클라이언트 별 트렁크 분기 라고합니다.

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

아이디어는 새로운 기능의 브랜치를 병합 한 코드 트렁크를 갖는 것입니다. 클라이언트가 아닌 모든 기능을 의미합니다.

그런 다음 클라이언트 별 브랜치 가 있으며 필요한 지점과 동일한 기능의 브랜치를 병합합니다 (원하는 경우 체리 따기).

이러한 클라이언트 분기는 일시적이거나 수명이 짧지 만 기능 분기와 유사하게 보입니다. 그것들은 더 오랜 기간 동안 유지되며 주로 병합됩니다. 클라이언트 별 기능 분기 개발은 가능한 한 적어야합니다.

클라이언트 별 브랜치는 트렁크와 병렬 브랜치이며 트렁크 자체 만 있으면 활성화되고 전체적으로 병합되지 않습니다.

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

7
기능 분기의 경우 +1 각 클라이언트에 대해서도 브랜치를 사용할 수 있습니다. 한 가지만 제안하고 싶습니다. SVN / CVS 대신 분산 VCS (hg, git, bzr)를 사용하여이를 수행하십시오.)
Herberth Amaral

42
-1. "기능 분기"의 목적이 아닙니다. "기능 개발이 끝나면 병합되는 임시 브랜치"라는 정의와 모순됩니다.
P Shved

13
소스 제어에서 모든 델타를 유지하고 영원히 정렬해야합니다. 단기적으로는 효과가있을 수 있습니다. 장기적으로 끔찍할 수 있습니다.
quick_now

4
-1, 이것은 이름 지정 문제가 아닙니다. 서로 다른 클라이언트에 서로 다른 브랜치를 사용하는 것은 코드 복제의 또 다른 형태 일뿐입니다. 이것은 IMHO 안티 패턴이며, 그것을하지 않는 방법의 예입니다.
Doc Brown

6
로버트, 나는 편집하기 전에도 당신이 제안한 것을 잘 이해했다고 생각하지만, 이것이 끔찍한 접근법이라고 생각합니다. 트렁크에 새 핵심 기능을 추가 할 때마다 N 개의 클라이언트가 있다고 가정하면 SCM을 통해 새 기능을 N 분기에 쉽게 전파 할 수 있습니다. 그러나 이런 방식으로 분기를 사용하면 클라이언트 별 수정이 명확하게 분리되는 것을 피하기가 너무 쉽습니다. 결과적으로 이제 트렁크의 각 변경에 대해 N 개의 기회가 병합 충돌을 일으킬 수 있습니다. 또한 이제 N 대신 통합 테스트를 실행해야합니다.
Doc Brown

37

SCM 분기에서는이 작업을 수행하지 마십시오. 공통 코드를 라이브러리 또는 프로젝트 골격 아티팩트를 생성하는 별도의 프로젝트로 만드십시오. 각 고객 프로젝트는 별도의 프로젝트이며, 공통 프로젝트에 의존합니다.

공통 기본 앱이 Spring과 같은 종속성 주입 프레임 워크를 사용하는 경우 가장 쉬운 방법이므로 사용자 정의 기능이 필요할 때 각 고객 프로젝트에서 서로 다른 대체 오브젝트를 주입 할 수 있습니다. DI 프레임 워크가 아직 없더라도이를 추가하고이 방법으로 작업하는 것이 가장 고통스러운 선택 일 수 있습니다.


일을 단순하게 유지하는 또 다른 방법은 종속성 삽입을 사용하는 대신 상속 (및 단일 프로젝트)을 사용하는 것입니다. 상속, 부분 클래스 또는 이벤트를 사용하여 동일한 프로젝트에서 공통 코드와 사용자 지정을 모두 유지합니다. 이 디자인을 사용하면 클라이언트 분기가 잘 작동하고 (선택된 답변에 설명 된대로) 공통 코드를 업데이트하여 클라이언트 분기를 항상 업데이트 할 수 있습니다.
drizin

내가 놓친 부분이 없다면 100 명의 고객이 있다면 (100 x NumberOfChangedProjects )를 만들고 DI를 사용하여 고객을 관리한다는 것입니다. 그렇다면 유지 관리가 끔찍하기 때문에 이런 종류의 솔루션에서 멀리 떨어져 있어야합니다.
sotn

12

모든 클라이언트를위한 소프트웨어를 단일 지점에 저장하십시오. 다른 고객을 위해 변경 한 내용을 분기 할 필요가 없습니다. 대부분의 소프트웨어는 모든 클라이언트에게 최상의 품질을 제공하기를 원하며, 핵심 인프라에 대한 버그 수정은 불필요한 병합 오버 헤드없이 모든 사람에게 영향을 미쳐 더 많은 버그가 발생할 수 있습니다.

공통 코드를 모듈화하고 다른 파일에서 다른 코드를 구현하거나 다른 정의로 ​​보호하십시오. 빌드 시스템에 각 클라이언트에 대한 특정 대상이 있으며 각 대상은 하나의 클라이언트와 관련된 코드로 버전을 컴파일합니다. 예를 들어 코드가 C 인 경우 " #ifdef"또는 빌드 구성 관리를위한 언어의 메커니즘을 사용하여 다른 클라이언트의 기능을 보호 하고 클라이언트가 지불 한 기능의 양에 해당하는 정의 세트를 준비 할 수 있습니다.

ifdefs가 마음에 들지 않으면 "인터페이스 및 구현", "펑터", "개체 파일"또는 언어가 제공하는 도구를 사용하여 한 곳에서 다른 것을 저장하십시오.

소스를 클라이언트에 배포하는 경우 빌드 스크립트에 특별한 "소스 배포 대상"을 두는 것이 가장 좋습니다. 이러한 대상을 호출하면 소프트웨어 의 특수 소스 버전을 작성 하여 별도의 폴더에 복사하여 배송 할 수 있으며 컴파일하지 않습니다.


8

많은 사람들이 언급했듯이 : 코드를 올바르게 인수 화 하고 공통 코드를 기반으로 사용자 정의 하면 유지 관리 성이 크게 향상됩니다. 객체 지향 언어 / 시스템을 사용하든 아니든간에 이것이 가능합니다 (실제로 객체 지향적 인 것보다 C에서 수행하는 것이 조금 더 어렵습니다). 이것은 상속과 캡슐화가 해결하는 데 도움이되는 유형의 문제입니다!


5

매우 신중하게

기능 분기는 옵션이지만 다소 무겁습니다. 또한 철저한 수정이 쉬워 통제력을 유지하지 않으면 응용 프로그램을 완전히 꺼낼 수 있습니다. 코어 코드 기반을 가능한 한 일반적이고 일반적인 상태로 유지하기 위해 가능한 한 사용자 지정을 최대한 많이 늘리는 것이 이상적입니다.

다음은 큰 수정이나 리팩토링없이 코드베이스에 적용 가능한지 모르겠지만 어떻게 할 것입니다. 기본 기능은 같지만 각 고객에게는 매우 특정한 기능이 필요한 비슷한 프로젝트가있었습니다. 구성 (a la IoC)을 통해 모은 모듈 및 컨테이너 세트를 작성했습니다.

그런 다음 각 고객에 대해 기본적으로 구성 및 빌드 스크립트가 포함 된 프로젝트를 작성하여 사이트에 대해 완전히 구성된 설치를 작성했습니다. 때때로 나는이 클라이언트를 위해 주문 제작 한 몇몇 컴포넌트들도 배치한다. 그러나 이것은 드문 일이며 가능한 한 더 일반적인 형태로 만들고 다른 프로젝트에서 사용할 수 있도록 아래로 밀어 넣으려고합니다.

최종 결과는 필요한 사용자 정의 수준을 얻었고, 사용자 정의 설치 스크립트를 얻었으므로 고객 사이트에 도착했을 때 시스템을 항상 tweeking하는 것처럼 보이지 않으며 추가로 매우 중요한 보너스를 얻습니다. 빌드에 직접 연결된 회귀 테스트를 만들 수 있습니다. 이 방법을 사용하면 언제든지 고객 고유의 버그가 발생하여 시스템을 배포 할 때이를 테스트하여 해당 수준에서도 TDD를 수행 할 수있는 테스트를 작성할 수 있습니다.

간단히 말해서 :

  1. 평평한 프로젝트 구조의 모듈 식 시스템.
  2. 각 구성 프로파일에 대한 프로젝트를 작성하십시오 (고객은 하나 이상이 프로파일을 공유 할 수 있지만)
  3. 필요한 기능 세트를 다른 제품으로 조립하여 그대로 취급하십시오.

제대로 완료되면 제품 어셈블리에 구성 파일이 거의 포함되어 있지 않아야합니다.

이것을 잠시 동안 사용한 후에 나는 가장 많이 사용되거나 필수적인 시스템을 핵심 단위로 조립하고 고객 어셈블리에이 메타 패키지를 사용하는 메타 패키지를 만들었습니다. 몇 년 후 나는 고객 솔루션을 만들기 위해 매우 빠르게 조립할 수있는 큰 도구 상자를 갖게되었습니다. 나는 현재 Spring Roo를 조사 중이며 언젠가 아이디어를 조금 더 추진할 수 없다면 언젠가 첫 인터뷰에서 고객과 함께 시스템의 초안을 만들 수 있다고 생각합니다. 개발 ;-).

이것이 도움이 되었기를 바랍니다.


3

변경 사항은 너무 작아서 각 클라이언트에 대해 모듈을 개별 (물리적) 모듈로 나누는 것을 정당화하기에는 너무 작습니다. 빌드 관련 문제, 연결 혼란이 걱정됩니다.

IMO, 그들은 너무 작을 수 없습니다. 가능하다면 전략 패턴을 사용하여 클라이언트 특정 코드를 거의 모든 곳에서 제외시킵니다. 이렇게하면 분기해야하는 코드의 양이 줄어들고 모든 클라이언트에 대해 일반 코드를 동기화하는 데 필요한 병합이 줄어 듭니다. 또한 테스트를 단순화합니다. 기본 전략을 사용하여 일반 코드를 테스트하고 클라이언트 별 클래스를 개별적으로 테스트 할 수 있습니다.

모듈 A에서 정책 X1을 사용하도록 모듈 B에서 정책 X2를 사용해야하도록 모듈이 코딩 된 경우 X1 및 X2를 단일 정책 클래스로 결합 할 수 있도록 리팩토링을 고려하십시오.


1

SCM을 사용하여 분기를 유지 관리 할 수 ​​있습니다. 클라이언트 브랜치에서 마스터 브랜치를 깨끗하게 유지하십시오. 이 지점에서 주요 개발을 수행하십시오. 응용 프로그램의 모든 사용자 정의 버전에 대해 별도의 분기를 유지하십시오. 좋은 SCM 도구는 브랜치 병합과 실제로 잘 작동합니다 (Git이 떠 오릅니다). 마스터 브랜치의 모든 업데이트는 사용자 지정 브랜치로 병합되어야하지만 클라이언트 특정 코드는 자체 브랜치에 남아있을 수 있습니다.


그러나 가능하면 모듈 식 및 구성 가능한 방식으로 시스템을 설계하십시오. 이러한 커스텀 브랜치의 단점은 코어 / 마스터와 너무 멀리 떨어져 있다는 것입니다.


1

평범한 C로 글을 쓰고 있다면 여기에 추한 방법이 있습니다.

  • 공통 코드 (예 : "frangulator.c"단위)

  • 클라이언트 특정 코드, 작고 각 클라이언트에만 사용되는 조각.

  • 기본 단위 코드에서 #ifdef 및 #include를 사용하여 다음과 같은 작업을 수행하십시오.

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

클라이언트 별 사용자 정의가 필요한 모든 코드 단위에서이 패턴을 반복해서 사용하십시오.

이것은 매우 추악하고 다른 문제로 이어지지 만 간단하지만 클라이언트 특정 파일을 서로 쉽게 비교할 수 있습니다.

또한 모든 클라이언트 특정 부분이 항상 명확하게 표시되고 (각각 고유 한 파일로) 주 코드 파일과 클라이언트 특정 파일 부분 사이에 명확한 관계가 있음을 의미합니다.

정말 영리한 경우 makefile을 설정하여 다음과 같은 올바른 클라이언트 정의를 만들 수 있습니다.

의뢰인

client_a를 빌드하고 "make clientb"는 client_b 등을 만듭니다.

(제공된 대상이없는 "make"는 경고 또는 사용법 설명을 발행 할 수 있습니다.)

전에 비슷한 아이디어를 사용했지만 설정하는 데 시간이 걸리지 만 매우 효과적 일 수 있습니다. 필자의 경우 하나의 소스 트리가 약 120 개의 서로 다른 제품을 만들었습니다.


0

git에서 내가하는 방식은 모든 공통 코드가있는 마스터 분기와 각 클라이언트의 분기를 갖는 것입니다. 코어 코드를 변경할 때마다 모든 클라이언트 특정 분기를 마스터 위에 리베이스하면 현재 기준 위에 적용된 클라이언트에 대한 이동 패치 세트가 생깁니다.

클라이언트를 변경하고 다른 브랜치에 포함되어야하는 버그를 발견 할 때마다 다른 클라이언트 브랜치가 코드를 공유하더라도 마스터 또는 수정이 필요한 다른 브랜치로 체리 픽을 선택할 수 있습니다. , 아마도 마스터에서 공통 브랜치에서 분기해야합니다.)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.