2016-03-16 24 views
6

Các ứng dụng thường cần kết nối với các dịch vụ khác (cơ sở dữ liệu, bộ nhớ cache, API, v.v.). Đối với sự tỉnh táo và DRY, chúng tôi muốn giữ tất cả các kết nối này trong một mô-đun để phần còn lại của cơ sở mã của chúng tôi có thể chia sẻ các kết nối.Quản lý tạo kết nối bằng Python?

Để giảm soạn sẵn, sử dụng hạ lưu nên đơn giản:

# app/do_stuff.py 
from .connections import AwesomeDB 

db = AwesomeDB() 

def get_stuff(): 
    return db.get('stuff') 

Và việc thiết lập kết nối cũng nên được đơn giản:

# app/cli.py or some other main entry point 
from .connections import AwesomeDB 

db = AwesomeDB() 
db.init(username='stuff admin') # Or os.environ['DB_USER'] 

khung web như Django và Flask làm điều gì đó như thế này, nhưng cảm giác hơi khó chịu:

Connect to a Database in Flask, Which Approach is better? http://flask.pocoo.org/docs/0.10/tutorial/dbcon/

Một vấn đề lớn với điều này là chúng tôi muốn tham chiếu đến đối tượng kết nối thực tế thay vì proxy, bởi vì chúng tôi muốn giữ lại tab hoàn thành trong iPython và các môi trường dev khác.

Vì vậy, Right Way (tm) để làm điều đó là gì? Sau một vài lần lặp lại, đây là ý tưởng của tôi:

#app/connections.py 
from awesome_database import AwesomeDB as RealAwesomeDB 
from horrible_database import HorribleDB as RealHorribleDB 


class ConnectionMixin(object): 
    __connection = None 

    def __new__(cls): 
     cls.__connection = cls.__connection or object.__new__(cls) 
     return cls.__connection 

    def __init__(self, real=False, **kwargs): 
     if real: 
      super().__init__(**kwargs) 

    def init(self, **kwargs): 
     kwargs['real'] = True 
     self.__init__(**kwargs) 


class AwesomeDB(ConnectionMixin, RealAwesomeDB): 
    pass 


class HorribleDB(ConnectionMixin, RealHorribleDB): 
    pass 

Phòng để cải thiện: Thiết __connection ban đầu để một ConnectionProxy generic thay vì Không, mà bắt tất cả các truy cập thuộc tính và ném một ngoại lệ.

Tôi đã thực hiện khá nhiều điều thú vị ở đây về SO và trong các dự án PMNM khác nhau và chưa thấy bất cứ điều gì như thế này. Nó cảm thấy khá vững chắc, mặc dù nó có nghĩa là một loạt các mô-đun sẽ được instantiating đối tượng kết nối như là một tác dụng phụ tại thời gian nhập khẩu. Điều này sẽ thổi lên trong khuôn mặt của tôi? Có bất kỳ hậu quả tiêu cực nào khác đối với phương pháp này không?

Trả lời

0

Thứ nhất, thiết kế-khôn ngoan, tôi có thể bị mất một cái gì đó, nhưng tôi không thấy lý do tại sao bạn cần mixin nặng + máy móc singleton thay vì chỉ định một helper như vậy:

_awesome_db = None 
def awesome_db(**overrides): 
    global _awesome_db 
    if _awesome_db is None: 
     # Read config/set defaults. 
     # overrides.setdefault(...) 
     _awesome_db = RealAwesomeDB(**overrides) 
    return _awesome_db 

Ngoài ra, có là lỗi có thể không giống như trường hợp sử dụng được hỗ trợ, nhưng dù sao: nếu bạn thực hiện 2 cuộc gọi sau trong một hàng, bạn sẽ nhận được cùng một đối tượng kết nối hai lần mặc dù bạn đã chuyển các tham số khác nhau:

db = AwesomeDB() 
db.init(username='stuff admin') 

db = AwesomeDB() 
db.init(username='not-admin') # You'll get admin connection here. 

Một sửa chữa dễ dàng cho điều đó sẽ là sử dụng một dict của các kết nối được khóa trên tham số đầu vào.

Bây giờ, về bản chất của câu hỏi.

Tôi nghĩ câu trả lời tùy thuộc vào cách các lớp "kết nối" của bạn thực sự được triển khai.

nhược điểm tiềm năng với cách tiếp cận của bạn tôi thấy là:

  • Trong một môi trường đa luồng, bạn có thể nhận được vấn đề với truy cập đồng thời unsychronized đến đối tượng kết nối toàn cầu từ nhiều chủ đề, trừ khi nó đã là thread-safe. Nếu bạn quan tâm đến điều đó, bạn có thể thay đổi mã và giao diện của bạn một chút và sử dụng một biến thread-local.

  • Điều gì sẽ xảy ra nếu quá trình dồn sau khi tạo kết nối? Máy chủ ứng dụng web có xu hướng làm điều đó và nó có thể không an toàn, một lần nữa tùy thuộc vào kết nối cơ bản.

  • Đối tượng kết nối có trạng thái không? Điều gì sẽ xảy ra nếu đối tượng kết nối không hợp lệ (do lỗi kết nối/hết thời gian kết nối)? Bạn có thể cần phải thay thế kết nối bị hỏng bằng một kết nối mới để trả lại lần sau khi kết nối được yêu cầu.

Quản lý kết nối thường được triển khai hiệu quả và an toàn thông qua connection pool trong thư viện khách hàng.

Ví dụ, Redis client redis-py sử dụng thực hiện như sau:

https://github.com/andymccurdy/redis-py/blob/1c2071762ad9b9288e786665990083e61c1cf355/redis/connection.py#L974

Client Redis sau đó sử dụng hồ bơi kết nối như vậy:

Vì vậy, vì ứng dụng Redis xử lý tất cả điều đó dưới mui xe, bạn có thể làm một cách an toàn những gì bạn muốn trực tiếp. Các kết nối sẽ được tạo ra một cách uể oải cho đến khi nhóm kết nối đạt công suất tối đa.

# app/connections.py 
def redis_client(**kwargs): 
    # Maybe read configuration/set default arguments 
    # kwargs.setdefault() 
    return redis.Redis(**kwargs) 

Tương tự, SQLAlchemy có thể sử dụng connection pooling as well.

Nói tóm lại, sự hiểu biết của tôi là:

  • Nếu thư viện khách hàng của bạn hỗ trợ kết nối tổng hợp, bạn không cần phải làm bất cứ điều gì đặc biệt để chia sẻ kết nối giữa các module và thậm chí đề. Bạn chỉ có thể định nghĩa một trình trợ giúp tương tự như redis_client() đọc cấu hình hoặc chỉ định các tham số mặc định.

  • Nếu thư viện khách hàng của bạn chỉ cung cấp các đối tượng kết nối cấp thấp, bạn sẽ cần phải đảm bảo quyền truy cập vào chúng là an toàn chỉ và an toàn ngã ba. Ngoài ra, bạn cần đảm bảo mỗi khi bạn trả lại kết nối hợp lệ (hoặc tăng ngoại lệ nếu bạn không thể thiết lập hoặc sử dụng lại một cái hiện có).

+0

Cảm ơn bạn đã trả lời kỹ lưỡng! Tôi đã không thực sự được coi là chủ đề an toàn và an toàn, tôi chắc chắn sẽ nghĩ về điều đó. Re: lỗi thông số kết nối, bắt tốt và sửa lỗi tốt. Re: khách hàng hồ bơi, cũng là một điểm tốt, nhưng ngay cả với một hồ bơi kết nối, bạn sẽ muốn tập trung khởi tạo và đảm bảo rằng bạn đang sử dụng cùng một hồ bơi trong suốt ứng dụng. – knite

+0

Re: chức năng trợ giúp đơn giản, tôi xem xét cách tiếp cận này ban đầu. Nó nhanh chóng trở nên khó chịu do mỗi mô-đun cần nhập khẩu awesome_db * và * gọi nó. – knite

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