Web appOpen in Telegram

Threadمن یه جمع بندی میخوام بکنم دررابطه با transaction, implicit transaction, autocommit, autobegin, ... و برداشت خودم رو بگم…

47 messages · –
S
من یه جمع بندی میخوام بکنم دررابطه با transaction, implicit transaction, autocommit, autobegin, ... و برداشت خودم رو بگم شاید به درد کسی خورد و اگه جاییش فکر میکنید مشکل داره بگید. در رابطه با postgresql صحبت میکنم. قبل از هر چیزی بگم که کلا تمام statement هایی که تو postgresql هست تو transaction انجام میشن. همشون! حتی read. https://www.postgresql.org/docs/17/tutorial-transactions.html > PostgreSQL actually treats every SQL statement as being executed within a transaction. If you do not issue a BEGIN command, then each individual statement has an implicit BEGIN and (if successful) COMMIT wrapped around it. پس یا transaction داریم یا transaction block داریم. وقتی autocommit روشن باشه تمام statement ها به صورت مفهومی اینشکلیه: BEGIN; statement1; COMMIT; BEGIN; statement2; COMMIT; مثلا با psql وصل شید atutocommit روشن هست دیفالت. اگه خاموش باشه میشه: BEGIN; statement1; statement2; COMMIT; اگه میخواید چند تا statement رو یکجا توی یک transaction بذارید از BEGIN استفاده میکنید. حتی اگه autocommit روشن باشه باز اونا تو یک transaction قرار میگیرن. حالا DBAPI یا همون pep 249 تو پایتون چیه؟ همون طوری که wsgi یه استاندارد بود گفت باید وب اپ ها چه استانداردیو رعایت کنن، اینم یه استاندارد برای اینه که درایورها باید چه استانداردی رو رعایت کنن برای کار با دیتابیس. پس مهمه بدونیم درایوری که استفاده میکنیم با pep 249 سازگار هست یا نه. asyncpg سازگار نیست psycopg هست. این pep میگه باید autocommit درایور ها off باشه دیفالت. پس وقتی داریم درباره رفتارشون صحبت میکنیم باید درایور رو هم ذکر کنیم. الان با psycopg اگه بیاید بزنیم: cursor.execute("INSERT ...") cursor.execute("UPDATE ...") این سر اجرا کردن اولین execute میاد یه transaction به صورت implicit رو اجرا میکنه! این جا اصلا صحبتی از sqlalchemy نیست. اون کاری نمیکنه، اون یه لاگ مینداره که آقا درجریان باشید یه implicit transaction ای ران شده و ما توشیم. چرا لاگ میندازه؟ چون که sqlalchemy با پیشفرض این pep داره کار میکنه و باهاش سازگاره. https://docs.sqlalchemy.org/en/21/tutorial/dbapi_transactions.html#committing-changes You might have noticed the log line “BEGIN (implicit)” at the start of a transaction block. “implicit” here means that SQLAlchemy did not actually send any command to the database; it just considers this to be the start of the DBAPI’s implicit transaction. این یعنی اگه از asyncpg استفاده کنیم implicit begin نداریم؟ چرا. خود نویسنده sqlalchemy میگه داریم و ما اینو با یه emulation layer انجام میدیم که با باقی کتابخونه سازگار بشه: https://github.com/sqlalchemy/sqlalchemy/discussions/6864#discussioncomment-1142958 You're probably using asyncpg which means you actually are using such a DBAPI emulation layer, as asyncpg is not by itself a pep-249 library. SQLAlchemy provides an emulation layer that allows it to implicitly begin, as the entirety of the rest of SQLAlchemy is built on this assumption. اگر تو sqlalchemy ما autobegin روشن باشه (دیفالت) و یه کوئری ای زده باشیم که transaction برامون باز شده باشه و بعد جلوتر بیایم session.begin() بزنیم ارور میده میگه already in transaction. پیشنهاد من اینه که autocommit و autobegin هردو False باشن. هر وقت نیاز داشتیم .begin میزنیم. و خب من پیشنهاد میکنم که sessionmaker یا wrapper ای که برای ساخت session دارید رو inject کنید توی روتر ها نه خود session رو. بعد دیگه باز و بسته کردن session تمام scope ای که باهاش کار میکنید دست شماست. فانکشن های لایه سرویس هم میشه هر چند تا بود داخل یه transaction رانش کرد.
  1. -
    ممنون از توضیحات کاملت سروش جان آیا اینکه ما مجبور میشیم توی تمام کوئری هامون هرچند کوچیک و چند خطی ببریمش زیر یه context manager انتی پترن نیست؟ یعنی مثلا من چندتا روت برا crud دارم برای یه entity که کدشونم بسیار سادس بدون وابستگی به چیزی و حالت uow ندارن. و من باید توی همه روت ها این indent async with session.begin() .... رو داشته باشم. چیز خاصی نیست یا من ذهنم به حالت جنگو عادت کرده حساس شدم :)
    1. S
      نه واقعا اوکیه. عوضش میدونی کجا باز میکنی کجا تموم میشه. شاید خواستی زودتر تموم کنی و منابع رو همون جا رها کنی. بعد میتونی به جای session روی session_maker هم بگین بزنیا. خودش کار ساختن session و clean-up ترنزاکشن رو انچام میده برات (۲ تا cm داخل هم باز نمیکنید)
      1. -
        ازینکه گفتی session maker رو اینجکت کنیم بجای خود سشن این بود درسته؟ یعنی این کد کل پیشنهادی بود که دادی: # db/base session_factory: async_sessionmaker[AsyncSession] = async_sessionmaker( bind=engine, expire_on_commit=False, autocommit=False, autobegin=False, ) # deps SessionMakerDep = Annotated[AsyncSession, Depends(session_factory)] @router.post('/login', response_model=TokenResponse) async def login( body: LoginRequest, services: ServicesDep, session_maker: SessionMakerDep, ) -> TokenResponse: async with session_maker() as session, session.begin(): admin = await services.admins.authenticate(body.username, body.password) return TokenResponse(access_token=create_access_token(admin.id))
        1. A
          یه نکته ای هم من اضافه کنم چند بار دیگه هم بحث شده بود کلا اضافه کردن دیپندنسی چیزای اینفرایی و سرویس هایی که در تکنیکالی(و نه بیزنسی) وجودشون اضافه شده رو درست نمیدونم دلایلش هم زیاده ولی مهمترینش اینه یه چیزی داره به سیستم اضافه میشه که وجودش توجیه نداره راه حلشم اینه که یه موجودیت گلوبال یا سینگلتون تعریف کنید مثلا session_maker رو توی همون فایل sqla.py بذارید و یدونه اینستنس ازش بسازید و بیت تمام اپلیکیشن ازش استفاده کنید الان دارید به ازای هر فانکشن کال یه سشن میکر میسازید که لازم نبوده بسازید توی همین مثال شما برای لاگین از نظر منطقی فقط body نیازه بقیش نیاز نیست بهتره که به عنوان پارامتر فانکشن داده نشه
          1. S
            با کلیت حرفت موافقم ولی اگه مثلا این دیتابیس رو به صورت dependency تعریف کنی بهت این امکان رو میده توی تست ها خیلی ساده override ش کنی: app.dependency_override[get_db] = lambda : ... یک بار اینو توی conftest مثلا میذاری.
            1. S
              الان کاری که من کردم هم singleton هست. همیشه یه دونه از این DatabaseManager ها داریم. یا مثلا میشد: @cache async def get_db() -> DataabaseManager: return DatabaseManager(..., ....)
            2. A
              اینم راهکار داره اگر از مسیر متغیر گلوبال بریم -> patch کردن اگر از مسیر ‌سینکلتون بریم -> mock کردن و این رو قبلا (چند سال پیش) صحبت کرده بودیم این راهکار رو داده بودم اما الان ذهنیتم عوض شده و اینطوریم که تستی که تهش نتیجه گرفتیم دیتابیس رو ماک کنیم اصلا وجودش بنظرم خیلی معنادار نیست و یکم شفاف تر بخوام بگم اصلا غلطه
              1. S
                راهکار که داره آره ولی اون تک خطی که من نوشتم ساده تر نبود ؟ با تیکه ی دوم هم موافقم ولی در نظر بگیر اون lambda ای که من نوشتم لزوما برای ماک کردن نیست. میتونه جایگزینی دیتابیس اصلی با یه دیتابیس تست باشه. (برای اون مورد که میگی اون تستا معنایی نداره و اینا) میتونه یه testcontainers باشه
                1. A
                  ببین اره این که یه لایه بیایم عقب تر خیلی دستمونو باز میکنه راه حل های مختلفی میشه زد حتی برای پیاده سازی شاردینگ اصلا مجبوریم اینکارو بکنیم ولی نکتم اینه اصلا هدف این که دیپندسی پستگرس رو بیاریم توی روتر کار منطقی ای نیست به استدلال مشابه تمام تکنولوژی ها باید بیان توی روتر و نکته بدش اینه که شما اینستنس های متعدیی از session_maker خواهید داشت
                    1. A
                      توی کد شما با کلمه cache جلوشو گرفتید
                      1. S
                        هم توی کدی که cache داشت جلوش گرفته میشه هم توی کد قبلی که اگه دقت کنی من db_manager رو یه instance تو ماژول ساختم و get_db من فقط داره اونو ریترن میکنه. صرفا wrap شده توی یک فانکشن که بتونم تو Depends بذارمش. همه جا تک instance داریم فقط
                  1. S
                    همه ی endpoint ها همه ی infra ها رو ندارن درسته؟ و اینکه این که توی همه endpoint ها مثلا database و broker توی پارامتر هاش تکرار بشه ولی در عوض با یک خط توی تست بشه override کرد کار راحت تری هست یا این intance ها import و استفاده بشن و تو تست patch بشن؟
                    1. A
                      ببین نکته اول اینه که اصلا چرا داری اورراید میکنی؟ من اخیرا نگاهم اینه نباید اورراید بشه چیو میخوای به چی اورراید کنی؟ معمولا همه تست ها یه جایی برای کانفیگ دارن و یه جایی دارن که بتونیم یسری چیزارو در حد کل سیستم تغییر بدی نه؟ خوب یکی از چیزایی که ممکنه بخوای تغییر بدی همینه حجم کدشم کمه حالا نمیدونم دقیقا ۱ خط میشه یا بیشتر ولی این که ۳ ۴ خط کد کمتر و بیشتر توی پروژه باشه واقعا اهمیتی نداره اینکه فانکشنت پارامتر هاش سر جای خودش باشه خیلی مهم تره
                      1. S
                        یعنی توی تست باید ایمیل و نوتیفیکیشن و مسیج و اینا بفرستیم؟ اورراید که باید بشه ولی فکر کنم منظورت این بود که از تو کانفیگ یه فایل خونده بشه
          2. -
            اینکه گفتید هربار دارم یه سشن میکر میسازم رو درک نکردم. هربار خود session maker رو دارم میسازم یا session میسازم؟
            1. A
              وقتی depend میذارید سر روتر هر بار session maker میسازه دیگه همین راهی که سروش گفت البته با cache جلوشو گرفته
              1. S
                نه فقط cache کد اولی cache نداره همون آبجکت ریترن میشه
        2. S
          تقریبا آره. با یکم تفاوت: class DatabaseManager: def __init__: # setup self.session_maker = ... @contextmanager def begin() async with self.session_maker.begin() as db_session: yield db_session db_manager = DatabaseManager(... async def get_db: return db_manager DatabaseT = Annotated[DatabaseManager, depends(get_db)] # inside router: async with db.begin() as db_session: await service1(db_Session...) await service2(db_Session...)
          1. -
            این کد رو نگاه کنید # db/base session_factory: async_sessionmaker[AsyncSession] = async_sessionmaker( bind=engine, expire_on_commit=False, autocommit=False, autobegin=False, ) @asynccontextmanager async def session_scope() -> AsyncGenerator[AsyncSession]: async with session_factory() as session: yield session # deps async def db_session() -> AsyncGenerator[AsyncSession]: async with session_scope() as session: yield session SessionDep = Annotated[AsyncSession, Depends(db_session)] def get_services(session: SessionDep) -> Services: return Services(session) ServicesDep = Annotated[Services, Depends(get_services)] @router.post('/login', response_model=TokenResponse) async def login( body: LoginRequest, services: ServicesDep, session: SessionDep, ) -> TokenResponse: async with session.begin(): admin = await services.admins.authenticate(body.username, body.password) return TokenResponse(access_token=create_access_token(admin.id)) اینجا من نمیتونم session maker رو به عنوان dep بدم. چون توی Services من session رو نیاز دارم. باید قبلش یه session داشته باشم و اینکه توی روت دقت کنید من توی خود services الان session رو دارم. اما باز نیاز دارم session رو inject کنم تا begin کنم حالا من نیاز دارم توی تمام روت هام این دو services و session رو اینجکت کنم. یا اینکه دیپندنسی services رو وردارم و توی خود روت instantiate اش کنم در کل احساس میکنم بد دارم جلو میرم
            1. A
              اگر اصرار به این مسیر داری یه راهش اینه اصلا سشن دیتابیس رو دیپندنسی سرویست کنی(اینطوری سرویست توی دلش یه sessionداره) به راهشم اینه که serviceت به جای session خود session maker رو بگیره
              1. -
                اصراری به این مسیر ندارم. مسیر درست نمیدونم چیه راه اولی که گفتید فکر میکنم بهتر باشه. اینکه سرویس یه سشن اماده رو بگیره بنظرم بهتره تا خودش سشن بسازه اگر خودش سشن بسازه اینطوری باید توی خودش هم begin و commit بکنم. که به گفته دوستان بهترش اینه caller اینکارو بکنه
                1. A
                  دومی هم درسته و مشکلی نداره مشکل اون که دوستان گفتن با session بود نه session maker
            2. S
              چیزی که من نوشتم شبیه چیزی که شما نوشتید نیست البته در مورد dependency یه بار دیگه ببینید چیکار کردم. بعد شما کد خودتون رو هم میتونید درستش کنید به شکل های مختلف. یکی اینکه اگه حتما میخواید آبجکت Service بسازید نمیخواد dependency ش کنید. داخل روتر خیلی راحت بعد پاس بدید session رو بهش. چرا dependency هست؟ ولی از اون بهتر چرا آبجکت Service میخواید داشته باشید؟ چرا یه ماژول service با یه سری فانکشن توش نه؟ بدونه آبجکت بدون هیچی. خود ماژول هم namespace فانکشناتون هست. ساده سریع راحت. بعد به فانکشن ها session رو پاس بدید
              1. A
                دقیقا مشکل من با ریپازیتوری همینه پترن جاواییه و به خاطر ضعف جاوا و c# و ایناست توی پایتون میتونید فانکشن داشته باشید اکثرا وجود کلاس بی معنیه
                1. X
                  ریپازیتوری به کلاس کاری ندارهاا 🤔 فقط یک لایه abstraction برای چگونگی دریفات دیتاست. میتونه با چندتا تابع پیاده سازی بشه. اینکه با کلاس پیاده سازی بشه هم سود خودش رو داره اگه دقیقا بدونیم چرا از کلاس استفاده میکنیم. جز این میشه با تابع پیاده سازیش کرد.
                  1. K
                    تا حالا DexProtect برخورد داشتی؟
                  2. A
                    ریپازیتوری رو میتونی توضیح بدی چیه و چجوری استفاده میشه؟ یه موجودیت statefull نیست مگه؟
                    1. X
                      چه state ای رو میخاد ذخیره کنه؟ میتونه ذخیره کنه ولی لزوما دلیل وجودش ذخیره state نیست. مثلا session رو میتونیم به عنوان param بهش بدیم. ریپازیتوری فقط یک لایه abstraction برای چگونگی دریافت دیتا هست. اینکه business layer دیگه به infra کاری نداشته باشه. اینکه دیتا چگونه read یا write میشه یا از database میاد ویا یک فایل csv یا json. بخاطر اینکه business layer خودمون رو خالی از پیچیدگی چگونگی دریافت و ذخیره دیتا نگهداریم ازش استفاده میکنیم. اینکه چطور با توابع پیاده سازی کنیم میگم بهت
                      1. A
                        مرسی راستش نظم منفی تر شد حتی از اونی که به نظرم میومد هم الان غیر منطقی تره
                        1. X
                          آره اون مثالی که زدم اشتباه بود همینطور که آقا سروش گفتن session خودش مرتبط با data layer هست و پاس دادنش یعنی دوباره داریم وابستگی ایجاد میکنیم بین دو لایه پس یه مثال کاملتر نیازه که استفاده از uow رو هم توضیح بده ولی حقیقتا من این کارو functional انجامش ندادم تاحالا که ببینم چجوریه ولی میشه صدرصد چون به کلاس ربطی نداره. repository pattern یک پترن اشتباه یا بی فایده نیست. این پترن ها ممکنه به تنهایی منطقی نباشن وقتی ما domain model نداشته باشیم و فقط data model داشته باشیم که مستقیما از sqlalchemy base ارث میبره استفاده از repository بخش زیادی از ارزش خودش رو از دست میده چون یکی از دلایل اصلیش جدا کردن domain layer از data layer هست تا ما منطق برناممون رو راحت بدون فکر کردن به اینکه sqlalchemy یا هر orm دیگه چه مشکلاتی داره و چجوری دیتارو read و write میکنه توسعه بدیم و موقع تست کردن بتونیم data layer رو با یک implementation fake جایگزین‌ کنیم و غیره. من دیدم اکثر برنامه‌ نویسها فقط با data model کار میکنن پس برای همین منطقی هست تا repository pattern استفاده نشه اما به معنی تین نیست که یک پترن اشتباه باشه که فقط چون تون java و csharp استفاده میشده تو پایتون هم استفاده میشه. domain model repository pattern unit of work pattern هر کدوم این پترن ها مشکلاتی رو حل میکنند و استفادشون باهم اون ارزش واقعی رو بهشون میبخشه و فقط یک لایه بیهوده abstraction نیستن.
                          1. A
                            دوست عزیز من یه چند بار نوشتم حرفمو و پاک کردم چون هر طوری بگم خیلی تند حرف زدم و ممکنه شخصی برداشت بشه این جمله هاتون یسری جمله های رایجه و تقریبا همشون اشتباهه و ممکنه نپذیرید اگر مثال خوبی داشتید که اثر مثبتشونو نشون بدید میتونیم صحبت کنیم لطفا هم یه مثال کامل باشه چون هر جایی قبل از شما تا امروز مثال زده ما تا شروع کردیم به نقد کردن هی میگن حالا این که مثاله
                          2. A
                            این دقیقا یکی از غلط ترین جمله هایی هست که شنیدم نمیدونم اولین بار کی این جنایت رو توی مهندسی نرم افزار رواج داد
                          3. A
                            اینا مشکلاتی که وجود نداشتن و خودشون به وجود اوردن رو حل میکنن (در واقع حتی حلش نمیکنن پاس کاری میکنن و پخشش میکنن)
                            1. S
                              جسارتا وقتی چیزی رو نقد یا نقض میکنید، یک توضیحات تکمیلی پشت‌بند اش بدید و روش و رویکرد درست از نظر خودتون رو بفرمایید. اینطوری آدم سبک سنگین میکنه و اگر حرف شما کاملا درست هم باشه با دلیل میپذیره و قانع میشه وگرنه دوباره همون حکایت بازگو کردن جمله های رایج میشه.
                              1. A
                                سبحان جان چند نکته بنظرم میرسه که بگم ۱.روش درست چیزی نیست که بشه از داخل پیام های توی گروه یادش گرفت پس سوال خوبیه ولی از مسیر بدی داری دنبالش میگردی ۲.تو صحبتات ۲ تا جمله خیلی بد داری: روش درست از نظر من چیه که تو بخوای سبک سنگین کنی؟؟! من کی ام که بخوام نظر بدم؟ شما کی ای که بخوای سبک سنگین کنی؟ این روش غیر علمی و نادقیق و فاجعه زاست که یکی برا خودش نظر بده یکی برا خودش سبک سنگین کنه. مسیر علمی و درست تهش اهمیتی نداره که تهش من و شما بپذیریم یا نپذیریم
                                1. S
                                  یه موردی هستش و شما با در نظر نگرفتن اون از مسیر صحبتی که کردم منحرف شدی عزیز. کسی قرار نیست کسی رو قضاوت کنه یا بازخواست کنه و حتی کسی قرار نیست روش درست یا درست ترین رو بگه. شما حرف اون عزیز رو نقض کردی بر پایه و تکیه به یک دانشی نقض کردی، هر چند اون دانش درست یا غلط، گفتم که بیان اش کنی تا دو نفر دیگه هم ادامه اش بدن و گپ و گفت کامپیوتری داشته باشیم، همین.
                                  1. A
                                    کی در مورد قضاوت شخصی حرف زد؟
        3. A
          اون کسی اینکارو کنه شما ردیس رو هم باید همینکارو کنی چند وقت بعدش rabbitmq یا kafka رو هم همین کارو باید بکنی بعدش click house بعد spark بعد elastic search اونوقت میبینی توی سرویس هات ۶ تا متغیر اولت اصلا بیزنسی نیست بعد یسری از دوستان میان ایده های خلاقانه میزنن میگن اصلا همه رو بذاریم توی یه کانتکس و دچار سیل بحران های بعدی میشن و همه چی برمیگرده به این که یه چیزی یه جایی هست که وجودش منطقی نیست
    2. S
      برای notifixation و اینام دقیقا مثل همون چیزی که xin گفت یه فلگی، یه آیدی ای چیزی که نشون بده که نیاز به notification هست یا نیست از لایه سرویس ریترن کن، توی همون روتر تصمیم بگیر باید بفرستی یا نه.
PPython Backend FellowPython Backend Fellow@PythonFellow · group · Tech
1 286members52writing in 30 days
Venue feed Open in Telegram

An open public feed from the search index ChatCrawler — “Google for public Telegram”; refreshed as the venue is crawled. Times are UTC.

Public content only, official Telegram API. About · FAQ · What we do not do · Remove a page · Catalog · Search · How we count