توسعه‌ی پروژه‌ی aleph با repl

clojure

#1

سلام بر همگی!

من الآن میخوام با aleph یه پروژه انجام بدم، ولی مشکلی که دارم اینه که نمیتونم پروژه رو ریفرش کنم. (یه جوری که وقتی کدی میزنم و ذخیره میکنم، بتونم نتیجشو توی بروزر ببینم.)
قصدم این نیست که بروزر refresh بشه، میخوام وقتی بروزر رو به صورت دستی refresh میکنم، اطلاعات جدید ببینم! (اگه تابعی رو تغییر داده بودم)

الآن کد من یه چیزیه شبیه این (تقریبا کپی از داکیومنت aleph):

(ns web.core
  (:require [aleph.http :as http])
  (:gen-class))

(defn handler [req]
  {:status 200
   :headers {"content-type" "text/plain"}
   :body "hello world!"})

(http/start-server handler {:port 8000})

باید تغییری توی کد ایجاد بشه؟ یا راه دیگه‌ای هست؟
توی داکیومنت چیزی ندیدم. توی سرچ هم کاملا مطالب غیر مرتبط وجود داره.


#2

دلیلش این نیست که پروژه باید ریکمپایل بشه؟


#3

دوست دارم که نشه :grinning:
خوب ما یه سیستمی داریم (cider در ایمکس و fireplace در ویم و چیزای دیگه برای ادیتورهای دیگه) که میتونه به یه repl وصل بشه و هر تابع رو در هر لحظه که میخوایم باهاش evaluate کنیم.

توی این تاپیک سمیر عزیز زحمت کشیدن توی یه ویدیو نسخه‌ی emacs ماجرا رو خیلی کامل توضیح دادن.

من هم الآن میتونم اینکار رو انجام بدم، ولی مثل اینکه وقتی کد (http/start-server handler {:port 8000}) زده میشه، یه آبجکت ساخته میشه توی یه thread و شروع میکنه به listenکردن پورت 8000 و انگار وقتی یه تابع رو evaluate میکنم، نادیده میگیره و نتیجه‌ی نسخه‌ی قبلی تابع رو به کاربر نمایش میده.

#object[aleph.netty$start_server$reify__5806 0x4c67a28d "[email protected]"]

وقتی دوباره میخوام این کد رو evaluate کنم، بعد از یه وقفه‌ی ۲ثانیه‌ای خطا میده Address already in use. مثل اینکه میخواد یه thread دیگه ایجاد کنه و به همون آدرس bind کنه.


#4

استفاده از repl برای دیباگ کردن و تست کردن و evaluate کردن و این چیزاست بیشتر نه برای کار نهایی، چون repl فقط یک ترد موقتی هست.
و این احتمال هم وجود داره که من هنوز منظور شما رو متوجه نشده باشم.


#5

باید سیستمت رو به بحالتی بسازی که هر کامپوننت قابلیت restart شدن داشته باشه. بعد با یه کتابخونه ای مثل hawk یه watcher بسازی برای Filesystem و بر اساس تغییراتی که مد نظرت هست کامپوننت aleph رو restart کنی. اما این روش خیلی کلوژری نیست.

برای روش درست و کلوژری باید سیستم رو بتونی از توی repl اجرا کنی و یه رفرنس به سیستم نگه داری و هر وقت نیاز داشتی از اون رفرنس برای restart کردن کل سیستم یا کامپوننت خاصی استفاده کنی.


#6

خب این بازم میشه restart کردن اما خب به صورت watched. در کل من فکر می کنم در نهایت ریستارت کردن لازمه، درسته؟


#7

بله باید اون instance ی که از aleph هست restart بشه. با watch زیاد کلوژری نیست.

البته راه هایی هم هست که نیاز به restart نباشه. ولی غیر معمول هستند. مثلا اینکه یه atom درست کرد که handler ها رو نگه داره و با تغییر فایل اون هندالر رو ریلود کنه و ترد aleph را deref کردن اون اتم هندلر ها رو بگیره. ولی فکر نمی کنم بصرفه به درد سرش


#8

اما در کل aleph طراحیش به شکلیه که خیلی بهتره باهاش به صورت async کار کرد. واسه hellhound منم از aleph استفاده می کنم. کاری که من انجام دادم اینه که یه هندلر دارم فقط برای aleph و کارش اینه که ریکوئست رو پوش می کنه توی یه ستریم که ستریم خورجی کامپوننت وب سرور هست (‌تو hellhound هر کامپوننت یه ستریم ورودی داره و یه ستریم خروجی) بعد کامپوننت های دیکه ریکوئست رو به صروت یه hashmap می خونن و روش کار می کنن و در آخر پاسش می دن به ورودی همون کامپوننت وب سرور که اونم به عنوان ریسپانس برش می گردونه به کلاینت. اینجوری هیچ وقت نیاز به ریستارت کردن وب سرور نیست و فقط کامپوننتی که نیاز به ریستارت داره اتوماتیک کامپایل می شه (‌تو repl) و ریستارت می شه و از ستریم ورودیش شروع می کنه به consume کردن از ورودی ستریم خودش و سیستم به کارش ادامه میده


#9

منم برای development میخوامش.
میخوام aleph مثل هر فریموورک وبی توی هر زبانی، وقتی یه end-point رو ادیت میکنم، لازم نباشه ببندم و بازش کنم!
کاش یه debug mode داشت. (به صورت built-in)


تمام چیزایی که اینجا گفته شد رو فهمیدم.
ولی نمیدونم باید چیکارش کنم :sweat_smile:
روشی که توی clojure-koans استفاده شده، اینجا به درد نمیخوره؟ (با توجه به اینکه وضعیت آبجکت باید تغییر کنه، فکر نکنم به درد بخوره.)

با watcher که مخالفم. همونطور که گفتید «کلوژری نیست». خیلی هم ضایع هست!
روش کلوژریش هم نمیدونم چطوری میتونم رفرنس به سیستم نگه دارم. متوجه نشدم کلا!


یه چیز دیگه!
الآن به ذهنم رسید و امتحان کردم مثل اینکه درست جواب میده ولی نمیتونم زیاد تستش کنم. باید برم یه جایی :joy:
من از تابع handler به عنوان «لولو سر خرمن» استفاده کنم و خودم با یه تابع معمولی کلوژر کار هندلینگ ریکوئستها رو انجام بدم.
مثل اینکه اینطوری جواب میده!

(ns web.core
  (:require [aleph.http :as http])
  (:gen-class))

(defn my-handler [req]
  {:status 200
   :headers {"content-type" "text/plain"}
   :body (str (get req :uri) (rand-int 10))})

(defn handler [req]
  (my-handler req))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (http/start-server handler {:port 8000}))

اینطوری وقتی تابع my-handler رو تغییر بدم، واقعا محتوای صفحه‌ی وب تغییر میکنه!
الآن وقتی (rand-int 10) رو به (rant-int 100) تغییر بدم و تابع رو eval کنم، نتیجه‌ی صفحه تغییر میکنه.

فقط یه کم مسخره بازیه.


#10

راهی که رفتی اوکی هست اما یه نکته داره اونم اینه که همه ترد ها ممکنه این آپدیت رو نداشته باشن. واسه همین گفتم atom می تونه کمک کنه.

اما تابع هندلر می تونه یه router برگردونه به جای هندلر مستقیم.

این رو هم در نظر داشته باش که aleph فقط یه وب سرور هست نه یه وب فریم ورک واسه همین نباید انتظار داشت که اتوماتیک ریستارت شه و از این حرفا.


#11

آیا aleph
pull based هستش ؟


#12

unlike both Java and core.async relies on pulling data towards the consumer rather than having it pushed.

جواب سوال خودم


#13

خوب پس همینو پیش میرم اگه بازم به مشکل خوردم یه فکر دیگه میکنم. چون فکر میکنم این دردسرش از «استفاده از اتم» کمتر باشه. (boiler plate کمتر)

میدونم. منم دنبال این بودم که خودم هرچی میخوام سرهم کنم. (مجبور به استفاده از یه پکیج از پیش تعریف شده نباشم)
ولی کاش یه راه حلی میساختن خودشون (یا داخل لایبرری یه debug mode مینوشتن یا یه لایبرری مجزا که واسه develop بتونیم تو فراخوانی کنیم) در غیر این صورت سرعت dovelop خیلی پایین میاد.

خوب http-request میزنیم و جواب میگیریم ازش پس میشه گفت از pull technology ساپورت میکنه.
ولی از اونجایی که websocket هم داره، احتمالا میشه گفت push technology هم داره.
البته نمیدونم چقدر این حرفم درست باشه.


#14

منظوره من نوعی که stream نوشته شده
stream میتونه به طرز push یا pull نوشته شده باشه
این کتابخانه pull based نوشته شده


#15

من شخصا مشکلی با watch ندارم اما کلا برای کار با پروژه های کمپایل شده بهترین گزینه نیست.
در مورد کاری که شما نیاز به انجامش دارید احتمالا TDD/BDD روش مناسبی باشه، اینو میگم چون روش کاری خودمه و خیلی کمک میکنه که در هر قدم کوچک دوبار فکر کنم و یک بار انجام بدم، در ضمن بعدا به تست های نوشته شده رجوع کنم و به عنوان بخشی از داکیومنت ازشون استفاده کنم.
یک تست ساده بنویس و وقتی fail شد، کد ساده ای بنویس که تست pass بشه و بعدش ببین چطور میتونی بهترش کنی(Red/Green/Refactor).
بعضی ها اعتقاد دارند تست کردن جلوی کار روی منطق و طرحی رو میگیره، در صورتی که تست نویسی یعنی دقیقا طراحی بهتر اگر به Test Driven Design اعتقاد داشته باشید.
استفاده از repl بیشتر زمانی بکار میاد که نیاز به کار با دیتا داشته باشیم یا اینکه بخوایم ارتباط با چیزهایی خارج از پروژه رو تست کنیم و البته دیباگ کردن یا حتی تغییرات سریع دیتا در دیتابیس در بعضی شرایط خاص.

به هر حال هدف ما اینجا اینه که نظر و تجربمونو به اشتراک بزاریم و با توجه به نظر دیگران به جمع بندی علمی و عملی برسیم. گاهی ممکنه کاری رو مدتها به روش خاصی انجام بدیم و فکر کنیم که بهترین روش از نوع خودشه. بعد یکی بیاد یه نکته ای بگه که زندگیمون عوض بشه و به دشت و بیایون سر بزاریم و شب و روز به اون نکته جدید فکر کنیم :grin:


#16

خوب TDD و BDD واسه بخش بعدی کاره! کاری که ما با REPL انجام میدیم، یه جورایی تست خودمونه که ببینیم درست نوشتیم این کد رو یا نه. قبل از اینکه بخوایم یه کامپایل کامل انجام بدیم و نتیجه‌ی تستها رو ببینیم. (یه مرحله قبل از اینکه به اجرای ۱۰۰۰خط test برسیم)
مساله‌ی منم این نیست که بخوام توی repl خروجی یه function رو ببینم و از کارش مطمئن شم. مساله‌ی من اینه که نمیخوام با هر خط کدی که تغییر میدم، یه بار کامپایل کنم کل سیستم رو. اینکار خیلی زمان‌بر هست (داریم درمورد jvm حرف میزنیم. بنده خدا خیلی خستست) و اصلا نمیشه develop سریع داشت.


حالا اگه بخوایم جدای بحث این پروژه و این لایبرری خاص صحبت کنیم، من از repl برای این استفاده میکنم که بدون نیاز به recompile کل پروژه و ران کردن پروژه و پاس کاری دیتا بین چندتا تابع، نتیجه‌ی کار یه تابع خاصی که همین الآن فقط همینو ادیت کردم ببینم.
از اونجایی که تو کلوژر اصلا side effect و state نداریم و همه چی مطمئنا immutable هست (مگر زمان استفاده از atom)، نتیجه‌ی تست با repl دقیقا همون نتیجه‌ی recompile و تست فایل نهایی رو داره. چون مطمئنیم state وجود نداره، پس خروجی یک تابع تکی با خروجی همون تابع در کنار توابع دیگه، هیچ فرقی نداره.


#17

در TDD تست نویسی دقیقا اولین کاریه که انجام میدیم, یعنی تست قبل از کد نوشته میشه


#18

میدونم tdd چیه. تاحالا استفاده‌ی دنیای واقعی ازش نداشتم ولی میدونم چیه.
مشکل من ران کردن تسته. هر بار من یه کدی میزنم نمیتونم تست ران کنم. کامپایل شدن و اجرای تست توی کلوژر خیلی طول میکشه.

repl جای tdd رو نمیگیره. فقط یه مرحله جلوتر قرار میگیره.


#19

برنامه ای که side effect نداشته باشه در اصل هیچ کاری انجام نمیده و به درد کسی نمیخوره :smile:


#20

منظورم side effect کم بود :sweat_smile:
تابعی که با global کار نداشته باشه. همون pure function خودمون. (اگه این تابع ۵ رو بگیره ۱۰ رو return کنه، همیشه در مقابل ۵، به ما ۱۰ رو میده. فرقی نمیکنه بقیه‌ی کد چه کاری انجام بده)