Apprentice Dev Blog: Week 1 - Getting Started

  • Friday, May 20, 2022
Two people working on a shared problem at a computer. They are both working at a laptop which is plugged into two external monitors, and are discussing the problem. One person is pointing to something on the laptop's screen, and the other person is following along.

The cover image for this post is by MArs-Sector-6

This blog post was written by Alex. Alex is our apprentice for 2022, and will be providing frequent developer logs.


Jamie here - to jump directly to Alex’s log for this week, click here

This past week saw RJJ take on our first apprentice. Our first apprentice, who we’ll name Alex

NOT their real name

is a student studying a Digital T-Level course. These new courses are designed to be the equivalent of an A-level, and have two years of study attached to them with a two month block of time dedicated to gaining relevant industry experience by working alongside developers in industry.

We are incredibly proud to be helping with Alex’s technical education, and I was assigned to be their PM on the project that they are working on and someone to reach out to for help in completing it. The Digital T-Level course that Alex has taken is very comprehensive and covers a number of different development technologies, but there are certain gaps in their knowledge. This is not meant as a negative point towards either the course designers or those who are delivering it, there is simply not enough time to teach all of the things required of a developer in just two short years.

in fact, development is one of the career choices which require a person to be constantly learning new things

As such, during their first week with us, I took Alex through a crash course in a few things. And I have to say that they were able to internalise and learn everything that I’ve asked of them so far.

Alex has been taught both Python 3 and C# & .NET Framework, so we had a stable base to build something up from. Most of our projects are written using the latest versions of the technology stacks available, so a compromise had to be made:

Let’s start with Framework, then move to modern .NET. And if there’s time, we’ll investigate .NET Maui when it’s released next week.

- Jamie

Starting with something you already know is always a good idea when learning something new. That way, you only have to deal with learning the new parts.

Anyway, you didn’t come here to read what I have to say. So here is a development log which represents Alex’s first week, in their own words:

one note, I’ve edited for presentation not content


Alex’s Devlog

Hi, I’m Alex and am currently studying a Digital T-Level course, am taking my industry experience with RJJ Software, and these are some of the things that I learned this week.

The first day of my apprenticeship was spent getting my laptop set up and ready to go. Jamie had arranged for me to have a Dell XPS 15, and had set most of it up for me including all of the software that I might need. I just needed to make sure that I could access the RJJ Software internal slack group. I was also taken through the health and safety policies, and shown how to get in touch with anyone I need. This is important because I’ll be working remotely for the majority of my apprenticeship.

Jamie talked me through the project, and which technologies we’ll be using. I’m going to be building a WinForms application using .NET 6. The application will work as an invoice generator. The project brief says that we’ll be using WinForms because I’ve been using it as part of my course and because there’s lots of documentation around. It also says that we might be able to migrate to .NET Maui when it’s released. Jamie has told me that it will be released this coming week at Microsoft’s Build conference.

editor’s note: if you’re reading this in the future, "this coming week" means May 25th, 2022

After the health and safety policies and the project brief where covered, we started getting to work. Jamie talked me through the basics of git, and showed me where to find the source code repository for the application. From there, I forked the repository (to create my own copy of it) then cloned the fork down to my machine. I would be using that local clone to make my changes, committing them to my clone of the forked repository, then I would push those changes back up to my fork, and arrange for pull requests back into either the main branch or Jamie’s upstream repository.

editor’s note: we’ve highlighted some of the keywords in that paragraph which are related to git’s process

Here are my notes on that:

The basic idea is that when we create a git repo, there is a main branch. This represents the current state of play for the code within the code base. When we want to make a change to the code base, we create a branch - this is a copy of the code up to that point - and work on it until we have completed the work. Changes made to the code base are committed into the branch, this ensures that related changes are saved into source control at the same time. Each commit requires a commmit message, and this should be a helpful description of what the changes are and how they effect the code base.

When the work is completed and has passed any QA or test requirements, the branch is then merged back into the main branch. Merging is the process of taking the changes in one branch and applying them onto another branch. Git will take care of the majority of the changes, figuring out where to insert your new code and which code changes take precedent.

Sometimes you will get a merge conflict. This is when git can’t automatically merge the changes from one branch into another. At this point, you need to intervene and tell git which changes need to be copied over and which changes need to be dropped.

- My notes

With source control taken care of, I set about creating a WinForms application which ran on .NET Framework. WinForms is an older (but very stable) technology for making UI applications for Windows, and .NET Framework is a slightly older version of .NET. Once I had created the UI with a very basic workflow and almost no logic (“click this button, show this empty window”, etc.), I pushed my changes to the main branch. Jamie then talked me through gitflow, which is something that RJJ use for their projects. The idea is that there is a main branch which represents the live product, and a number of branches off of that. In this project, we’re focussing on just one branch off of main called develop, and all of my work will be done there before being merged back to main

With the UI in place, I created a branch from main using git checkout -b develop and started creating the application. The first thing to do was to migrate the application up to .NET 6. For this, we used the .NET Upgrade Assistant which is a command line tool to upgrade from .NET Framework to .NET 6. The upgrade process was very easy, and I pushed all changes to my develop branch, created a pull request, and assigned Jamie to look at it. A few moments later my changes where in main, and I was ready for the next task.

editor’s note: .NET 6 is cross platform, but a WinForms application which is built using it will ONLY run on Windows

We needed a database to store things in, so Jamie created one and gave me the connection strings. He said that we weren’t going to use the SqlConnection class as this would mean we had to write SQL strings directly in the code. This could lead to SQL Injection attacks. A SQL Injection attack is when a user sends a SQL string into your application along with their input, and it is run against the database. This is very serious, as it could lead to a user deleting your database or exporting data that they’re not supposed to get.

This XKCD comic shows a silly example of SQL Injection.

Because we weren’t going to use the SqlConnection class, Jamie introduced me to Entity Framework Core. This is an object-database mapper, and it allows you to interact with your database via a Database Context. The Database Context needs to know which database you are using (SQL, mySql, Postgres) and how to connect to it. Once that is in place, you tell it about the different tables that you want to interact with by creating DbSet<> objects. The angled brackets mean “of type” and you have to put your class names between them. Our database has a Clients table which maps to a Client class, so I have to create a DbSet<Client> and Entity Framework Core maps that to the Clients table because it can see a table called Clients and that the columns in that table map to the properties in the Client class.

Jamie was teaching me about SOLID, which stands for:

  • Single-responsibility Principle
  • Open-closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

My UI should have only one responsibility: show the user interface and allow the user to interact with it.

editor’s note: this is called a "Presentation layer"

So all of the code that I had written to either create a Client or get a list of them to display had to be moved to a Business Logic Layer. This is a .NET 6 application which the UI application has a reference to, and means that the UI doesn’t need to know about the database.

editor’s note: the BLL is a .NET 6 application because, at this point, it has references to Entity Framework and (at the time of writing) EF has a hard requirement on being run inside an application

All the UI needs to know about is that the Business Logic Layer exists, and that it has a pair of methods:

  • CreateClient which returns a ViewModel
  • GetAllClients which returns a list of ViewModels

All of the Entity Framework code was moved to the Business Logic Layer, and the UI no longer needed to know about it. This makes the UI simpler to use and build on.

We haven’t covered the other parts of SOLID yet, but I’m sure that we will. Jamie has said that we’ll be looking into Dependency Injection next, and that I’ll be able on inject my Business Logic Layer into the UI, and the Database Context into the Business Logic Layer. He said something about how “new is glue”, because I’m doing this when accessing the Database Context in the Business Logic Layer:

using (var context = new DatabaseContext())
{
  var clients = context.Clients.ToList();

  var clientViewModels = clients.Select(c => ClientViewModel.ToViewModel(c));

  return clientViewModels;
}

This code gets all of the client objects from the Clients table and does a projection which converts them all to ClientViewModels. The Select here acts is an extension method provided by LINQ, acts like a foreach and works through all of the items in the clients list.

Jamie also mentioned using interfaces - this is not related to a user interface. He explained it like this:

Rather than relying on using the new keyword to create new concrete class instances, we ask a Dependency Injection framework (.NET has one built in, and we’ll use that) to inject one for us. The bonus for using this is that we can create an interface (which is different to a user interface) and pass that around.

The beauty of using interfaces is that they don’t tell the consumer (the UI is a consumer of the ClientService, and the ClientService is a consumer of the DatabaseContext) how the thing it’s calling works, just how to call it.

Look at the power socket that your laptop is plugged into. You don’t need to know how the power comes out of that socket, just that by plugging in a power adaptor you’ll get power. That’s dependency injection. Technically, it’s inversion of dependency (you are no longer depending on a positive wire, a negative wire and an earth wire; but are dependent on a three pin plug) and Dependency Injection is an implementation of inversion of dependencies.

It’s a little complex, and might be way too complex for what you’re doing right now. But the idea is that if you can inject your database context rather than create a concrete one with new, you can swap it for something else, and your app never needs to know. You could swap your database context for an in memory list, or you could swap it our for a file.

- Jamie

Though I understood what he meant, Jamie also explained in using video games as an example:

If you’re building a cross platform game, you don’t want to have code which reads an Xbox Series X controller and separate code which reads a PS5 controller - mainly because most of the buttons are the same. So you could come up with an IController interface which exposes each of the buttons, then two classes which implement that interface Ps5Controller and XboxSeriesXController.

- Jamie
public interface IController
{
   Jump();
   Melee();
   Reload();
   SwitchWeapon();
}

public class Ps5Controller : IController
{
    public void Jump()
    {
        // when the user presses X
    }
}

public class XboxSeriesXController : IController
{
    public void Jump()
    {
        // when the user presses A
    }
}

public class Game
{
   // Here we'expecting an IController, and it needs to be readonly so that
   // we don't accidentally change it once it's been instantiated
   private readonly IController _controller;
   
   // In the constructor, we want .NET to inject a class which implements
   // the IController interface. We'll tell .NET which class to pass here in
   // what's called the Composition Root - don't worry about what that is right
   // now, as we'll cover that when we get to dependency injection.
   public Game(IController controller)
   {
       _controller = controller;
   }
   
   public void ReadController
   {
       // because our class was injected for us, we no longer need to know
       // which class it is (XboxSeriesXController or PS5Controller) in order
       // to use it. We just need to call the right methods on it, and all
       // classes which implement the IController interface MUST have the
       // methods that it lists
       if (_controller.Jump())
       {
           // do jump
       }
   }
}

Then you only have to change which controller class you’re using in one place: what’s called the Composition Root. This is the place where you set up Dependency Injection - where we tell .NET which class to inject in

- Jamie

And it would be similar for a PC version of the game, too. You could have a PcController class which looks like:

public class XboxSeriesXController : IController
{
    public void Jump()
    {
        // when the user presses space
    }
}

It looks like interfaces can be extremely powerful and I’m looking forward to using interfaces and dependency injection next week. Hopefully I’ll be able to port the application to .NET Maui, too. That way, I’ll be able to turn this application into mobile app without having to do build one separately.


Jamie again.

In the next week, we’re going to be looking:

  • Setting up interfaces for the Database Context and Business Logic Layer
  • Using .NET’s built-in dependency injection to inject our dependencies
  • Exploring the different scopes for the built-in DI container
  • Creating a Domain Layer for the domain objects
  • Creating either an IRepository interface

And if there’s time, we’ll start investigating .NET Maui. So do check back as we add more dev logs during Alex’s journey with us.