Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Podcasts & Audio Briefings9to5Google: Pixel 11 Pro: Software is built DIFFERENT(02.10.2026 um 19:35 Uhr)
•
Windows Tipps & SecurityHP Announces Wildcat Lake Laptops for Consumers and Businesses(02.10.2026 um 19:49 Uhr)
•••••••
Sichere ProgrammierungThe Boring Part of Test Automation That Matters More Than AI(02.10.2026 um 19:51 Uhr)
•
Sichere ProgrammierungBitbucket app passwords removal: what breaks(02.10.2026 um 19:51 Uhr)
•
Podcasts & Audio Briefings9to5Google: Pixel 11 Pro: Software is built DIFFERENT(02.10.2026 um 19:35 Uhr)
•
Windows Tipps & SecurityHP Announces Wildcat Lake Laptops for Consumers and Businesses(02.10.2026 um 19:49 Uhr)
•••••••
Sichere ProgrammierungThe Boring Part of Test Automation That Matters More Than AI(02.10.2026 um 19:51 Uhr)
•
Sichere ProgrammierungBitbucket app passwords removal: what breaks(02.10.2026 um 19:51 Uhr)
•
Intelligence View
⚡ tsecurity.de Intelligence

Building My First Scala DSL (and the Design Mistakes I Made)

I’ve always liked DSLs, but building one turned out to be more humbling than I expected. ScalaCron was my first real attempt at designing an embedded DSL, and w…

Beitrag
0
Seite
0
↗ Quelle (dev.to)
Social ReaktionenReagiere als Erste:r — dein Feedback zählt!

I’ve always liked DSLs, but building one turned out to be more humbling than I expected. ScalaCron was my first real attempt at designing an embedded DSL, and while it works, it also taught me a few lessons the hard way.






Some months I decided to pick up Scala again and make some projects. One of them was my first embedded DSL experiment : ScalaCron.



Just in case you aren't familiar with the term, a Domain Specific Language is a language used for very specific fields. It attempts to solve the complexity of a language by removing imperative programming in favor of a more declarative approach. It has a syntax that tells what will be done instead of how to do it. There are two types: external languages and non-external. The difference is that the former runs on top of a host language and the latter does not. Some popular DSLs of both types are: SQL, Regex, and Makefiles (external), and LINQ, Scala collections, and Gradle Kotlin DSL (non-external).



Scala syntax and abstractions make writing DSLs relatively easy.



ScalaCron can generate Cronjob expressions programmatically.



DSL development is a two layer architecture design. I knew this before, but it hit me harder a bit too late, perhaps.



What are these two layers?

1.- Domain modeling.

2.- Syntax and interpreation layer.



Designing the domain was relatively easy, but soon enough I faced a point at which, even though I had a safe model layer, it made it difficult to make the syntax more English-like.



I had this method for expressions to validate their values:




    final def applyValidated(value: A): Either[CronError, CronExpr[A]] =
if range.contains(value) && pf.isDefinedAt(value) && pf(value) then
Right(At(build(value)))
else
Left(InvalidRange(value, range.end))






At the time it worked just fine, but when I was moving on to making the syntax prettier, I found that the handling of errors leaked through and made abstracting things harder (not impossible, but syntactically ugly).



At some point I had something like:




  import CronDSL.*

val job = CronJobExpr.build { c =>
c.m = * / 5
c.h = 12.h
c.dom = 1.dom
c.dow = 1.dow
}

println(job) //*/5 12 1 1






which needed a builder and I didn't like it.



After applying some abstractions and making tradeoffs for keeping my sanity without refactor, I decided to move validation to the interpretation layer:




private def validateExpr(expr: CronExpr[Int]): List[CronError] =
expr match {

case At(v) =>
validateValue(v)

case Range(from, to) =>
validateValue(from) ++
validateValue(to) ++
validateRangeOrder(from, to)

case ListExpr(values) =>
values.flatMap(validateValue)

case Every(step, field) =>
val stepErrors =
if (step <= 0) List(InvalidStep(step)) else Nil
stepErrors

case Step(from, step) =>
val stepErrors =
if (step <= 0) List(InvalidStep(step)) else Nil
validateValue(from) ++ stepErrors
}






Granted it made things easier, but I still feel a bit guilty that validation was delayed this way.



In the end, I decided not to refactor as this was just an experiment and the syntax was just good enough




import domain.Models.*
import domain.Models.Minute.given
import dsl.CronDSL.{to, *}
import Days.*
import scala.language.postfixOps

object Main :
def main(args: Array[String]): Unit =

val job2 = cron { c =>
c.minute(* / 5)
c.hour(2.h)
c.dom(1.dom)
c.dow(Friday)
} >> "/usr/bin/backup.sh"
println(job2?) //same as job.schedule
println(job2) //whole expression as string

/*
- Minute : every 5 minutes
- Hour : at 2
- Day of Month : at 1
- Day of Week : Friday
*/
5 12 1 1 /usr/bin/backup.sh
*/

In an upcoming post, I’ll walk through a second DSL where I applied these lessons from the start, especially around validation, layering, and syntax design and the results were noticeably better.







ScalaCron repo

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Building My First Scala DSL (and the Design Mistakes I Made)

Thematisch verwandte Begriffe: Building, First, Scala, Design · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag