2012-04-01 20 views
6

Đây là một loại dưa chua khá khó chịu mà chúng tôi đã đưa vào trang web của khách hàng. Khách hàng có khoảng 100 máy trạm, trên đó chúng tôi đã triển khai phiên bản 1.0.0 của sản phẩm "MyApp".Thay đổi giao diện giữa các phiên bản - cách quản lý?

Bây giờ, một trong những thứ mà sản phẩm làm là tải lên một bổ trợ (gọi nó là "MyPlugIn", mà nó đầu tiên tìm kiếm trên một máy chủ trung tâm để xem có phiên bản mới hơn không, và nếu có thì nó sao chép tập tin đó tại địa phương, sau đó nó tải lên add-in bằng cách sử dụng Assembly.Load và gọi một giao diện được biết đến nhất định.Điều này đã hoạt động tốt trong vài tháng. máy (nhưng không phải tất cả) Nó đi kèm với một phiên bản mới và cập nhật của MyPlugIn

Nhưng sau đó đã xảy ra sự cố.Có một DLL được chia sẻ, được tham chiếu bởi cả MyApp và MyPlugIn, được gọi là MyDLL, có một phương thức MyClass.MyMethod. Giữa v1.0.0 và v1.0.1, chữ ký của MyClass.MyMethod đã thay đổi (một tham số đã được thêm). Và bây giờ là phiên bản mới của MyPlugIn làm cho các ứng dụng client v1.0.0 sụp đổ:

Phương pháp không tìm thấy: MyClass.MyMethod (System.String)

Client châm chọc không muốn triển khai v1 .0.1 trên tất cả các trạm khách, là sửa chữa được bao gồm trong v1.0.1 là cần thiết chỉ cho một vài máy trạm, và không cần phải cuộn nó ra cho tất cả các máy khách. Đáng buồn thay, chúng tôi chưa (chưa) sử dụng ClickOnce hoặc các tiện ích triển khai hàng loạt khác, do đó, tung ra phiên bản v1.0.1 sẽ là một bài tập đau đớn và không cần thiết.

Có cách nào đó để viết mã trong MyPlugin để nó hoạt động tốt như nhau, không phân biệt liệu nó có giao dịch với MyDLL v1.0.0 hoặc v1.0.1 không? Có lẽ có một số cách để thăm dò cho một giao diện dự kiến ​​bằng cách sử dụng phản ánh để xem nếu nó tồn tại, trước khi thực sự gọi nó?

CHỈNH SỬA: Tôi cũng nên đề cập - chúng tôi có một số quy trình QA khá chặt chẽ. Vì v1.0.1 đã được chính thức phát hành bởi QA, chúng tôi không được phép thực hiện bất kỳ thay đổi nào đối với MyApp hoặc MyDLL. Sự tự do duy nhất của phong trào mà chúng tôi có là thay đổi MyPlugin, là mã tùy chỉnh được viết riêng cho khách hàng này.

+1

Tại sao không thêm lại vào MyDll phương thức được mong đợi bởi phiên bản plugin cũ? Bên trong phương thức này có thể gọi phiên bản phương thức mới chuyển một giá trị mặc định cho tham số phương thức mới. – Steve

+0

@Steve - xem chỉnh sửa của tôi - không thể thực hiện bất kỳ thay đổi nào đối với MyDLL –

+0

Is MyClass.MyMethod có tĩnh không? –

Trả lời

3

Tôi đã trích xuất mã này từ một ứng dụng tôi đã viết cách đây một thời gian và đã xóa một số phần.
Nhiều điều được giả định ở đây:

  1. Vị trí của MyDll.dll là thư mục hiện hành
  2. Các Namespace để nhận được thông tin phản ánh là "MyDll.MyClass"
  3. Lớp có một constructor không có tham số.
  4. Bạn không mong đợi một giá trị trả về
using System.Reflection; 

private void CallPluginMethod(string param) 
{ 
    // Is MyDLL.Dll in current directory ??? 
    // Probably it's better to call Assembly.GetExecutingAssembly().Location but.... 
    string libToCheck = Path.Combine(Environment.CurrentDirectory, "MyDLL.dll"); 
    Assembly a = Assembly.LoadFile(libToCheck); 
    string typeAssembly = "MyDll.MyClass"; // Is this namespace correct ??? 
    Type c = a.GetType(typeAssembly); 

    // Get all method infos for public non static methods 
    MethodInfo[] miList = c.GetMethods(BindingFlags.Public|BindingFlags.Instance|BindingFlags.DeclaredOnly); 
    // Search the one required (could be optimized with Linq?) 
    foreach(MethodInfo mi in miList) 
    { 
     if(mi.Name == "MyMethod") 
     { 
      // Create a MyClass object supposing it has an empty constructor 
      ConstructorInfo clsConstructor = c.GetConstructor(Type.EmptyTypes); 
      object myClass = clsConstructor.Invoke(new object[]{}); 

      // check how many parameters are required 
      if(mi.GetParameters().Length == 1) 
       // call the new interface 
       mi.Invoke(myClass, new object[]{param}); 
      else 
       // call the old interface or give out an exception 
       mi.Invoke(myClass, null); 
      break; 
     } 
    } 
} 

Những gì chúng ta làm ở đây:

  1. tải động thư viện và trích xuất các loại MyClass.
  2. Sử dụng loại, yêu cầu hệ thống phụ phản ánh danh sách MethodInfo có trong loại đó.
  3. Kiểm tra mọi tên phương thức để tìm tên phương thức được yêu cầu.
  4. Khi phương pháp được tìm thấy, hãy tạo một phiên bản loại.
  5. Nhận số tham số dự kiến ​​theo phương pháp.
  6. Tùy thuộc vào số lượng thông số gọi phiên bản phù hợp bằng cách sử dụng Invoke.
+0

Cảm ơn, tôi đã theo một cách tiếp cận bằng cách sử dụng sự phản chiếu, chính xác như bạn đã làm ở đây, và nó hoạt động rất tốt! –

2

Thực ra, có vẻ như ý tưởng tồi là thay đổi hợp đồng giữa các bản phát hành. Đang ở trong môi trường hướng đối tượng, bạn nên tạo một hợp đồng mới, có thể kế thừa từ hợp đồng cũ.

public interface MyServiceV1 { } 

public interface MyServiceV2 { } 

Nội bộ bạn làm cho động cơ sử dụng giao diện mới và cung cấp bộ điều hợp để dịch các đối tượng cũ sang giao diện mới.

public class V1ToV2Adapter : MyServiceV2 { 
    public V1ToV2Adapter(MyServiceV1) { ... } 
} 

Sau khi nạp một assembly, bạn quét nó và:

  • khi bạn tìm thấy một lớp học thực hiện giao diện mới, bạn sử dụng nó trực tiếp
  • khi bạn tìm thấy một lớp học thực hiện giao diện cũ, bạn sử dụng bộ điều hợp trên nó

Sử dụng hack (như kiểm tra giao diện) sẽ sớm hay muộn bạn cắn hoặc bất kỳ ai khác sử dụng hợp đồng - chi tiết của bản hack phải là được biết đến với bất kỳ ai dựa vào giao diện nghe có vẻ khủng khiếp từ góc độ hướng đối tượng.

+0

Đồng ý, điều này sẽ không bao giờ được cho phép. Nhưng bây giờ chúng ta đang theo dõi sự thật và chúng tôi phải tìm ra cách khắc phục tình huống này, với sự linh hoạt chỉ để thay đổi mã trong MyPlugin. –

1

Trong MyDLL 1.0.1, không dùng cũ MyClass.MyMethod(System.String) và quá tải với phiên bản mới.

+0

À vâng, đó sẽ là một ý tưởng hay ... ** nếu ** chúng tôi không có quy trình QA thực sự chặt chẽ. QA đã chính thức phát hành v1.0.1 và chúng tôi không được phép thực hiện bất kỳ thay đổi nào cho nó ngay bây giờ ... điều duy nhất tôi tự do thay đổi là MyPlugIn, là mã tùy chỉnh được viết cho khách hàng cụ thể này. –

+3

Đó không phải là QA chặt chẽ, đó là QA cứng nhắc, QA chặt chẽ sẽ tìm ra vấn đề trước khi nó biến mất. Giải pháp tốt nhất là sửa lỗi này đúng cách, nếu không nó sẽ trở lại và ám ảnh bạn lần nữa. –

1

Bạn có thể quá tải MyMethod để chấp nhận MyMethod (chuỗi) (phiên bản 1.0.0 tương thích) và MyMethod (chuỗi, chuỗi) (phiên bản v1.0.1) không?

+0

Không, xem chỉnh sửa của tôi - Tôi không thể thực hiện bất kỳ thay đổi nào đối với MyDLL. –

4

Điều là thay đổi bạn đã thực hiện phải là về cơ bản ngoài và không thay đổi thay đổi. Vì vậy, nếu bạn muốn trở lại tương thích trong triển khai của bạn (như tôi đã hiểu trong chiến lược triển khai hiện tại bạn có đây là lựa chọn duy nhất), bạn nên không bao giờthay đổi giao diện nhưng thêm một phương pháp mới vào nó và tránh liên kết chặt chẽ của plugin của bạn với DLL được chia sẻ, nhưng tải nó động. Trong trường hợp này

  • bạn sẽ thêm một funcionality mới mà không làm phiền một cũ một

  • bạn sẽ có thể chọn phiên bản của dll để tải khi chạy.

+0

Điểm của bạn được thực hiện tốt trong tương lai - nhưng bây giờ chúng ta đang thực tế và phải đối phó với sự lộn xộn mà chúng tôi đã thực hiện ... –

+0

@Shaul: có phải là một mớ hỗn độn khi tải tham chiếu của plugin không? – Tigran

+0

Tôi không chắc mình hiểu câu hỏi của bạn? –

1

Trong bối cảnh, tôi nghĩ rằng điều duy nhất bạn có thể làm thực sự là có hai phiên bản của mydll chạy 'bên cạnh',
và đó có nghĩa là một cái gì đó giống như những gì Tigran đề nghị, tải mydll động - ví dụ như một ví dụ bên không liên quan nhưng có thể giúp bạn, hãy xem RedemptionLoader http://www.dimastr.com/redemption/security.htm#redemptionloader (dành cho plugin Outlook thường gặp sự cố khi liên kết với nhau tham chiếu các phiên bản khác nhau của dll trợ giúp, giống như một câu chuyện nền - đó là nguyên nhân phức tạp hơn một chút của COM liên quan nhưng không thay đổi nhiều ở đây) -
đó là những gì bạn có thể làm, một cái gì đó tương tự. Tải động dll theo vị trí, tên - bạn có thể chỉ định vị trí đó trong nội bộ, mã cứng hoặc thậm chí thiết lập từ cấu hình hoặc thứ gì đó (hoặc kiểm tra và thực hiện điều đó nếu bạn thấy MyDll không đúng phiên bản),
và sau đó 'quấn' các đối tượng, các cuộc gọi tạo thành dll được nạp động để khớp với những gì bạn thường có - hoặc thực hiện một số mẹo như vậy (bạn phải quấn thứ gì đó hoặc 'ngã ba' vào việc triển khai) để làm mọi thứ hoạt động ở cả hai các trường hợp.
Ngoài ra để thêm vào 'no-nos' và nỗi lo QA của bạn :),
chúng không được phá vỡ tính tương thích ngược từ 1.0.0 đến 1.0.1 - đó là (thường) những thay đổi nhỏ, sửa lỗi - không vi phạm thay đổi, phiên bản chính # là cần thiết cho điều đó.

3

Nhóm của tôi đã phạm phải sai lầm tương tự mà bạn có nhiều lần. Chúng tôi có kiến ​​trúc plugin tương tự và lời khuyên tốt nhất tôi có thể cung cấp cho bạn trong thời gian dài là thay đổi kiến ​​trúc này càng sớm càng tốt. Đây là một cơn ác mộng bảo trì. Ma trận tương thích ngược phát triển phi tuyến tính với mỗi bản phát hành. Đánh giá mã nghiêm ngặt có thể cung cấp một số cứu trợ, nhưng vấn đề là bạn luôn cần phải biết khi nào các phương pháp được thêm vào hoặc thay đổi để gọi chúng theo cách thích hợp. Trừ khi cả nhà phát triển và người đánh giá biết chính xác khi nào phương thức được thay đổi lần cuối, bạn có nguy cơ bị ngoại lệ thời gian chạy khi không tìm thấy phương pháp. Bạn có thể KHÔNG BAO GIỜ gọi một phương thức mới trong MyDLL trong plugin một cách an toàn, bởi vì bạn có thể chạy trên một máy khách cũ không có phiên bản MyDLL mới nhất với các phương thức.

Tạm thời, bạn có thể làm một cái gì đó như thế này trong MyPlugin:

static class MyClassWrapper 
{ 
    internal static void MyMethodWrapper(string name) 
    { 
     try 
     { 
     MyMethodWrapperImpl(name); 
     } 
     catch (MissingMethodException) 
     { 
     // do whatever you need to to make it work without the method. 
     // this may go as far as re-implementing my method. 
     } 
    } 

    private static void MyMethodWrapperImpl(string name) 
    { 
     MyClass.MyMethod(name); 
    } 

} 

Nếu MyMethod không phải là tĩnh, bạn có thể làm cho một wrapper không tĩnh tương tự.

Đối với những thay đổi dài hạn, một điều bạn có thể làm cuối cùng là cung cấp giao diện plugin để giao tiếp. Bạn không thể thay đổi giao diện sau khi phát hành, nhưng bạn có thể xác định giao diện mới mà các phiên bản sau của plugin sẽ sử dụng. Ngoài ra, bạn không thể gọi các phương thức tĩnh trong MyDLL từ MyPlugIn. Nếu bạn có thể thay đổi mọi thứ ở cấp độ máy chủ (tôi nhận ra điều này có thể nằm ngoài tầm kiểm soát của bạn), một tùy chọn khác là cung cấp một số loại hỗ trợ phiên bản để plugin mới có thể tuyên bố nó không hoạt động với ứng dụng khách cũ. Sau đó, máy khách cũ sẽ chỉ tải xuống phiên bản cũ từ máy chủ, trong khi các máy khách mới hơn tải xuống phiên bản mới.

Các vấn đề liên quan