2012-04-04 3 views
2

Я работаю над клиент-серверным приложением (.NET 4, WCF), которое должно поддерживать обратную совместимость. Другими словами, старые клиенты должны быть совместимы с новыми серверами и наоборот. В результате, наш клиентский код завален заявлениями, такие как:Управление проверкой совместимости клиентов

if (_serverVersion > new Version(2, 1, 3)) 
{ 
    //show/hide something or call method Foo()... 
} 
else 
{ 
    //show/hide something or call method Foo2()... 
} 

Очевидно, что это становится в некоторой степени обслуживания кошмара. К счастью, нам разрешено полностью отказаться от совместимости с каждым незначительным выпуском. Когда мы доберемся до точки, где совместимость может быть нарушена, я хотел бы очистить код, который находится в форме приведенного выше примера.

Мои вопросы:

(1) Есть ли способ, чтобы легко идентифицировать блоки кода, такие как эти, когда они больше не «действует»? Мои первоначальные мысли заключались в том, чтобы каким-то образом условно применить атрибут Obsolete, основанный на версии сборки. Когда мы перейдем к новой второстепенной версии, атрибут Obsolete будет «kick-in», и внезапно у нас будет несколько предупреждений компилятора, указывающих на эти блоки кода ... Кто-нибудь сделал что-нибудь подобное? Или есть лучший способ справиться с этим?

(2) Я сжимаю каждый раз, когда вижу жесткие версии, такие как new Version(2, 1, 3). Что еще хуже, так это то, что во время разработки мы не знаем окончательную версию, выпущенную, поэтому проверки версий основаны на текущем номере сборки + 1, когда разработчик добавляет проверку. Хотя это работает, это не очень чисто. Любые идеи о том, как это можно улучшить?

Спасибо!

ответ

1

Я хотел бы предложить, по крайней мере, создать метод, где вы можете сделать логику так:

public static class ServerUtilities 
{ 
    public static bool IsValidToRun(Version desiredVersion) 
    { 
     if (_serverVersion >= desiredVersion) 
      return true; 
     else if (/* your other logic to determine if they're in some acceptable range */) 
      return true; 

     return false; 
    } 
} 

Затем, используйте так:

if (ServerUtilities.IsValidToRun(new Version(2, 1, 3))) 
{ 
    // Do new logic 
} 
else 
{ 
    // Do old logic 
} 

Если вам необходимо централизовать версии, имеют статический репозиторий функций для сопоставления версий, так что вы можете позвонить:

if (ServerUtilities.IsValidToRun(ServerFeatures.FancyFeatureRequiredVersion)) 
{ 
    ... 
} 

public static class ServerFeatures 
{ 
    public static Version FancyFeatureRequiredVersion 
    { 
     get { return new Version(2, 1, 3); } 
    } 
} 
+0

Спасибо за быстрый ответ! Как минимум, я думаю, что мы должны централизовать эту логику с помощью метода IsValidToRun. Я считаю, что класс ServerFeatures является отличной идеей и может помочь нам отслеживать, какие проверки можно устранить по мере выпуска новых сборок. –

+0

@JohnRussell, это также даст вам хороший способ разбить сборку по назначению. Вы удаляете версию «ServerFeatures», когда вам больше не нужна проверка, компиляция, а затем исправление всех сломанных мест, которые полагаются на нее. –

+0

Точно! Мне это нравится! Я думаю, что я действительно сделаю ServerFeature перечислением, а затем выполним сопоставление Version to ServerFeature в вспомогательном методе, например. большой оператор переключения. Это позволило бы нам повторно использовать одну и ту же версию для нескольких функций и иметь всю эту логику в одном месте. Затем IsValidToRun может взять ServerFeature в качестве параметра и, надеюсь, не позволит потребителям использовать объекты на лету. Спасибо за помощь! –

0

Альтернативный wo uld должно реализовать управление вашими контрактами на обслуживание: в этот момент вы можете использовать собственные функции WCF, чтобы игнорировать незначительные изменения, которые не нарушают работу клиента, как указано на этой странице Versioning Strategies.

На рисунке 1 вы можете видеть, что при добавлении новых параметров к сигнатуре операции, удалении параметров из сигнатуры операции и добавлении новых операций клиент не подвергается воздействию.

В случае, если все еще происходят изменения, или ваш клиент должен поддерживать обе версии (пожалуйста, исправьте меня, если я ошибаюсь, так как не знаю вашу стратегию развертывания), вы можете предлагать разные версии службы на разных конечных точках и в вашем клиентском коде есть фабрика клиентов WCF, которая затем может быть настроена для возврата клиента для соответствующей конечной точки.

На данный момент вы изолировали различные реализации в разных клиентах, что, вероятно, является более чистым и менее кошмаром обслуживания, чем сейчас.


Очень простой пример реализации, чтобы очистить вещи: предположим, что мы имеем два различных контрактов на наш сервис, старый и новый.

[ServiceContract(Name = "Service", Namespace = "http://stackoverflow.com/2012/03")] 
public interface IServiceOld 
{ 
    [OperationContract] 
    void DoWork(); 
} 

[ServiceContract(Name = "Service", Namespace = "http://stackoverflow.com/2012/04")] 
public interface IServiceNew 
{ 
    [OperationContract] 
    void DoWork(); 

    [OperationContract] 
    void DoAdditionalWork(); 
} 

Обратите внимание, что обе службы имеют одно и то же имя, но разные пространства имен.

Давайте рассмотрим проблему с клиентом, который должен поддерживать как расширенный, так и новый сервис и старый. Предположим, что мы хотим вызвать метод DoAdditionalWork, когда мы ранее называли DoWork, и что мы хотим обрабатывать ситуацию на стороне клиента, потому что гипотетически DoAdditionalWork может потребовать дополнительные аргументы от клиента. Тогда конфигурация службы может быть что-то вроде этого:

<service name="ConsoleApplication1.Service"> 
    <endpoint address="http://localhost:8732/test/new" binding="wsHttpBinding" contract="ConsoleApplication1.IServiceNew" /> 
    <endpoint address="http://localhost:8732/test/old" binding="wsHttpBinding" contract="ConsoleApplication1.IServiceOld" /> 
    ... 
</service> 

Хорошо, у нас есть сторона службы, то теперь к интересной части: мы хотели бы общаться с услугами, используя один и тот же интерфейс. В этом случае я буду использовать старый, но вам может понадобиться установить адаптер между ними. В идеале, в нашем клиентском коде, мы хотели бы сделать что-то вроде этого:

IServiceOld client = *Magic* 

client.DoWork(); 

Магия в данном случае является простой завод, как это:

internal class ClientFactory 
{ 
    public IServiceOld GetClient() 
    { 
     string service = ConfigurationManager.AppSettings["Service"]; 
     if(service == "Old") 
      return new ClientOld(); 
     else if(service == "New") 
      return new ClientNew(); 

     throw new NotImplementedException(); 
    } 
} 

Я делегировал решение которой клиент использовать для app.config, но вы можете вставить свою версию. Реализация ClientOld просто обычный WCF клиент для IServiceOld:

public class ClientOld : IServiceOld 
{ 
    private IServiceOld m_Client; 

    public ClientOld() 
    { 
     var factory = new ChannelFactory<IServiceOld>(new WSHttpBinding(), "http://localhost:8732/test/old"); 
     m_Client = factory.CreateChannel(); 
    } 

    public void DoWork() 
    { 
     m_Client.DoWork(); 
    } 

    ... 
} 

ClientNew вместо реализует поведение мы желать, а именно вызов операции DoAdditionalWork:

public class ClientNew : IServiceOld 
{ 
    private IServiceNew m_Client; 

    public ClientNew() 
    { 
     var factory = new ChannelFactory<IServiceNew>(new WSHttpBinding(), "http://localhost:8732/test/new"); 
     m_Client = factory.CreateChannel(); 
    } 

    public void DoWork() 
    { 
     m_Client.DoWork(); 
     m_Client.DoAdditionalWork(); 
    } 
    ... 
} 

Вот именно, теперь наш клиент может как в следующем примере:

var client = new ClientFactory().GetClient(); 
client.DoWork(); 

Что мы достигли? Код с использованием клиента абстрагируется от того, какую дополнительную работу должен выполнять фактический клиент WCF, и решение о том, какой клиент использовать, делегируется на заводе. Я надеюсь, что некоторые варианты/расширение этой выборки соответствуют вашим потребностям.

Смежные вопросы