התייעצות על שיטת העבודה בפרוייקט עם Java – MongoDB

YIM222

New member
התייעצות על שיטת העבודה בפרוייקט עם Java – MongoDB

שלום לכולם.
**אשמח אם אפשר לא לענות בזמן השבת , תודה**
**השאלות שלי הן בסוף אבל הקדמתי כדי שיהיה אפשר להבין מה הרקע)
יש לי כמה דילמות לגבי פרוייקט שאני מפתח והצורה שלסדר את התוכנה. קראתי וחקרתי על זה והגעתי לכמה מסקנות אך עדיין אני לא בטוח שזו הדרך הנכונה, והייתי שמח לדעת דברים שלא נעשו נכון, כדי שאדע לעשות refactor בשלב הזה ולא אח"כ.

בגדול זו תוכנה שאמורה להיות משחק אונליין של מולטי משתתפים, לא משחק כבד של 3D - משחק של אותיות וכיו"ב.
אני מתחיל בפיתוח מערכת מרובת משתתפים. כוונתי בזה גם שהמערכת תהיה כללית ותוכל לשמש אותי גם לפרוייקטים אחרות, ולDBS אחרים.
אני גם מתחיל לפתח ברמת הCore לCMD ואח"כ אפתח את זה בהתאם לפלטפורמה רצויה כגון Web/Mobile ועוד...
התוכנה ברמת השאילתות - אני לא חושב שהיא אמורה לעשות המון ולאחסן הרבה מידע ברמת המשתמשים וכאלה אבל ודאי שיהיה מידע לא מבוטל גם לכל משתמש כגון – נצחונות הפסדים נקודות, ובטח ברמת המערכת עצמה של הניהול משחקים ומידע וכיו"ב . אני חושב שהיחס בין הread to write הוא די שווה .
הדילמות שלי כרגע הם בעיקר ברמת הקשר עם הDB .
כדי שהמערכת תהיה מותאמת לשינוי DB בעתיד, אני מפתח בנפרד DBConnector שמכיל פונקציות ששולחות את השאילתות לDB , ואותם אני מכניס במערכת עצמה. ככה שבעתיד זה יהיה כמו API מסויים שרק צריך להתאים לו את הפונקציות המסויימות של הDB .
לא כל כך ידעתי אם לעשות את הconnector סטטי ובסוף החלטתי שכן, כי הוא מכיל פונקציות קבועות, של שירות, אני יודע שאולי אני טועה בזה.
כמו"כ קראתי במונגו שכדאי לעשות את הconnector סינגלטון, כדי לא ליצור הרבה pool , ושזה תומך בmulti threating .
Your instance of MongoClient (e.g. mongoClient above) will ordinarily be a singleton in your application.
https://www.mongodb.com/blog/post/getting-started-with-mongodb-and-java-part-i
דבר נוסף שלא הייתי סגור עליו זה האם לגרום לתוכנה לפתוח connection שישאר פתוח כל הזמן או בכל שאילתא לDB לפתוח ולסגור.
בסוף החלטתי שבכל קריאה לDB תהיה פונקציה פנימית שיוצרת קשר לdata , מבוצעת השאילתא המסויימת , ונסגר הקשר . כמו לדוגמא בקובץ DBConnector שצירפתי בפונקציה-
createNewUser(String userName, String email, String password)
יצרתי איזה פונקציה שמייצרת singleTon של האובייקט הזה, אבל אח"כ הבנתי שלא עשיתי נכון כי הconnection מתייצר כל פעם מחדש. (ראה את הפונקציה DBConnector.connectDB ) .
השאלות שלי הם :
1- האם לעשות את הDBconnector סטטיק זה נכון, או הוא גם צריך להיות instance . (בכלל לחדד את העניין מתי צריך להיות סטטיק ומתי לא, כי גם על המערכת עצמה יש לי את הדילמה הזו) .
2- הדרך הנכונה לייעל את הזיכרון היא ליצור פתיחה וסגירה בכל שאילתא, ואם נגיד השיטה הזו תתבצע לעשות את ה MongoClient סינגלטון ? (כי חשבתי שאולי זה או זה או זה, ואם זה יהיה סינגלטון במקרה תיאורתי של כמה משתמשים משתמש אחד יפתח ואחר במקביל יפתח את הclient ואז אחד יסגור והפונקציה של השני לא תתבצע) .
3- או שאולי הדרך הנכונה זה לפתוח Connection שנשאר פתוח כל זמן שהתוכנה רצה, ובדרך הזאת לעשות אותו סינגלטון שלא נוצר כל פעם אחד חדש .

אני מצרף כאן 2 קבצים של הDbConnector ושל הSystem כדי להמחיש את השיטת עבודה שלי.
תודה מראש לכל העונים ,
ואשמח לתשובות עניניות
בבקשה אל תשלחו אותי לקורסים למדעי המחשב, כי זה פחות יעזור אני יודע שיש השלמת חומר שצריכה להעשות ואני יכול להבין רק דרך העשייה.

שבת שלום
DBConnector
https://www.jdoodle.com/a/kIh

MultiUsersSystem
https://www.jdoodle.com/a/kIi
 

BravoMan

Active member
המתנתי שיענה לך מישהו שמכיר את mongoDB כי לי אין ניסיון

כלל עם mongo וניסיון מועט ביותר עם DB אחרים.
&nbsp
אז הנה דעה כללית בנושא:
אתה עושה טעות בכך שאתה מנסה לתכנן רכיב גישה ל-DB ודרך שימוש ב-DB לפני שתכננת עד הסוף את האפליקציה האמתית שלך - זו שתשתמש ב-DB.
&nbsp
בסה"כ, לכל מנגנוני ה-db כבר יש שכבות אבסטרקציה נחמדות עבור Java, וזה שתוסיף עוד שכבה דקה משלך לא יעזור לכלום אם תנסה להיות גינרי מידי.
&nbsp
אתה צריך קודם כל לתכנן איך ומתי המשחק שלך ירצה לגשת ל-DB, ואלו פעולות הוא ירצה לעשות שם, לפני שאתה כותב את המעטפת.
רק אז תוכל לכתוב משהו טוב ושימושי באמת, שלא יהווה סתם שכבת קוד מיותרת.
&nbsp
זכור גם, שה-client של המשחק, לא משנה אם הוא רץ בתוך דפדפן, בטלפון, או כיישום שולחני, לעולם לא ייגש ישירות ל-DB שלך.
אם תאפשר לו - זה יהיה אסון.
&nbsp
במקומך, הייתי מתרכז קודם בצד שרת - אפליקציה שתרוץ על השרת שלך ותקבל בקשות מה-client של המשחק.
איך היא תנהל את כל השיח ישפיע על הדרך בה היא תנהל את ה-DB.
&nbsp
לשאלות הספציפיות שלך:
1. אישית, אני חושב שלעשות את ה-DBConnector סטטי זה לא נכון.
ב-Java, רק פונקציות שירות בסיסיות למדי הן סטטיות.
היות ו-DBConnector אצלך מטפל במשאבים "חיים" - חיבור ל-DB שיש לפתוח ולסגור, נכון שהוא יהיה instance.
&nbsp
ככלל - הימנע משימוש במתודות סטטיות לכל דבר שמשויך אליו מידע משלו.
&nbsp
שים לב, ש-Singleton לא סטטי!
אבל כמובן, שזו רק תבנית, ובסוף דיזיין נכון תלוי בשימוש התוכנן.
&nbsp
2. זה תלוי לחלוטין ב-DB שאתה משתמש בו.
היצמד לתיעוד של היצרן, או ברר בפורום הרלוונטי שם מכירים את ה-DB היטב.
&nbsp
3. זכור ש-Singleton הוא יחיד לאורך כל חיי התוכנה שלך.
זה ה-Pattenr המקובל.
אם אתה צריך לייצר כמה מהם, כנראה שה-pattern לא ממש מתאים.
&nbsp
בהצלחה!
 

YIM222

New member
תודה רבה על העצות

אתה צריך קודם כל לתכנן איך ומתי המשחק שלך ירצה לגשת ל-DB, ואלו פעולות הוא ירצה לעשות שם, לפני שאתה כותב את המעטפת.
רק אז תוכל לכתוב משהו טוב ושימושי באמת, שלא יהווה סתם שכבת קוד מיותרת.

זה נשמע עצה מצויינת גם תעזור לי בתיכנון וגם יקל עליי בלהגיע לקוד. מה שמטריד אותי בזה, זה שנראה לי שאני אתחיל וארצה לראות דברים שנשמרו כמו משחקים של משתמשים וכאלה ויהיה קשה לתפקד על זה וליצור את כל הנתונים כל פעם מחדש בhardCode . (מן הסתם ארצה לבדוק שהמשתמש רואה את הנתונים שעברו, ושהמערכת יודעת ליצור משחקים בין כמה משתמשים וכיו"ב). אז אתה מתכוון שאת השלבים האלה להשאיר לסוף ?
אישית, אני חושב שלעשות את ה-DBConnector סטטי זה לא נכון. וכו'"
אז לגבי המערכת עצמה שנותנת לאובייקטים של התוכנה פונקציות שירותיות של פעולות שאפשר לעשות, יהיה נכון לכאורה לעשות את זה סטטי לא ?
זכור גם, שה-client של המשחק, לא משנה אם הוא רץ בתוך דפדפן, בטלפון, או כיישום שולחני, לעולם לא ייגש ישירות ל-DB שלך.
אם תאפשר לו - זה יהיה אסון.


כן זוכר :) אימצתי את זה מאז שאמרת לי את זה בהודעה אחרת.

ערב טוב
 

BravoMan

Active member
לא משנה מאיפה תתחיל לבנות את המערכת,

תמיד יהיה לך חלק "חסר" שתצטרך לזייף כדי לבצע בדיקות.
&nbsp
יש דרכים שונות "לזייף" הן את צד לקוח, הן את צד שרת, והן את ה-DB.
בזמנו, כשהתחלתי לפתח תוכנת לקוח לפני שהיה שרת בכלל שעינה, שמתי קבצים סטטיים עם JSON בתוכם, שידמו תשובה נכונה משרת שאמור היה לבנות את התשובות שלו דינמית מתוך DB.
&nbsp
אבל ל-clinet זה לא היה משנה. הוא לא יודע אם יש קובץ סטטי או קוד עם DB בצד שני.
התפקיד שלו היה לפרסר ולפעול בתאם, וזה כל מה שהייתי צריך לזמן פיתוח.
&nbsp
את השרת שמנגיש את הקבצים אפילו הייתי מרים מקומית על מחשב פיתוח שלי עם מודול Python בלי להסתבך עם דברים כמו Apache או nginx (אני אפילו לא מזכיר את IIS יימחה שמו וזכרו!)
 

YIM222

New member


סתם ככה שאלה לא קשורה אבל אתה בטח יודע
יש משהו לא טוב בפרקטיקה הזו? - במקרה שיש כמה cases in method , להעביר את המצב עם string . לדוגמא:

קוד:
//return user ID by identification (mail, userName) 
	public void getUserID(String identification, String key){
		//get the object id and return it as string either by mail or identification 
		
		if(key.equals("email")){
			//do something
		}
		else{
			//do something else
			
		}
		
		
		
		
	}
 

BravoMan

Active member
כמה דברים:

1. השוואת מחרוזות היא "פעולה יקרה", אז תשאל את עצמך אם ה-key שלך באמת צריך להיות מחרוזת.
אולי עדיף לו להיות קבוע מספרי, שאתה עדיין תראה בקוד כמלל, אבל המחשב יוכל להשוות כ-int בקלות.
&nbsp
2. אם בכל זאת החלטת על string, תשקול לעשות השוואה case insensitive כדי לא ליפול בגלל גודל אות.
compaireIgnoreCase.
&nbsp
3. ב-Java משפט switch תומך במחרוזות החל מגרסה 7.
אז אם יש לך כמה אפשרויות על תעשה הרבה if else if אלא תשתמש ב-switch עם case.
&nbsp
4. אם ה-do something זה לשלוף או למלא ערך לפי ה-key, שקול שימוש ב-HashMap או מבנה אחר מסוג "מילון" שיחסוך לך את ה-if וישלוף ערך לפי מפתח.
זה יהיה הרבה יותר יעיל, קל, ויפה!
 

BravoMan

Active member
זה ממש לא יוריד את הקריאות וההבנה, להפך!

אם אני מבין אותך נכון, אתה רוצה לממש פונקציה שמקבלת 2 פרמטרים:
1. פרמטר כלשהו שמזהה משתמש
2. ערך קבוע בתוכנה שלך לפיו אתה מפרש את הפרמטר הראשון.

לערך כזה קוראים "דגל" (flag), וכמעת אף פעם לא משתמשים במחרוזת לצורך כזה, אלא אם יש סיבה מיוחדת שהופכת את זה לנוח בתוך הקוד של הפונקציה.

שימוש במחרוזת יגרור מספר בעיות:
1. בכל מקום בקוד שתשתמש בו, תצטרך לכתוב ידנית את המחרוזת, ותוכל בקלות לעשות שגיאת כתיב.
הקומפיילר לא יתקן אותך, ויהיו לך באגים שתתקשה לתקן.

מה יקרה אם במקום אחד תכתוב email ובמקום אחר תתבלבל ותכתוב emale?

2. במקום לבצע השוואה אחת על ערך מספרי, רק בשביל לבדוק את המילה email המחשב יבצע לפחות 5 השוואות.
יותר אם תנסה להשוות בלי להתחשב באות גדולה או קטנה.

3. לא תהיה דרך קלה וברורה לדעת אלו דגלים קיימים, מה שיקשה על הבנת הקוד ועל הרחבה שלו, במידה ותרצה להוסיף דגלים (או להפך, להוריד דגל שהפך ללא רלוונטי).

יש שתי דרכים מקובלות ונוחות לממש דגלים:
שיטה ראשונה: להשתמש ב-emun
קוד:
public enum UserIdType {
    EMAIL, FACEBOOK, GOOGLE, USERNAME
}

public void getUserID(String identification, UserIdType key) {
    switch(key) {
        case UserIdType.EMAIL:
            //do something
            break;

        case UserIdType.FACEBOOK:
            //do something else
    }
}

לשיטה הזו מספר יתרונות:
  • הקומפיילר תמיד יבדוק את האיות שלך, ואם תשגה בשם הדגל תקבל שגיאת קומפילציה.
  • הקומפיילר יוודא שרק דגלים שהגדרת ישלחו כפרמטר לפונקציה, וכל דבר אחר יגרור שגיאת קומפילציה.
  • קל להבין אלו דגלים קיימים, הם כולם במקום אחד ובנויים בצורה אחידה.
    סביבות פיתוח יאפשרו גילוי והבנה שלהם בלחיצת כפתור (במקום לחפש מחרוזות בכל מיני מקומות בקוד או לרדוף אחרי הערות שאולי בכלל שכחת לעדכן)
  • אם תכתוב משפט switch ותשכח לטפל באחד הדגלים, סביבת הפיתוח תתריע בפניך, כי היא יודעת בדיוק אלו case יכולים להיות.
לשיטה הזו יש גם חסרונות:
  • אי אפשר לשלב כמה דגלים בפרמטר אחד.
    (זה כנראה לא רלוונטי לדוגמה הספציפית שלך, אבל יש מקרים בהם זה נחוץ)
  • אי אפשר לעשות חישובים מתמטיים או חישובי סיביות על הדגלים.
  • יותר מסובך לשמור דגל כזה בתוך קובץ או לשלוח אותו ל-DB.

כאן נכנסת לתמונה שיטה שניה: להשתמש בקבועים מטיפוס int
קוד:
public static final int EMAIL = 1;
public static final int FACEBOOK = 2;
public static final int GOOGLE = 3;
public static final int USERNAME = 4;

public void getUserID(String identification, int key) {
    switch(key) {
        case EMAIL:
            //do something
            break;

        case FACEBOOK:
            //do something else
    }
}

[לחץ וגרור להזזה]

השיטה הזו מאפשרת לשלב דגלים בעזרת אופרטור or |, לעשות פעולות מתמטיות על דגלים וכמובן לשמור דגל בקלות בקובץ או ב-DB.

אבל, אתה מאבד מספר יתרונות מהשיטה הקודמת:
  • תצטרך לממש בעצמך בדיקה עם ערך הדגל הוא חוקי, הקומפיילר לא ידע אלו מספרים מותר לשלוח לפונקציה.
  • כנ"ל במשפטי switch: תצטרך לוודא בעצמך שכיסית את כל האפשרויות.
  • אם לא תזהר, הגדרת הדגלים עלולה להתפזר למקומות שונים, מה שיקשה על הבנת הקוד.

אני חושב שלמקרה שלך השיטה הראשונה הכי מתאימה, אבל בלי לראות את הקוד המלא של הפונקציה, או לפחות להבין מה היא אמורה לעשות, קשה לדעת מה השיקולים שלך.

תבחר בעצמך לפי יתרונות וחסרונות, אבל בכל מקרה הייתי ממליץ להתרחק משימוש במחרוזות למקרה הזה!
 

YIM222

New member
וואוו תודה על התשובה המפורטת

אני לגמרי אקח את זה בחשבון בפעולות האלו.

לגבי הפונקציה הזו, אני מנסה בה להשיג את הID של המשתמש מהDB ע"י או אימייל או שם משתמש.

בסוף זה יצא לי ככה שאני לא צריך if-else כי זה ממש השם של הfield שאני מבצע עליו שאילתא (לא משנה מאיזה צד), אז אולי עדיף לי להשאיר את זה ככה .

קוד:
public String getUserID(String identification, String key){
		//get the object id and return it as string either by mail or identification 
		Document query = new Document().append(key, identification);
		Document document = collection.find(query).first();
		String objectID = document.get("_id").toString();
		return objectID;
		
		
		
	}
 

BravoMan

Active member
שמחתי לעזור.

אם הקוד שלך נראה כמו בדוגמה האחרונה, בהחלט הגיוני ונכון להשתמש במחרוזת.
&nbsp
בגלל זה כל הזמן סייגתי את דברי שהכל תלוי במימוש הפונקציה.
עדיין, אני ממליץ לכתוב את המחרוזת במקום אחד בלבד בתור ערך של קבוע (static final) מכמה סיבות:
&nbsp
1. זה ייתן לך השלמה אוטומטית וימנע שגיאות כתיב.
2. זה יקל עליך להחליף את המחרוזת אם תצטרך (שינוי במקום אחד במקום הרבה מקומות בקוד)
3. זה יאפשר לתת למחרוזת שם שמתאר אותה טוב יותר או להוסיף הערה על משמעות בקוד בלי להוסיף לך הרבה הקלדות מיותרות בכל שימוש בה.
 

YIM222

New member
אגב סינגלטון לא סטטי אבל מייצרים אותו בדרך סטטית ,

אין ברירה אחרת , לא ?
 

BravoMan

Active member
Singleton ב-Java בד"כ מוחזר ממתודה

סטטית שבודקת אם כבר קיים instanse שלו ומחזירה אותו, ואם עדיין לא קיים, מייצרת ומאתחלת את ה-instance.
&nbsp
לפעמים, האתחול נפרד מהבקשה לקבל instance תלוי בדרישות המערכת.
&nbsp
אבל אובייקט ה-singleton עצמו אינו סטטי. הוא instance לכל דבר ועניין, רק שיש אחד ממנו.
 
למעלה