App 2: The Database Class

When I think about how TDL’s main method starts, I think that one of the first things it needs to do is create an instance of a Database class:

@main
def ToDoList = 
    val db = Database("./ToDoList.dat")
    ...

In that code I pass Database the name of the file it should use as its flat-file database. Right away, this tells me that I need a Database class that takes a filename input parameter:

class Database(val dbFilename: String):
    // methods go here ...
end Database

As you’ll see in the following lessons, we’ll then define these functions inside that class, to mimic the way SQL functions typically work:

  • insert
  • delete
  • selectAll

NOTE: Writing this code is the same as writing OOP code, where you have a class that takes a constructor parameter (dbFilename), and then the methods in that class use that parameter. The only difference is that I won’t mutate that parameter, so, as shown, I define it as a val parameter.

About the ‘Try’ rule

Recall my rule that any function that reaches out to the outside world must return the Try error-handling type. This is overkill for a flat-file database, but it helps if you recall that real database code typically accesses another server (or cluster) that may or may not be running when you call it. This is also just like calling a REST API that may or may not be running, across a network that may or may not be available.

In these situations, robust code must return an error-handling type like Try or Either, and for our purposes, I’ll use Try.

How this affects Database

Given this rule, I sketch the Database class to have methods with these type signatures:

class Database(dbFilename: String):

    def insert(record: String): Try[Unit] = ???
    def selectAll(): Try[Seq[String]] = ???
    def delete(indexToDelete: Int): Try[Int] = ???

end Database

I’ll explain that code in the following chapters, but briefly:

  • insert returns Try[Unit], and its Success and Failure results tell us if it worked
  • selectAll returns Try[Seq[String]], which is a list of strings inside a Try.
  • delete returns Try[Int], and its Int value indicates the number of rows deleted.

In all of those cases, if our “database server” is down, the Try we get back will be a Failure, but if the server is up and running, we should get back a Success.

Methods vs functions

As a final note, this is my personal standard about using the names “methods” and “functions” when talking about Scala code:

  • When you define these inside a class, I refer to them as methods.
  • Anywhere else, I refer to them as functions.

This isn’t a standard Scala naming convention, it’s my own personal convention. It always feels weird to talk about functional programming while constantly using the term method, so that’s how I came up with this personal standard. Therefore, from here on out, I’ll refer to “methods in the Database class.”