Week 9 - reflectie

reflectie

Maandag Dinsdag - knowledge base

Afgelopen vrijdag heb ik van collega’s begrepen dat er een initiatief is om product eigenschappen beschikbaar te krijgen in onze SDK.

Daarover heb ik vandaag met Nuno een gesprekje gehad. Hij werkt aan een overzicht van de door hem benodigde product informatie en de bijbehorende JSON structuur. Deze informatie sluit mooi aan bij de informatie die ik in mijn hueProductDatabase wil opslaan. We hebben afgesproken dat ik zijn JSON structuur voorstel zal bekijken en daar feedback op zal geven. Ook zal ik er in mijn applicatie voor zorgen dat ik dezelfde informatie als JSON kan produceren.

Dinsdag - inheritance

Bij de implementatie van het datamodel maak ik gebruik van inheritance om het dupliceren van data structuren te voorkomen. Ook zorg ik er daarmee voor dat bijvoorbeeld alle Release sub-klassen de zelfde basis structuur hebben en daarmee consistent te gebruiken zijn.

Al eerder liep ik aan tegen het fenomeen dat het database schema veranderde afhankelijk van welke view ik op de data probeerde te genereren. De oorzaak hangt af van de gekozen inheritance strategy.

Je kunt de hele inheritance hierarchy plat slaan en opslaan in één tabel. Deze strategy heet Table per Hierarchy (TPH) en is de default in Entity Framework. Om alle eigenschappen van de sub-klassen te kunnen opslaan, moeten er veel nullable zijn, ofwel leeg kunnen blijven. Eigenschappen voor een software release zijn niet relevant voor een hardware release, maar staan wél in dezelfde tabel. Hierdoor is het vrijwel onmogelijk de database te laten helpen de informatie in de database consistent te laten houden. Dit zal in de applicatie moeten gebeuren.

Of, je kiest voor Table per Type (TPT) waarbij iedere domein klasse een eigen tabel krijgt en een verwijzing heeft naar de tabel van zijn parent klasse. Hierbij reflecteerd het database schema vrijwel compleet het domein klassen diagram. De database helpt optimaal de data consistent te houden door gebruik van foreign keys tussen de tabellen. Een overzicht van alle releases is eenvoudig te verkrijgen door een query te doen op de Releases tabel.

En tenslotte is er Table per Concrete Class (TPC). Hierbij krijgt iedere concrete domein klasse (dus niet de abstracte klassen) een eigen tabel. Eigenschappen van de abstracte parent worden dan dus opgeslagen in de tabel van de concrete klasse. In dit geval is het niet mogelijk een uniek Id te geven aan iedere klasse onafhankelijk van het type. Ook is het niet eenvoudig een listing te geven van alle release klassen onafhankelijk van het type.

Voor mijn applicatie is consistentie essentieel en is ook een overzicht van alle releases een noodzaak. Ik heb daarom gekozen voor de Table per Type (TPT) strategie.

Dinsdag - foutmeldingen

Bij het aanpassen van de klassen aan het nieuwe datamodel breekt regelmatig de werking van de diverse API controllers. Daar zie je alleen niets van in de interface, omdat alles op de achtergond in Javascript wordt afgehandeld.

Als er iets niet werkt, kijk ik dus in de developer view van de browser (F12) om te zien of er iets is misgegaan op de server. Dat is wat omslachtig en daarom heb ik even wat tijd genomen om deze foutmeldingen te parsen en in de GUI te tonen:

Voorbeeld van een server foutmelding

Woensdag - migrations

Bij het aanpassen en toevoegen van de klassen veranderd ook het database schema. Daarvoor biedt Entity Framework code-first migrations. Tot nu toe gebruikte ik automatische migratie. Daarbij worden wijzigingen gewoon doorgevoerd in de database, maar meestal moest ik daarvoor mijn database eerst droppen om conflicten te voorkomen.

Dus heb ik er wat tijd aan besteed om het goed te doen. Ik heb de automatische migratie uitgezet en laat alle migraties eerst in code vast leggen. Voordeel is dat je daarmee kunt zien wat de wijzigingen zijn en, dat de migratie incrementeel is uit te voeren. Ik heb nu ook op de test server een aanpassing gemaakt dat ik deze migratie via de command line kan uitvoeren.

Donderdag - bug in MySQL .NET connector

Tijdens het werken met de migratie scripts ben ik er achter gekomen dat de namen van mijn foreign key constraints nogal lang kunnen worden. Dit komt doordat ik beschrijvende klasse namen gebruik volgens een conventie:

Voorbeelden: SystemRelease, AppProductRelease, SdkProductRelease, BridgePlatformSoftwareRelease etc.

Consequentie is dat de namen nogal lang worden en daarmee de foreign keys ook:

FK_ProductReleases_SoftwareReleases_ZigBeeBridgeSoftwareReleaseI

Het probleem is echter dat MySQL een maximum lengte van 64 karakters ondersteund. Nu vervangt de MySQL SQL generator (MySqlMigrationSqlGenerator) dit soort foreign keys door een random hash en dat werkt prima. Totdat je migraties probeert uit te voeren…

Doordat de hash random is, is ie bij elke migratie script verschillend, terwijl het dezelfde foreign key probeert te modificeren… Dat breekt dus.

Omdat ik de migratie functionaliteit nodig heb om, met behoud van data, wijzigingen in de database door te kunnen voeren, heb ik besloten om de MySQL SQL generator klasse te sub-klassen en enkele methoden te overriden:

public class HueMySqlMigrationSqlGenerator: MySql.Data.Entity.MySqlMigrationSqlGenerator {
  // Maximum constraint name length is limited. See http://dev.mysql.com/doc/refman/5.6/en/identifiers.html
  // Default behavior for MySqlMigrationSqlGenerator is to create some kind of random hash and use
  // that instead. But this hash appears to be generated at random and is different for adding and dropping
  // foreign keys failing these actions.
  const int maxLength = 64;

  override protected MigrationStatement Generate(AddForeignKeyOperation op) {
    if (op.Name.Length > maxLength) {
      // As the constraint name is not used in code nor in database logic, 
      // its length can simply be limited to maxLength.
      op.Name = op.Name.Substring(0, maxLength);
    }
    var migrationStatement = base.Generate(op);

    // For some reason the Generate method include dbo.<tablename> in the sql, but 
    // no such tables exist. This hack removes dbo. and solves this problem.
    migrationStatement.Sql = migrationStatement.Sql.Replace("dbo.", "");
    return migrationStatement;
  }

  override protected MigrationStatement Generate(DropForeignKeyOperation op) {
    if (op.Name.Length > maxLength) {
      // As the constraint name is not used in code nor in database logic, 
      // its length can simply be limited to maxLength.
      op.Name = op.Name.Substring(0, maxLength);
    }
    var migrationStatement = base.Generate(op);

    // For some reason the Generate method include dbo.<tablename> in the sql, but 
    // no such tables exist. This hack removes dbo. and solves this problem.
    migrationStatement.Sql = migrationStatement.Sql.Replace("dbo.", "");
    return migrationStatement;
  }

}

Update, zaterdag 11 april:

Ik ben nog een probleem tegengekomen met de MySQL .NET Connector: System.NotImplementedException: RenameIndexOperation

Naar aanleiding hiervan ben ik maar ’s op de MySQL site gaan kijken en heb de volgende bug meldingen gevonden voor de al eerder genoemde problemen:

Voor het nieuwe probleem is blijkbaar nog geen bug gesubmit.

Dit nieuwe probleem blokkeert mijn migraties na wijzigingen aan mijn datamodel en dat is een probleem zodra de database informatie bevat die behouden moet worden. Ik heb daarom besloten om de code te downloaden en deze zelf aan te passen.

Update 2, zondag 12 april:

Ik heb de code gedownload en in Visual Studio 2013 geladen. Helaas heb ik niet voor elkaar gekregen de code te bouwen. Er zijn diverse externe dependencies en nadat ik die had opgelost, vlogen er nog diverse foutmeldingen om mijn oren.

Als alternatief heb ik de code uit een Github repository gedownload en deze geprobeerd, maar dat was een oudere versie en ook daarbij liep ik tegen vergelijkbare problemen aan.

Voor beide mogelijkheden heb ik geen documentatie gevonden over hoe de software te bouwen. Dit is voor mij dus geen oplossing.

Een ander idee is om RenameIndex te vervangen door twee opdrachten, DropIndex en CreateIndex. Dat probeer ik nu.

Update 3, zondag, 2 uur later:

DropIndex resulteert in MySql.Data.MySqlClient.MySqlException (0x80004005): Fatal error encountered during command execution. —> MySql.Data.MySqlClient.MySqlException (0x80004005): Parameter ‘@columnType’ must be defined.

Ik geef het op en ga verder met mijn applicatie. Ik doe geen migraties meer, maar genereer de database iedere keer from-scratch. De SQL hiervoor kan ik opslaan in een file en vergelijken met de vorige keer dat ik de database heb gegenereerd. Op basis hiervan kan ik dan met de hand SQL schrijven om van de ene naar de andere versie te komen…. Het is niet ideaal, maar het is niet anders.

Donderdag - welke keuzes documenteren?

Zoals vrijwel elke week, lopen John en ik een rondje en bespreken de afgelopen week. Omdat we beide afstuderen, lopen we regelmatig tegen dezelfde punten aan en herkennen veel vanuit school van elkaar.

Deze keer vertelde John hoe hij de afgelopen week een aantal keuzes heeft moeten maken en dat hij deze heeft gedocumenteerd. Dat heeft me aan het denken gezet. Ik maak dagelijks diverse keuzes bij het realiseren van mijn hueProductDatabase, maar vergeet deze meestal ergens vast te leggen. Enkele van de grotere keuzes behandel ik soms in dit blog en de echte grote design keuzes leg ik wel vast in mijn scriptie. Maar, welke keuzes zou ik allemaal vast moeten leggen? Tot op welk detail niveau?

De 14e hebben we een terugkommiddag van school. Dan maar ’s over hebben.

Vrijdag - keuzes documenteren

Ik heb vandaag de reflectie op de afgelopen week geschreven en er meteen voor gekozen een aantal keuzes vast te leggen. Kostte me een paar uur, maar het resultaat kan ik mooi gebruiken in mijn scriptie.

vrijdag 10 april 2015