單體式客服系統通常整合預約、留言、訊息等功能於單一應用程式中。本文將以 Java 和 Spring 為例,說明如何設計和建置此類別系統,並探討其在實際應用中的優缺點。首先,我們會介紹預約服務的 getAvailableDatessaveAppointment API,以及留言板服務的 getMessage API,並提供程式碼範例說明其實作方式。接著,會說明如何組態資料函式庫連線、使用 Ant 建置專案,以及佈署至 AWS EC2 的流程。這些步驟涵蓋了單體式應用程式從開發到佈署的完整生命週期。

單體式客服系統應用案例研究

預約服務

預約服務允許使用者根據可用時間和日期預約客服專家。此服務提供以下功能:

  • getAvailableDates:根據給定的請求檢索可用的日期和時間。
  • saveAppointment:為選定的可用時間和日期儲存預約。

getAvailableDates 服務

此服務檢索特定時間段內可用的日期和時間。

  • Context:AppointmentService/getAvailableDates
  • Method:POST
  • Consumes:application/xml、application/json
  • Produces:application/json
  • Input:HttpHeaders、AppointmentAvailableDateRequest
  • Output:AppointmentAvailableDateResponse
@Override
@POST
@Consumes({"application/xml", "application/json"})
@Produces({"application/json"})
@Path("/getAvailableDates/")
public AppointmentAvailableDateResponse getAvailableDates(
    @Context HttpHeaders headers,
    AppointmentAvailableDateRequest request) {
    // 實作 DAO 邏輯
}

內容解密:

  1. @POST 表示此服務使用 HTTP POST 方法。
  2. @Consumes 指定服務可接受的輸入格式,包括 XML 和 JSON。
  3. @Produces 指定服務輸出的格式為 JSON。
  4. @Path 定義服務的 URL 路徑。
  5. getAvailableDates 方法根據請求檢索可用的日期和時間,並傳回相應的回應。

saveAppointment 服務

此服務儲存特定時間和日期的預約。

  • Context:AppointmentService/saveAppointment
  • Method:POST
  • Consumes:application/xml、application/json
  • Produces:application/json
  • Input:HttpHeaders、TITLE、request
  • Output:回應狀態(成功或失敗)
@Override
@POST
@Consumes({"application/xml", "application/json"})
@Produces({"application/json"})
@Path("/saveAppointment/")
public Response saveAppointment(
    @Context HttpHeaders headers,
    // 請求物件
) {
    // 實作儲存預約邏輯
}

內容解密:

  1. 此服務使用 HTTP POST 方法儲存預約。
  2. 服務接受 XML 和 JSON 格式的輸入,並輸出 JSON 格式的回應。
  3. saveAppointment 方法儲存預約並傳回相應的回應狀態。

留言板服務

留言板服務提供使用者與客服專家之間的協作功能。該服務包含以下功能:

  • getMessage:根據給定的標題檢索留言。
  • getAllMessage:檢索特定時間段內的所有留言。
  • createMessage:儲存使用者提供的留言、問題或答案。

getMessage 服務

此服務根據給定的標題檢索留言、問題和答案。

  • Context:MessageService/getMessage/{title}
  • Method:GET
  • Consumes:application/xml、application/json
  • Produces:application/json
  • Input:HttpHeaders、TITLE
  • Output:回應狀態(成功或失敗)
@Override
@GET
@Consumes({"application/xml", "application/json"})
@Produces({"application/json"})
@Path("/getMessage/{title}")
public MessageViewResponse getMessage(
    @Context HttpHeaders headers,
    @PathParam("title") String title) throws ServiceInvocationException {
    // 實作 DAO 邏輯
}

內容解密:

  1. @GET 表示此服務使用 HTTP GET 方法。
  2. @PathParam 用於從 URL 路徑中提取標題引數。
  3. getMessage 方法根據標題檢索留言並傳回相應的回應。

建置應用程式

現在,我們已經介紹了應用程式的架構、Web 服務和各種依賴項,接下來將下載程式碼、建置並執行它。

設定 Eclipse

我們使用 Eclipse IDE 進行開發,您可以選擇自己最舒適的 IDE。以下是設定 Eclipse 的步驟:

  1. https://eclipse.org/downloads/index-developer.php 下載 Eclipse。
  2. 解壓縮下載的檔案,Eclipse 的先決條件是系統路徑中的 JRE。
  3. 雙擊 eclipse.exe。
  4. 在套件總管中右鍵點選並選擇「新建 Java 專案」,命名為 Helpdesk。
  5. 取消選取「使用預設位置」,瀏覽到您克隆程式碼的目錄,然後點選「開啟」。

建置應用程式

以下是建置應用程式並產生可佈署 WAR 檔案的步驟:

  1. 在建置 WAR 檔案之前,需要在 applicationContext.xml 檔案中組態資料函式庫。
  2. 在 Project Location/src/main/webapp/WEB-INF/applicationContext.xml 中找到 DataSource bean,並修改 url、username 和 password 屬性。
<!-- 資料函式庫組態範例 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="url" value="jdbc:mysql://localhost:3306/helpdesk"/>
    <property name="username" value="your_username"/>
    <property name="password" value="your_password"/>
</bean>

內容解密:

  1. 資料函式庫組態是建置應用程式的關鍵步驟。
  2. 需要根據實際資料函式庫環境修改 url、username 和 password 屬性。

單體式Helpdesk應用程式案例研究:建置與佈署

本章節將探討單體式Helpdesk應用程式的建置與佈署流程,涵蓋從環境組態到最終佈署的全過程。

資料來源組態

首先,我們需要組態資料來源以連線MySQL資料函式庫。以下是一個典型的Spring組態範例:

<bean id="DataSource" destroy-method="close"
      class="org.apache.tomcat.jdbc.pool.DataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://<dbhost>:<dbport>/<dbname>" />
    <property name="username" value="<Username>" />
    <property name="password" value="<Password>" />
    <property name="initialSize" value="5" />
    <property name="maxActive" value="50" />
    <property name="validationQuery" value="select 1 from dual" />
    <property name="testWhileIdle" value="true" />
    <property name="testOnBorrow" value="true" />
    <property name="minIdle" value="0" />
    <property name="minEvictableIdleTimeMillis" value="30000" />
    <property name="timeBetweenEvictionRunsMillis" value="60000" />
    <property name="removeAbandoned" value="true"/>
    <property name="removeAbandonedTimeout" value="30000" />
    <property name="logAbandoned" value="true" />
    <property name="maxWait" value="120000" />
</bean>

內容解密:

此組態定義了一個名為DataSource的Bean,使用Apache Tomcat的JDBC連線池來管理與MySQL資料函式庫的連線。主要屬性包括:

  • driverClassNameurl:指定資料函式庫驅動程式和連線URL。
  • usernamepassword:資料函式庫登入憑證。
  • initialSizemaxActive:初始化和最大活動連線數。
  • validationQuery:驗證連線是否有效的SQL查詢。
  • testWhileIdletestOnBorrow:控制連線驗證時機。
  • minIdleminEvictableIdleTimeMillis:最小閒置連線數和連線可被收回的最短時間。
  • removeAbandonedlogAbandoned:控制放棄連線的處理。

建置指令碼(Build.xml)組態

接下來,我們需要建立一個Ant建置指令碼(Build.xml)來編譯和封裝應用程式。

<project name="projects" default="jar" basedir=".">
    <!-- 定義專案屬性 -->
    <property name="src" location="src"/>
    <property name="build" location="build"/>
    <property name="dist" location="dist"/>
    <!-- 編譯和封裝目標 -->
    <target name="compile.individual" depends="init">
        <javac includeantruntime="false" debug="true" compiler="javac1.6"
               srcdir="${src}" destdir="${build}">
            <classpath refid="project.classpath"/>
        </javac>
    </target>
    <target name="jar.individual" depends="compile.individual">
        <!-- 封裝JAR檔案 -->
        <jar jarfile="${jar.location}/org-${ant.project.name}.jar" basedir="${build}"/>
    </target>
    <!-- WAR檔案封裝目標 -->
    <target name="war" depends="init.war,copy.files">
        <war destfile="dist/lib/helpdesk.war" webxml="src/main/webapp/WEB-INF/web.xml">
            <fileset dir="src/main/webapp">
                <exclude name="**/.svn"/>
            </fileset>
            <lib dir="src/main/webapp/WEB-INF/lib" />
            <classes dir="${build}/classes" />
        </war>
    </target>
</project>

內容解密:

此Ant指令碼定義了編譯、封裝JAR和WAR檔案的目標。主要步驟包括:

  • 編譯原始碼(compile.individual目標)。
  • 封裝JAR檔案(jar.individual目標)。
  • 複製必要檔案到指定目錄(copy.files目標)。
  • 封裝WAR檔案(war目標),包含Web應用程式的組態和類別檔案。

佈署到AWS EC2

最後,我們將WAR檔案佈署到AWS EC2上的Tomcat伺服器。

  1. 安裝Tomcat 7和MySQL

    • sudo apt-get install tomcat7
    • sudo apt-get install mysql-server
  2. 啟動和管理服務

    • sudo service tomcat7 start/stop/status
    • sudo service mysql start/stop/status
  3. 建立資料函式庫和佈署WAR檔案

    • 在MySQL中建立名為helpdesk的資料函式庫。
    • 將WAR檔案透過Tomcat管理控制檯佈署到伺服器。

內容解密:

佈署過程包括安裝必要的軟體、組態和管理服務、建立資料函式庫以及最終佈署應用程式。確保Tomcat和MySQL服務正確執行,並透過Tomcat管理控制檯佈署WAR檔案。

第 11 章:單體式 Helpdesk 應用程式案例研究

新需求與錯誤修復

想像一下,應用程式已經上線並為客戶提供服務,這標誌著軟體維護生命週期的開始。隨著時間的推移,將會有新的請求來更新或更改應用程式的功能,同時客戶也可能會發現一些錯誤。所有這些請求都需要更改程式碼和/或重建應用程式。讓我們來理解維護單體式應用程式所面臨的挑戰和所涉及的工作。

假設我們需要為檢視票據服務新增一個額外的引數,該引數對其他元件的依賴性非常有限或沒有依賴。透過以下程式碼,我們更改了票據請求:

public TicketResponse createHdTicket(
    @Context HttpHeaders headers,
    TicketRequest ticketRequest)
    throws ServiceInvocationException{

以下程式碼允許我們在 Plain Old Java Object(POJO,即網頁模型)中新增一個新的屬性:

@Component
private String emailAddress;

@XmlElement
public String getEmailAddress() {
    return emailAddress;
}

public void setEmailAddress(String emailAddress) {
    this.emailAddress = emailAddress;
}

使用以下程式碼,我們可以在 DAO 層新增邏輯以從資料函式庫中取得該屬性:

private String saveToDatabase(TicketRequest ticketRequest){
    //與現有的程式碼一起新增
    ticket.setEmailAddress(ticketRequest.getEmailAddress());
}

內容解密:

  1. createHdTicket 方法:此方法負責建立一個新的幫助台票據。它接受 HttpHeadersTicketRequest 物件作為輸入,並傳回一個 TicketResponse 物件。
  2. emailAddress 屬性:在 TicketRequest 物件中增加了一個新的屬性 emailAddress,用於儲存使用者的電子郵件地址。
  3. saveToDatabase 方法:此方法負責將票據請求儲存到資料函式庫中。它從 TicketRequest 物件中取得 emailAddress 屬性,並將其儲存到資料函式庫中。

雖然程式碼變更看起來很簡單,但接下來需要做以下工作:

  1. 構建整個網頁應用程式:這意味著需要重新佈署整個應用程式。
  2. 進行迴歸測試:需要對整個應用程式進行迴歸測試,以確保其他功能仍然按預期工作。
  3. 解決錯誤和依賴問題
  4. 佈署到測試環境並進行品質保證流程。
  5. 佈署到生產環境

如果應用程式未以高用性(HA)模式佈署,則意味著將會發生停機時間,因為應用程式將被重新佈署。

所有這些步驟都增加了發布此次小幅變更所需的時間,並違背了敏捷開發的原則。這還不包括持續變更的情況,您可能需要建立新的程式碼分支並再次合併和測試。

面臨的其他挑戰

  • 修復錯誤:每個錯誤修復都需要佈署整個構建版本,這意味著如果沒有適當的 HA 佈署架構,則可能導致系統停機。此外,根據所使用的系統開發生命週期(SDLC)方法論,這可能意味著在引入錯誤修復之前需要很長時間。對於關鍵錯誤,通常意味著建立和維護一個「熱修復」分支,這可能會使程式碼函式庫變得複雜,並在稍後造成合併問題。
  • 更換應用程式元件:這是另一個需要對整個應用程式進行重構/重新實作的案例。假設組織希望使用雲端服務進行票據管理;目前應用程式的編寫方式很難將相關模組與應用程式解耦。
  • 更換或新增新的技術堆積疊:在這種情況下,除非整個應用程式被重新實作,否則您無法自由選擇新模組/功能所需的技術。由於單體式架構,組織被迫使用所選擇的技術。
  • 選擇性擴充套件:假設您希望擴充套件票據模組以適應使用模式。在這種情況下,由於應用程式元件緊密整合為單體式應用程式,因此擴充套件變得非常複雜。例如,僅分離票據模組就需要大量的程式碼重構、與獨立的票據系統整合、測試、新的佈署架構等。
  • 處理故障:在單體式應用程式中,一個元件的故障可能會破壞整個應用程式。假設產品目錄服務關閉,這將阻止使用者提交新的工作票據。新的工作票據應該指示使用者遇到問題的產品,以便更好地路由票據和更快地解決問題。但是,產品目錄服務中的錯誤不應該阻止使用者建立票據本身,也就是說,它不應該使票據服務本身宕機。但是,鑑於單體式架構,如果產品目錄是一個必填欄位並且存在錯誤,使用者將在此階段卡住,即使他或她可以描述問題。

雖然對於我們的應用程式來說這些挑戰很大,但它們是當今數位世界中的基本需求。使用單體式方法解決這些問題變得非常昂貴和耗時。在接下來的兩章中,我們將討論如何透過微服務和容器來解決這些挑戰。