JavaRush /جاوا بلاگ /Random-UR /ہم ڈیٹا بیس سے متعلق ہر چیز کو شامل کرتے ہیں۔ (حصہ 2) - "...

ہم ڈیٹا بیس سے متعلق ہر چیز کو شامل کرتے ہیں۔ (حصہ 2) - "A سے Z تک جاوا پروجیکٹ"

گروپ میں شائع ہوا۔
سب کو سلام. میں آپ کو یاد دلاتا ہوں: پہلے حصے میں ہم نے Flyway کو شامل کیا۔ آئیے جاری رکھیں۔

docker-compose.yml میں ڈیٹا بیس شامل کرنا

اگلا مرحلہ مین docker-compose.yml میں ڈیٹا بیس کے ساتھ کام کو ترتیب دے رہا ہے۔ آئیے ڈیٹا بیس کو docker-compose فائل میں شامل کریں:
version: '3.1'

services:
 jrtb-bot:
   depends_on:
     - jrtb-db
   build:
     context: .
   environment:
     BOT_NAME: ${BOT_NAME}
     BOT_TOKEN: ${BOT_TOKEN}
     BOT_DB_USERNAME: ${BOT_DB_USERNAME}
     BOT_DB_PASSWORD: ${BOT_DB_PASSWORD}
   restart: always
 jrtb-db:
   image: mysql:5.7
   restart: always
   environment:
     MYSQL_USER: ${BOT_DB_USERNAME}
     MYSQL_PASSWORD: ${BOT_DB_PASSWORD}
     MYSQL_DATABASE: 'jrtb_db'
     MYSQL_ROOT_PASSWORD: 'root'
   ports:
     - '3306:3306'
   expose:
     - '3306'
میں نے اپنی درخواست میں یہ لائن بھی شامل کی:
depends_on:
 - jrtb-db
اس کا مطلب ہے کہ ہم ایپلیکیشن شروع کرنے سے پہلے ڈیٹا بیس کے شروع ہونے کا انتظار کرتے ہیں۔ اگلا، آپ مزید دو متغیرات کا اضافہ دیکھ سکتے ہیں جن کی ہمیں ڈیٹا بیس کے ساتھ کام کرنے کی ضرورت ہے۔
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
ہم انہیں ڈوکر کمپوز میں اسی طرح حاصل کریں گے جیسے ٹیلیگرام بوٹ کے لیے - ماحولیاتی متغیرات کے ذریعے۔ میں نے ایسا اس لیے کیا کہ ہمارے پاس صرف ایک جگہ ہے جہاں ہم ڈیٹابیس کے صارف نام اور اس کے پاس ورڈ کی قدریں مرتب کرتے ہیں۔ ہم انہیں اپنی درخواست کی ڈاکر امیج اور اپنے ڈیٹا بیس کے ڈوکر کنٹینر پر منتقل کرتے ہیں۔ اس کے بعد ہمیں اپنے SpringBoot کو ڈیٹا بیس کے لیے متغیرات کو قبول کرنے کے لیے سکھانے کے لیے Dockerfile کو اپ ڈیٹ کرنے کی ضرورت ہے۔
FROM adoptopenjdk/openjdk11:ubi
ARG JAR_FILE=target/*.jar
ENV BOT_NAME=test.javarush_community_bot
ENV BOT_TOKEN=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}", "-Dbot.token=${BOT_TOKEN}", "-Dspring.datasource.username=${BOT_DB_USERNAME}", "-jar", "app.jar"]
اب ہم Dockerfile میں ڈیٹا بیس متغیرات شامل کرتے ہیں:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
متغیر کی قدریں مختلف ہوں گی۔ جنہیں ہم Dockerfile میں منتقل کریں گے، تاہم، پہلے سے طے شدہ اقدار کی ضرورت ہوتی ہے، اس لیے میں نے کچھ درج کیا۔ ہم آخری لائن کو دو عناصر کے ساتھ بڑھاتے ہیں، جس کی مدد سے ہم DB صارف نام اور پاس ورڈ کو ایپلیکیشن لانچ کرنے کے لیے بھیجیں گے:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
Dockerfile میں آخری لائن (جو ENTRYPOINT سے شروع ہوتی ہے) بغیر لپیٹے ہونی چاہیے۔ اگر آپ ٹرانسفر کرتے ہیں تو یہ کوڈ کام نہیں کرے گا۔ آخری مرحلہ ڈیٹا بیس میں متغیرات کو منتقل کرنے کے لیے start.sh فائل کو اپ ڈیٹ کرنا ہے۔
#!/bin/bash

# Pull new changes
git pull

# Prepare Jar
mvn clean
mvn package

# Ensure, that docker-compose stopped
docker-compose stop

# Add environment variables
export BOT_NAME=$1
export BOT_TOKEN=$2
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'

# Start new deployment
docker-compose up --build -d
ہم پہلے ہی جانتے ہیں کہ docker-compose چلانے سے پہلے ماحولیاتی متغیرات کو کیسے شامل کیا جائے۔ ایسا کرنے کے لیے، آپ کو صرف برآمد کرنے کی ضرورت ہے var_name=var_value.. لہذا، ہم صرف دو لائنیں شامل کرتے ہیں:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
یہ وہ جگہ ہے جہاں ہم ڈیٹا بیس کا صارف نام اور پاس ورڈ سیٹ کرتے ہیں۔ بلاشبہ، bash اسکرپٹ کو چلاتے وقت ان متغیرات کو پاس کرنا ممکن ہوگا، جیسا کہ ہم بوٹ کے نام اور ٹوکن کے لیے کرتے ہیں۔ لیکن مجھے لگتا ہے کہ یہ غیر ضروری ہے۔ اصل میں ڈیٹا بیس تک رسائی حاصل کرنے کے لیے، آپ کو سرور کا آئی پی جاننے کی ضرورت ہے جس پر ڈیٹا بیس کو تعینات کیا جائے گا، اور درخواست کے لیے اجازت شدہ IP پتوں کی فہرست میں شامل ہوں۔ میرے لیے تو یہ کافی ہے۔ بنیاد رکھ دی گئی ہے: اب آپ ایسی چیزیں کر سکتے ہیں جو ایک ڈویلپر کے لیے زیادہ قابل فہم ہوں - کوڈ لکھیں۔ اس سے پہلے، ہم وہی کر رہے تھے جو DevOps انجینئرز کرتے ہیں - ماحول کو ترتیب دینا۔

ریپوزٹری پرت کو شامل کرنا

عام طور پر ایک درخواست کی تین پرتیں ہوتی ہیں:
  1. کنٹرولرز ایپلی کیشن میں داخلے کے مقامات ہیں۔
  2. خدمات وہ ہیں جہاں کاروباری منطق کام کرتی ہے۔ ہمارے پاس یہ جزوی طور پر پہلے سے موجود ہے: SendMessageService کاروباری منطق کا واضح نمائندہ ہے۔
  3. ریپوزٹریز ڈیٹا بیس کے ساتھ کام کرنے کی جگہ ہیں۔ ہمارے معاملے میں، یہ ٹیلیگرام بوٹ ہے۔
اب ہم ایک تیسری تہہ شامل کریں گے - ریپوزٹریز۔ یہاں ہم اسپرنگ ایکو سسٹم - اسپرنگ ڈیٹا سے ایک پروجیکٹ استعمال کریں گے۔ آپ Habré پر اس مضمون میں اس کے بارے میں پڑھ سکتے ہیں ۔ ہمیں کئی نکات کو جاننے اور سمجھنے کی ضرورت ہے:
  1. ہمیں JDBC کے ساتھ کام کرنے کی ضرورت نہیں ہوگی: ہم براہ راست اعلی تجرید کے ساتھ کام کریں گے۔ یعنی، POJO کو ذخیرہ کریں جو ڈیٹا بیس میں موجود ٹیبلز سے مطابقت رکھتے ہیں۔ ہم ایسی کلاسوں کو entity کہیں گے ، جیسا کہ انہیں باضابطہ طور پر Java Persistence API میں کہا جاتا ہے (یہ ایک ORM کے ذریعے ڈیٹا بیس کے ساتھ کام کرنے کے لیے انٹرفیس کا ایک عام سیٹ ہے، یعنی JDBC کے ساتھ کام کرنے پر ایک خلاصہ)۔ ہمارے پاس ایک ہستی کی کلاس ہوگی جسے ہم ڈیٹا بیس میں محفوظ کریں گے، اور وہ بالکل اسی ٹیبل پر لکھے جائیں گے جس کی ہمیں ضرورت ہے۔ ڈیٹا بیس میں تلاش کرتے وقت ہمیں وہی چیزیں موصول ہوں گی۔
  2. اسپرنگ ڈیٹا ان کے انٹرفیس کے سیٹ کو استعمال کرنے کا مشورہ دیتا ہے: JpaRepository , CrudRepository ، وغیرہ... دوسرے انٹرفیس ہیں: ایک مکمل فہرست یہاں مل سکتی ہے ۔ خوبصورتی یہ ہے کہ آپ ان کے طریقوں کو لاگو کیے بغیر استعمال کرسکتے ہیں۔ مزید یہ کہ، ایک مخصوص ٹیمپلیٹ ہے جس کا استعمال کرتے ہوئے آپ انٹرفیس میں نئے طریقے لکھ سکتے ہیں، اور وہ خود بخود نافذ ہو جائیں گے۔
  3. موسم بہار ہماری ترقی کو اتنا ہی آسان بناتا ہے جتنا یہ ہو سکتا ہے۔ ایسا کرنے کے لیے، ہمیں اپنا انٹرفیس بنانا ہوگا اور اوپر بیان کردہ ان سے وراثت حاصل کرنی ہوگی۔ اور اس لیے کہ اسپرنگ کو معلوم ہو کہ اسے اس انٹرفیس کو استعمال کرنے کی ضرورت ہے، ریپوزٹری تشریح شامل کریں۔
  4. اگر ہمیں کسی ایسے ڈیٹا بیس کے ساتھ کام کرنے کا طریقہ لکھنے کی ضرورت ہے جو موجود نہیں ہے، تو یہ بھی کوئی مسئلہ نہیں ہے - ہم اسے لکھیں گے۔ میں آپ کو دکھاؤں گا کہ وہاں کیا اور کیسے کرنا ہے۔
اس مضمون میں، ہم TelegramUser کے پورے راستے کو شامل کرنے کے ساتھ کام کریں گے اور اس حصے کو بطور مثال دکھائیں گے۔ ہم باقی کو دوسرے کاموں پر پھیلائیں گے۔ یعنی، جب ہم /start کمانڈ پر عمل کرتے ہیں، تو ہم اپنے صارف کے ڈیٹا بیس پر ایکٹیو = true لکھیں گے۔ اس کا مطلب یہ ہوگا کہ صارف بوٹ استعمال کر رہا ہے۔ اگر صارف پہلے سے ہی ڈیٹا بیس میں ہے، تو ہم فیلڈ کو اپ ڈیٹ کریں گے active = true۔ /stop کمانڈ پر عمل کرتے وقت، ہم صارف کو حذف نہیں کریں گے، لیکن صرف ایکٹو فیلڈ کو فالس میں اپ ڈیٹ کریں گے، تاکہ اگر صارف بوٹ کو دوبارہ استعمال کرنا چاہتا ہے، تو وہ اسے شروع کر سکتا ہے اور وہیں سے اٹھا سکتا ہے جہاں سے اس نے چھوڑا تھا۔ اور تاکہ جانچ کرتے وقت ہم دیکھ سکیں کہ کچھ ہو رہا ہے، ہم ایک /stat کمانڈ بنائیں گے: یہ فعال صارفین کی تعداد کو ظاہر کرے گا۔ ہم بوٹ، کمانڈ، سروس پیکجز کے آگے ایک ریپوزٹری پیکج بناتے ہیں۔ اس پیکیج میں ہم ایک اور ہستی بناتے ہیں ۔ ہستی پیکج میں ہم TelegramUser کلاس بناتے ہیں:
package com.github.javarushcommunity.jrtb.repository.entity;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;

/**
* Telegram User entity.
*/
@Data
@Entity
@Table(name = "tg_user")
public class TelegramUser {

   @Id
   @Column(name = "chat_id")
   private String chatId;

   @Column(name = "active")
   private boolean active;
}
یہاں آپ دیکھ سکتے ہیں کہ ہمارے پاس javax.persistence پیکیج کی تمام تشریحات ہیں۔ یہ عام تشریحات ہیں جو تمام ORM نفاذ کے لیے استعمال ہوتی ہیں۔ پہلے سے طے شدہ طور پر، Spring Data Jpa ہائبرنیٹ کا استعمال کرتا ہے، حالانکہ دیگر نفاذات کو استعمال کیا جا سکتا ہے۔ یہاں ان تشریحات کی فہرست ہے جو ہم استعمال کرتے ہیں:
  • ہستی - اشارہ کرتا ہے کہ یہ ڈیٹا بیس کے ساتھ کام کرنے کے لیے ایک ہستی ہے۔
  • ٹیبل - یہاں ہم ٹیبل کے نام کی وضاحت کرتے ہیں؛
  • ID - تشریح بتاتی ہے کہ ٹیبل میں بنیادی کلید کون سی فیلڈ ہوگی؛
  • کالم - ٹیبل سے فیلڈ کے نام کا تعین کریں۔
اگلا، ہم ڈیٹا بیس کے ساتھ کام کرنے کے لیے ایک انٹرفیس بناتے ہیں۔ عام طور پر، ایسے انٹرفیس کے نام ٹیمپلیٹ - EntiryNameRepository کا استعمال کرتے ہوئے لکھے جاتے ہیں۔ ہمارے پاس ایک TelegramuserRepository ہوگی:
package com.github.javarushcommunity.jrtb.repository;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
* {@link Repository} for handling with {@link TelegramUser} entity.
*/
@Repository
public interface TelegramUserRepository extends JpaRepository<TelegramUser, String> {
   List<TelegramUser> findAllByActiveTrue();
}
یہاں آپ دیکھ سکتے ہیں کہ میں نے کیسے شامل کیا findAllByActiveTrue() طریقہ ، جسے میں کہیں بھی نافذ نہیں کرتا ہوں۔ لیکن یہ اسے کام کرنے سے نہیں روکے گا۔ اسپرنگ ڈیٹا سمجھے گا کہ اسے tg_user ٹیبل سے تمام ریکارڈ حاصل کرنے کی ضرورت ہے جس کا فعال فیلڈ = true ۔ ہم TelegramUser entity کے ساتھ کام کرنے کے لیے ایک سروس شامل کرتے ہیں (ہم اس تناظر میں SOLID سے انحصار الٹا استعمال کرتے ہیں کہ دیگر اداروں کی خدمات براہ راست کسی دوسرے ادارے کے ذخیرے کے ساتھ بات چیت نہیں کر سکتیں - صرف اس ادارے کی خدمت کے ذریعے)۔ ہم پیکج میں ایک سروس TelegramUserService بناتے ہیں، جس کے فی الحال کئی طریقے ہوں گے: صارف کو محفوظ کریں، صارف کو اس کی ID کے ذریعے حاصل کریں اور فعال صارفین کی فہرست دکھائیں۔ پہلے ہم TelegramUserService انٹرفیس بناتے ہیں:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
* {@link Service} for handling {@link TelegramUser} entity.
*/
public interface TelegramUserService {

   /**
    * Save provided {@link TelegramUser} entity.
    *
    * @param  telegramUser provided telegram user.
    */
   void save(TelegramUser telegramUser);

   /**
    * Retrieve all active {@link TelegramUser}.
    *
    * @return the collection of the active {@link TelegramUser} objects.
    */
   List<TelegramUser> retrieveAllActiveUsers();

   /**
    * Find {@link TelegramUser} by chatId.
    *
    * @param chatId provided Chat ID
    * @return {@link TelegramUser} with provided chat ID or null otherwise.
    */
   Optional<TelegramUser> findByChatId(String chatId);
}
اور، حقیقت میں، TelegramUserServiceImpl کا نفاذ:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.repository.TelegramUserRepository;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
* Implementation of {@link TelegramUserService}.
*/
@Service
public class TelegramUserServiceImpl implements TelegramUserService {

   private final TelegramUserRepository telegramUserRepository;

   @Autowired
   public TelegramUserServiceImpl(TelegramUserRepository telegramUserRepository) {
       this.telegramUserRepository = telegramUserRepository;
   }

   @Override
   public void save(TelegramUser telegramUser) {
       telegramUserRepository.save(telegramUser);
   }

   @Override
   public List<TelegramUser> retrieveAllActiveUsers() {
       return telegramUserRepository.findAllByActiveTrue();
   }

   @Override
   public Optional<TelegramUser> findByChatId(String chatId) {
       return telegramUserRepository.findById(chatId);
   }
}
یہاں یہ واضح رہے کہ ہم TelegramuserRepository آبجیکٹ کا انحصار انجیکشن (ایک کلاس مثال متعارف کروائیں) استعمال کرتے ہیں Autowired تشریح کا استعمال کرتے ہوئے ، اور کنسٹرکٹر پر۔ آپ یہ ایک متغیر کے لیے کر سکتے ہیں، لیکن یہ وہی طریقہ ہے جس کی سپرنگ فریم ورک ٹیم ہمیں تجویز کرتی ہے۔

بوٹ کے لیے اعداد و شمار شامل کرنا

اگلا آپ کو /start اور /stop کمانڈز کو اپ ڈیٹ کرنے کی ضرورت ہے۔ جب /start کمانڈ استعمال کی جاتی ہے، تو آپ کو ڈیٹا بیس میں نئے صارف کو محفوظ کرنے اور اسے Active = true پر سیٹ کرنے کی ضرورت ہوتی ہے۔ اور جب /اسٹاپ ہو تو صارف کا ڈیٹا اپ ڈیٹ کریں: set active = false۔ آئیے StartCommand کلاس کو ٹھیک کریں :
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Start {@link Command}.
*/
public class StartCommand implements Command {

   private final SendBotMessageService sendBotMessageService;
   private final TelegramUserService telegramUserService;

   public final static String START_MESSAGE = "Привет. Я Javarush Telegram Bot. Я помогу тебе быть в курсе последних " +
           "статей тех авторов, котрые тебе интересны. Я еще маленький и только учусь.";

   public StartCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
       this.sendBotMessageService = sendBotMessageService;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public void execute(Update update) {
       String chatId = update.getMessage().getChatId().toString();

       telegramUserService.findByChatId(chatId).ifPresentOrElse(
               user -> {
                   user.setActive(true);
                   telegramUserService.save(user);
               },
               () -> {
                   TelegramUser telegramUser = new TelegramUser();
                   telegramUser.setActive(true);
                   telegramUser.setChatId(chatId);
                   telegramUserService.save(telegramUser);
               });

       sendBotMessageService.sendMessage(chatId, START_MESSAGE);
   }
}
یہاں ہم TelegramuserService آبجیکٹ کو کنسٹرکٹر کو بھی دیتے ہیں، جس کے ساتھ ہم نئے صارف کو محفوظ کریں گے۔ مزید، جاوا میں اختیاری کی خوشیاں استعمال کرتے ہوئے، درج ذیل منطق کام کرتی ہے: اگر ہمارے پاس ڈیٹا بیس میں کوئی صارف ہے، تو ہم اسے صرف ایکٹو بناتے ہیں، اگر نہیں، تو ہم ایک نیا فعال بناتے ہیں۔ سٹاپ کمانڈ:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.telegram.telegrambots.meta.api.objects.Update;

import java.util.Optional;

/**
* Stop {@link Command}.
*/
public class StopCommand implements Command {

   private final SendBotMessageService sendBotMessageService;
   private final TelegramUserService telegramUserService;

   public static final String STOP_MESSAGE = "Деактивировал все ваши подписки \uD83D\uDE1F.";

   public StopCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
       this.sendBotMessageService = sendBotMessageService;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
       telegramUserService.findByChatId(update.getMessage().getChatId().toString())
               .ifPresent(it -> {
                   it.setActive(false);
                   telegramUserService.save(it);
               });
   }
}
ہم TelegramServiceTest کو StopCommand پر اسی طرح پاس کرتے ہیں۔ اضافی منطق یہ ہے: اگر ہمارے پاس ایسی چیٹ آئی ڈی والا صارف ہے، تو ہم اسے غیر فعال کر دیتے ہیں، یعنی ہم ایکٹیو = غلط سیٹ کرتے ہیں۔ آپ اپنی آنکھوں سے یہ کیسے دیکھ سکتے ہیں؟ آئیے ایک نئی کمانڈ/stat بناتے ہیں، جو بوٹ کے اعدادوشمار کو ظاہر کرے گا۔ اس مرحلے پر، یہ تمام صارفین کے لیے دستیاب سادہ اعدادوشمار ہوں گے۔ مستقبل میں، ہم اسے محدود کر دیں گے اور رسائی صرف منتظمین کے لیے کر دیں گے۔ اعداد و شمار میں ایک اندراج ہوگا: فعال بوٹ صارفین کی تعداد۔ ایسا کرنے کے لیے، CommandName میں ویلیو STAT("/stat") شامل کریں۔ اگلا، StatCommand کلاس بنائیں:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Statistics {@link Command}.
*/
public class StatCommand implements Command {

   private final TelegramUserService telegramUserService;
   private final SendBotMessageService sendBotMessageService;

   public final static String STAT_MESSAGE = "Javarush Telegram Bot использует %s человек.";

   @Autowired
   public StatCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
       this.sendBotMessageService = sendBotMessageService;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public void execute(Update update) {
       int activeUserCount = telegramUserService.retrieveAllActiveUsers().size();
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), String.format(STAT_MESSAGE, activeUserCount));
   }
}
یہاں سب کچھ آسان ہے: ہمیں retrieveAllActiveUsers طریقہ استعمال کرتے ہوئے تمام فعال صارفین کی فہرست ملتی ہے اور مجموعہ کا سائز حاصل ہوتا ہے۔ ہمیں اب چڑھتے ہوئے کلاسز کو بھی اپ ڈیٹ کرنے کی ضرورت ہے: CommandContainer اور JavarushTelegramBot تاکہ وہ ہماری ضرورت کی نئی سروس کو منتقل کرنا سیکھیں۔ کمانڈ کنٹینر:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import com.google.common.collect.ImmutableMap;

import static com.github.javarushcommunity.jrtb.command.CommandName.*;

/**
* Container of the {@link Command}s, which are using for handling telegram commands.
*/
public class CommandContainer {

   private final ImmutableMap<String, Command> commandMap;
   private final Command unknownCommand;

   public CommandContainer(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {

       commandMap = ImmutableMap.<String, Command>builder()
               .put(START.getCommandName(), new StartCommand(sendBotMessageService, telegramUserService))
               .put(STOP.getCommandName(), new StopCommand(sendBotMessageService, telegramUserService))
               .put(HELP.getCommandName(), new HelpCommand(sendBotMessageService))
               .put(NO.getCommandName(), new NoCommand(sendBotMessageService))
               .put(STAT.getCommandName(), new StatCommand(sendBotMessageService, telegramUserService))
               .build();

       unknownCommand = new UnknownCommand(sendBotMessageService);
   }

   public Command retrieveCommand(String commandIdentifier) {
       return commandMap.getOrDefault(commandIdentifier, unknownCommand);
   }

}
یہاں ہم نے نقشے میں ایک نئی کمانڈ شامل کی اور اسے TelegramUserService کنسٹرکٹر کے ذریعے منتقل کیا۔ لیکن خود بوٹ میں، صرف کنسٹرکٹر بدل جائے گا:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
اب ہم TelegramUserService کو دلیل کے طور پر پاس کرتے ہیں، آٹو وائرڈ تشریح شامل کرتے ہوئے۔ اس کا مطلب ہے کہ ہم اسے درخواست کے سیاق و سباق سے حاصل کریں گے۔ ہم HelpCommand کلاس کو بھی اپ ڈیٹ کریں گے تاکہ تفصیل میں ایک نیا شماریاتی کمانڈ ظاہر ہو۔

دستی جانچ

آئیے ڈیٹا بیس کو docker-compose-test.yml سے شروع کرتے ہیں اور JavarushTelegramBotApplication کلاس میں بنیادی طریقہ۔ اگلا ہم کمانڈز کا ایک سیٹ لکھتے ہیں:
  • /stat - ہم توقع کرتے ہیں کہ اگر ڈیٹا بیس خالی ہے، تو اس بوٹ کو استعمال کرنے والے صفر لوگ ہوں گے۔
  • /start - بوٹ شروع کریں؛
  • /stat - اب ہم توقع کرتے ہیں کہ بوٹ 1 شخص استعمال کرے گا۔
  • / اسٹاپ - بوٹ کو روکیں؛
  • /stat - ہم امید کرتے ہیں کہ دوبارہ 0 لوگ اسے استعمال کریں گے۔
"A سے Z تک جاوا پروجیکٹ": ڈیٹا بیس سے متعلق ہر چیز کو شامل کرنا۔  حصہ 2 - 2اگر نتیجہ آپ کے لیے ایک جیسا ہے، تو ہم کہہ سکتے ہیں کہ فعالیت نے صحیح طریقے سے کام کیا اور بوٹ ٹھیک سے کام کر رہا ہے۔ اگر کچھ غلط ہو جاتا ہے، تو اس سے کوئی فرق نہیں پڑتا: ہم بنیادی طریقہ کو ڈیبگ موڈ میں دوبارہ شروع کرتے ہیں اور واضح طور پر پورے راستے سے گزرتے ہیں تاکہ معلوم ہو کہ غلطی کیا تھی۔

ہم ٹیسٹ لکھتے اور اپ ڈیٹ کرتے ہیں۔

چونکہ ہم نے کنسٹرکٹرز کو تبدیل کیا ہے، ہمیں ٹیسٹ کلاسز کو بھی اپ ڈیٹ کرنے کی ضرورت ہوگی۔ AbstractCommandTest کلاس میں ، ہمیں ایک اور فیلڈ شامل کرنے کی ضرورت ہے - TelegramUserService کلاس ، جو تین کمانڈز کے لیے درکار ہے:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
اگلا، CommandContainer میں init() طریقہ کو اپ ڈیٹ کریں :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
StartCommand میں آپ کو getCommand() طریقہ کو اپ ڈیٹ کرنے کی ضرورت ہے۔
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
سٹاپ کمانڈ میں بھی:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
اگلا، آئیے نئے ٹیسٹوں کو دیکھتے ہیں۔ آئیے StatCommand کے لیے ایک عام ٹیسٹ بنائیں :
package com.github.javarushcommunity.jrtb.command;

import static com.github.javarushcommunity.jrtb.command.CommandName.STAT;
import static com.github.javarushcommunity.jrtb.command.StatCommand.STAT_MESSAGE;

public class StatCommandTest extends AbstractCommandTest {
   @Override
   String getCommandName() {
       return STAT.getCommandName();
   }

   @Override
   String getCommandMessage() {
       return String.format(STAT_MESSAGE, 0);
   }

   @Override
   Command getCommand() {
       return new StatCommand(sendBotMessageService, telegramUserService);
   }
}
یہ سادہ ہے۔ اب آئیے اس بارے میں بات کرتے ہیں کہ ہم ڈیٹا بیس کے ساتھ کام کرنے کی جانچ کیسے کریں گے۔ ہم نے پہلے جو کچھ کیا وہ یونٹ ٹیسٹ تھے۔ ایک انضمام ٹیسٹ ایک درخواست کے متعدد حصوں کے درمیان انضمام کی جانچ کرتا ہے۔ مثال کے طور پر ایپلی کیشنز اور ڈیٹا بیس۔ یہاں سب کچھ زیادہ پیچیدہ ہو جائے گا، کیونکہ جانچ کے لیے ہمیں ایک تعینات ڈیٹا بیس کی ضرورت ہے۔ لہذا، جب ہم اپنے ٹیسٹ مقامی طور پر چلاتے ہیں، تو ہمارے پاس docker-compose-test.yml سے چلنے والا ڈیٹا بیس ہونا چاہیے۔ اس ٹیسٹ کو چلانے کے لیے، آپ کو پوری SpringBoot ایپلیکیشن چلانے کی ضرورت ہے۔ ٹیسٹ کلاس میں SpringBootTest تشریح ہے جو ایپلیکیشن شروع کر دے گی۔ لیکن یہ طریقہ ہمارے لیے کام نہیں کرے گا، کیونکہ جب ایپلی کیشن لانچ ہوگی، ٹیلی گرام بوٹ بھی لانچ ہوگا۔ لیکن یہاں ایک تضاد ہے۔ ٹیسٹ مقامی طور پر ہماری مشین پر اور عوامی طور پر GitHub ایکشنز کے ذریعے چلائے جائیں گے۔ پوری ایپلیکیشن کے آغاز کے ساتھ ہی ٹیسٹ پاس کرنے کے لیے، ہمیں ٹیلی گرام بوٹ پر درست ڈیٹا کے ساتھ ان کو چلانا چاہیے: یعنی اس کے نام اور ٹوکن سے... اس لیے، ہمارے پاس دو اختیارات ہیں:
  1. اس لیے بوٹ کا نام اور ٹوکن پبلک کریں اور امید رکھیں کہ سب کچھ ٹھیک ہو جائے گا، کوئی اسے استعمال نہیں کرے گا اور ہمارے ساتھ مداخلت نہیں کرے گا۔
  2. ایک اور طریقہ کے ساتھ آو.
میں نے دوسرا آپشن منتخب کیا۔ SpringBoot ٹیسٹنگ میں DataJpaTest تشریح ہوتی ہے، جو اس لیے بنائی گئی تھی کہ ڈیٹا بیس کی جانچ کرتے وقت، ہم صرف وہی کلاسز استعمال کرتے ہیں جن کی ہمیں ضرورت ہوتی ہے اور دوسروں کو تنہا چھوڑ دیتے ہیں۔ لیکن یہ ہمارے لیے مناسب ہے، کیونکہ ٹیلیگرام بوٹ بالکل بھی لانچ نہیں ہوگا۔ اس کا مطلب ہے کہ اسے ایک درست نام اور ٹوکن پاس کرنے کی ضرورت نہیں ہے!))) ہمیں ایک ٹیسٹ ملے گا جس میں ہم جانچیں گے کہ اسپرنگ ڈیٹا ہمارے لیے جو طریقے لاگو کرتا ہے وہ ہماری توقع کے مطابق کام کرتا ہے۔ یہاں یہ نوٹ کرنا ضروری ہے کہ ہم ٹیسٹ پروفائل کے استعمال کی وضاحت کے لیے @ActiveProfiles("test") تشریح استعمال کرتے ہیں۔ اور یہ بالکل وہی ہے جس کی ہمیں ضرورت ہے تاکہ ہم اپنے ڈیٹا بیس کے لیے صحیح خصوصیات کو شمار کر سکیں۔ یہ اچھا ہو گا کہ ہمارے ٹیسٹ چلانے سے پہلے ڈیٹا بیس تیار کر لیا جائے۔ اس معاملے کے لئے ایک ایسا نقطہ نظر ہے: ٹیسٹ میں ایک ایس کیو ایل تشریح شامل کریں اور اسے اسکرپٹ کے ناموں کا مجموعہ پاس کریں جسے ٹیسٹ شروع کرنے سے پہلے چلانے کی ضرورت ہے:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
ہمارے لیے، وہ ./src/test/resources/ + تشریح میں بیان کردہ راستے کے ساتھ ساتھ واقع ہوں گے۔ یہاں وہ کس طرح نظر آتے ہیں:
clearDbs.sql:
DELETE FROM tg_user;

telegram_users.sql:
INSERT INTO tg_user VALUES ("123456789", 1);
INSERT INTO tg_user VALUES ("123456788", 1);
INSERT INTO tg_user VALUES ("123456787", 1);
INSERT INTO tg_user VALUES ("123456786", 1);
INSERT INTO tg_user VALUES ("123456785", 1);
INSERT INTO tg_user VALUES ("123456784", 0);
INSERT INTO tg_user VALUES ("123456782", 0);
INSERT INTO tg_user VALUES ("123456781", 0);
اس کے نتیجے میں ہمارا TelegramUserRepositoryIT ٹیسٹ ایسا ہی نظر آئے گا (جیسا کہ آپ دیکھ سکتے ہیں، انٹیگریشن ٹیسٹنگ کا نام مختلف ہوگا - ہم IT شامل کرتے ہیں، ٹیسٹ نہیں):
package com.github.javarushcommunity.jrtb.repository;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;

import java.util.List;
import java.util.Optional;

import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;

/**
* Integration-level testing for {@link TelegramUserRepository}.
*/
@ActiveProfiles("test")
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
public class TelegramUserRepositoryIT {

   @Autowired
   private TelegramUserRepository telegramUserRepository;

   @Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
   @Test
   public void shouldProperlyFindAllActiveUsers() {
       //when
       List<TelegramUser> users = telegramUserRepository.findAllByActiveTrue();

       //then
       Assertions.assertEquals(5, users.size());
   }

   @Sql(scripts = {"/sql/clearDbs.sql"})
   @Test
   public void shouldProperlySaveTelegramUser() {
       //given
       TelegramUser telegramUser = new TelegramUser();
       telegramUser.setChatId("1234567890");
       telegramUser.setActive(false);
       telegramUserRepository.save(telegramUser);

       //when
       Optional<TelegramUser> saved = telegramUserRepository.findById(telegramUser.getChatId());

       //then
       Assertions.assertTrue(saved.isPresent());
       Assertions.assertEquals(telegramUser, saved.get());
   }
}
ہم نے ٹیسٹ لکھے، لیکن سوال یہ پیدا ہوتا ہے: GitHub پر ہمارے CI عمل کے آغاز کے ساتھ کیا ہوگا؟ اس کا ڈیٹا بیس نہیں ہوگا۔ ابھی کے لئے واقعی صرف ایک سرخ تعمیر ہوگی۔ ایسا کرنے کے لیے، ہمارے پاس GitHub ایکشنز ہیں، جس میں ہم اپنی تعمیر کے آغاز کو ترتیب دے سکتے ہیں۔ ٹیسٹ چلانے سے پہلے، آپ کو ضروری ترتیبات کے ساتھ ڈیٹا بیس لانچ شامل کرنے کی ضرورت ہے۔ جیسا کہ یہ پتہ چلتا ہے، انٹرنیٹ پر بہت سی مثالیں نہیں ہیں، لہذا میں آپ کو مشورہ دیتا ہوں کہ اسے کہیں محفوظ کر لیں۔ آئیے .github/workflows/maven.yml فائل کو اپ ڈیٹ کریں:
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven

name: Java CI with Maven

on:
 push:
   branches: [ main ]
 pull_request:
   branches: [ main ]

jobs:
 build:
   runs-on: ubuntu-latest
   steps:
   - uses: actions/checkout@v2
   - name: Set up MySQL
     uses: mirromutth/mysql-action@v1.1
     with:
       mysql version: '5.7'
       mysql database: 'dev_jrtb_db'
       mysql root password: 'root'
       mysql user: 'dev_jrtb_db_user'
       mysql password: 'dev_jrtb_db_password'
   - name: Set up JDK 1.11
     uses: actions/setup-java@v1
     with:
       java-version: 1.11
   - name: Build with Maven
     run: mvn -B package --file pom.xml
اب ایک نیا سیٹ اپ MySQL بلاک ہے ۔ اس میں ہم MySQL کو اپنے CI عمل میں شامل کرتے ہیں، ساتھ ہی ساتھ ان متغیرات کی وضاحت کرتے ہیں جن کی ہمیں ضرورت ہے۔ اب ہم نے وہ سب کچھ شامل کر دیا ہے جو ہم چاہتے تھے۔ آخری مرحلہ تبدیلیوں کو آگے بڑھانا اور دیکھنا ہے کہ تعمیر گزر جائے گی اور سبز ہو جائے گی۔

دستاویزات کو اپ ڈیٹ کرنا

آئیے پروجیکٹ ورژن کو pom.xml میں 0.3.0-SNAPSHOT سے 0.4.0-SNAPSHOT میں اپ ڈیٹ کریں اور RELEASE_NOTES میں بھی شامل کریں:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
اس سب کے بعد، ہم ایک کمٹ، پش اور پل کی درخواست بناتے ہیں۔ اور سب سے اہم بات، ہماری تعمیر سبز ہے!"A سے Z تک جاوا پروجیکٹ": ڈیٹا بیس سے متعلق ہر چیز کو شامل کرنا۔  حصہ 2 - 3

مفید لنکس:

تمام تبدیلیاں یہاں تخلیق کردہ پل کی درخواست میں دیکھی جا سکتی ہیں ۔ پڑھنے کے لیے سب کا شکریہ۔

سیریز کے تمام مواد کی فہرست اس مضمون کے شروع میں ہے۔

تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION