Seperation af forretningslag og databaselag

Hvis du har prøvet af lave et website i ASP som bare er lidt komplekst, har du sikkert også kæmpet lidt med at holde tingene overskueligt. Det ender ofte med at databasekald og forretningsregler bliver flettet ind i HTML-kode og danner en dejlig suppe af spagettikode, som til tider kan være lidt frustrerende at vedligeholde. Desuden ender man i værste fald med at kode de samme ting flere gange, fordi det er for besværligt at udrede det man skal bruge fra den eksisterende kode og gøre det genbrugeligt.

En vej fremad derfra, kunne være at opsplitte sin kode i funktioner og så fordele disse i en flok includefiler og forsøge at holde styr på hvilke includefiler der skulle bruges hvornår...

Jeg vil dog prøve at beskrive en anden vej, nemlig ved at benytte klasser (se evt. andre artikler her på sitet, som omhandler brugen af klasser i VBScript). Med klasser kan vi indkapsle logikken og opdele den i nogle fornuftigt sammenhængende klumper, f.eks. forretningslogik og databaseintegrationslogik.

Forretningslogik er det der håndterer reglerne og sammenhængene mellem forskellige elementer i systemet og databaseintegrationslogikken håndterer transporten af forretningsdata/-tilstand mellem forretningslaget og databasen.

Databaseintegrationslaget håndterer som nævnt logikken vedr. transport af data og indkapsler optimalt set alt viden om hvordan data skal lagres og transporteres mellem databasen og forretningslaget. Hvis man kan opnå total isolation af denne viden i databaseintegrationslaget, kan man "let" udskifte den databaseplatform man anvender til fordel for en anden platform. Dette kunne f.eks. opstå i forbindelse med en opskalering af et site, som er "vokset fra" den eksisterende databaseløsning og har behov for flere kræfter i det lag. Det kunne også være en ændring af måden data behandles og transporteres mellem database og forretningslaget der skulle ændres, f.eks. bedre caching, eller en spredning af database over flere databaseservere som skulle håndteres. Alt dette kan ske isoleret i databaselaget, hvis man kan holde en stringent grænseflade mellem forretningslaget (og alt over det lag) og databaselaget.

Min strategi går ud på, at benytte formatet for forretningsobjekter fra artiklen "forreningslag og validering" og så bygge et databaseintegrationslag på (som det også blev indikeret i nævnte artikel), der varetager datatransport. En databaseintegrationsklasse følger typisk en bestemt forretningsklasse, så derfor kan man godt inkludere filen med databaseintegrationsklassen i filen med forretningsklassen. På den måde skal man som bruger af forretningsklassen kun bekymre sig om at inkludere filen med forretningsklassen og endnu en "bekymring" er fjernet fra abonnentens mængde af "bekymringer" i forbindelse med konstruktionen af et komplekst system.

Det essentielle af forretningsklassen i forbindelse med kommunikationen med databaseintegrationslaget:

class CPerson
  private m_id
  private m_brugernavn
  private m_kodeord
  private m_email

  public property get Id: Id = m_Id: end property
  public property let Id(value): m_id = value: end property

  public property get Brugernavn: Brugernavn = m_brugernavn: end property
  public property let Brugernavn(value): m_brugernavn = value: end property

  public property get Kodeord: Kodeord = m_kodeord: end property
  public property let Kodeord(value): m_kodeord = value: end property

  public property get Email: Email = m_email: end property
  public property let Email(value): m_email = value: end property

  public sub Gem()
    dim pdo: set pdo = new CPersonDO
    call pdo.Gem(me)
  end sub
end class 

Som det ses oprettes der et nyt objekt som tager sig af den faktiske gem-operation. Det tager selve forretningsobjektet som parameter og bruger forretningsobjektet til at aflæse og sætte værdier i forbindelse med transporten af data. Det kunne f.eks. se således ud:

class CPersonDO
  public sub Gem(fo)
    dim sql, conn, cmd, rs

    if fo.Id = 0 then
      sql = "INSERT INTO person(brugernavn,kodeord,email) VALUES(?,?,?)"
    else
      sql = "UPDATE person SET brugernavn = ?, kodeord = ?, email = ? WHERE id = ?"
    end if

    set conn = getConn()

    set cmd = Server.CreateObject("ADODB.Command")
    set cmd.ActiveConnection = conn
    cmd.CommandText = sql
    cmd.CommandType = adCmdText
    cmd.Parameters.Append _
        cmd.CreateParameter("@brugernavn", adVarChar, _
            adParamInput, 20, fo.Brugernavn)
    cmd.Parameters.Append _
        cmd.CreateParameter("@kodeord", adVarChar, _
            adParamInput, 20, fo.Kodeord)
    cmd.Parameters.Append _
        cmd.CreateParameter("@email", adVarChar, _
            adParamInput, 255, fo.Email)
    if fo.Id <> 0 then
      cmd.Parameters.Append _
          cmd.CreateParameter("@id", adInteger, _
              adParamInput, , fo.Id)
    end if
    cmd.Execute

    if fo.Id = 0 then
      ' Da det er et nyt objekt, så skal det nye Id hentes fra
      ' databasen og indsættes i forretningsobjektet, så det kan
      ' bruges videre og gemmes (dvs. opdateres) igen senere
      set cmd = Server.CreateObject("ADODB.Command")
      set cmd.ActiveConnection = conn
      cmd.CommandText = "SELECT @@IDENTITY"
      cmd.CommandType = adCmdText
      set rs = cmd.Execute()
      fo.Id = CLng(rs(0))
      rs.Close
      set rs = nothing
    end if  

    call conn.close()
  end sub
end class 

Der er i ovenstående et par forudsætninger som skal være på plads. Dels skal ado-konstanterne være tilgængelige (enten via metatag i f.eks. global.asa eller via en includefil med konstanterne - den første anbefales!), dels skal der være en global funktion som kan hente et gyldigt connectionobjekt via funktionen getConn.

Selve funktionen CPersonDO.Gem sørger for at tage stilling til om forretningsobjektet skal oprettes eller opdateres, hvilket besluttes på grundlag af forretningsobjektets aktuelle id og en antagelse af om id'et er 0 eller noget andet. Hvis det er nul, så findes det ikke i databasen (da databaseid'er typisk har en værdi større end 0, hvis de er autogenererede). Der kan naturligvis sagtens ligge andre regler til grund for identicerende kolonner i en database (og der bør i virkeligheden nok gøre det), men jeg har taget udgangspunkt i denne simple forudsætning, da det er den metode jeg typisk benytter i mine databasearkitekturer...

 
Sidst opdateret: 05-09-2010 00:08:02
Tilmeld link | Tilføj Link | Tilføj Link | @-begynder
Erklæring om beskyttelse af personlige oplysninger

nope.dk - Danmarks Website Chart