What is declarative? describes, or declares, what the desired result should look like. At the same time, it doesn’t explicitly specify the steps needed to reach its goal. Wikipedia has a bit more formal definition, yet still readable: Declarative code Declarative programming is a programming paradigm — a style of building the structure and elements of computer programs — that expresses the logic of a computation without describing its control flow. As we see, declarative programming has nothing to do with the language. You may very well use dense imperative code in Haskell. You can write declarative code in PHP. It does have something to do with you though. Check out a quick example of declarative and imperative code, deliberately in pseudo-code. "Unit" thing there is a main building block in your language of choice. Typically, it’s either a class or a function. Here is a declarative one: unit sum { (int a, int b) => a + b } Why is it declarative in the first place? Because only a desired result is described, which is a sum. You can implement it in a number of ways, not only by addition. Declarative programming concept is so badass that it even has a real-life counterpart. It's starting any endeavor with the end in mind. and knew what they talked about. Seneca Stephen Covey Nevertheless, when one needs a sum, the following code is typical: unit add { (int a, int b) => a + b } What a big deal, just a different name, you might say. But naming is what reflects your way of thinking. By the way, this is exactly the cause why, they say, . So there is just a tiny difference in this situation, where we have only two basic programming units. And it’s huge when we have 1 million lines of code. naming is among two most difficult things in development The consequences of being imperative on a bigger scale How do your user stories (or Application services, or Controllers) look like? I bet you recognize the following pattern: Validate a request Send some http request Parse response Update some entity Save it in a database Quick declarativeness test: you API changes, 3rd party API changes either, including the number and format of requests, thus parsing logic changes, consequently entity update logic does either, and finally, however crazy, you DB vendor changes. Does you controller code have to change? If yes – bad news, sorry: your code is probably imperative. I hope you have controller tests in place. But chances are you don’t. Anyways, you’re facing a changes that affect a significant area of your codebase. Declarative approach helps you mitigate those issues. First, you don’t have to validate the request. You just need a request. Second, you don’t have to send an http request and parse its response. Instead, you need either a (like whether a transaction authorization was successful or not) or particular data (like special marketing offer for current client). And finally you don’t have to update an entity and save it in database; instead you want it . validated command result persisted So as a result we get the following list of expressions: 1. Validated request 2. Information about either successfulness or remote entity data 3. Persisted local object as a result There are no implementation details left on this user-story level. Thus, your design will be subjected to less changes in case of any requirement changes. The scope of changes will be smaller, which means more readable and maintainable code. Declarative code probably can be considered as a special case of encapsulation. But there are plenty of ways to organize your classes in such a way that encapsulation is respected. Declarative code is both encapsulated and descriptive -- contrary to prescriptive -- giving you an extra benefit. What declarative is not? One of the misconceptions I see quite often is that the fact that you’ve put all your dependencies in a config file automatically makes your code declarative. Nope. Declarative code is much more than mechanical actions. Simply sticking your in a config file won’t bring you closer to maintainable code. service classes Using functions does not ensure your code to be declarative either. You can wrap each implementation step into its own function (or a service class), as in a previous controller example. But it still remains susceptible to specific kinds of changes. Declarative validation If you’re tired of spaghetti validation code mess, you can try a . Class names reflect what is validated, not how. Their implementation doesn’t clutter the higher-level validation logic description. It’s especially convenient in case of complex json data structures, when your validation composite object reflects the request structure. And as a nice declarative-approach side-effect, your code is not . declarative approach temporally coupled To get a feel of what it looks like, consider a following example of a complex request validation. JSON structure looks the following: { :{ : , : , : }, :{ :[ { : }, { : } ], :{ : }, : }, :{ : , :{ : }, :{ : }, :{ : } }, :{ : }, : } "guest" "phone" "+44123456789" "name" "Vasily Belov" "email" "vasya@belov.com" "bag" "items" "id" 888 "id" 777 "discount" "promo_code" "VASYA1988" "served_for" 3 "delivery" "type_id" 20 "where" "restaurant_id" 1 "when" "datetime" "2019-10-23T08:33:11.798400+00:00" "from_where" "restaurant_id" 1 "payment" "type_id" 30 "source" 20 The semantics basically doesn’t really matter, though you can guess that it has something to do with food delivery order registration. Schema is quite large. Typically, validation code is less then clear. Here is the declarative validation composite with (check for a line-by-line analysis, it’s not as scary as you imagine): Validol library Validol’s Quick start page FastFail<>( WellFormedJson( Unnamed<>(Either.right( Present<>( .jsonRequestString))) ), requestJsonObject -> UnnamedBlocOfNameds<>( List.of( FastFail<>( IsJsonObject( Required( IndexedValue( , requestJsonObject) ) ), guestJsonObject -> NamedBlocOfNameds<>( , List.of( AsString( Required( IndexedValue( , guestJsonObject) ) ), AsString( Required( IndexedValue( , guestJsonObject) ) ) ), Guest.class ) ), FastFail<>( Required( IndexedValue( , requestJsonObject) ), itemsJsonElement -> NamedBlocOfUnnameds<>( , itemsJsonElement, item -> UnnamedBlocOfNameds<>( List.of( AsInteger( Required( IndexedValue( , item) ) ) ), Item.class ), Items.class ) ), FastFail<>( Required( IndexedValue( , requestJsonObject) ), deliveryJsonElement -> SwitchTrue<>( , List.of( Specific<>( () -> , UnnamedBlocOfNameds<>( List.of( FastFail<>( IndexedValue( , deliveryJsonElement), whereJsonElement -> NamedBlocOfNameds<>( , List.of( AsString( Required( IndexedValue( , whereJsonElement) ) ), AsInteger( Required( IndexedValue( , whereJsonElement) ) ) ), Where.class ) ), FastFail<>( IndexedValue( , deliveryJsonElement), whenJsonElement -> NamedBlocOfNameds<>( , List.of( AsDate( AsString( Required( IndexedValue( , whenJsonElement) ) ), SimpleDateFormat( ) ) ), DefaultWhen.class ) ) ), CourierDelivery.class ) ) ) ) ), AsInteger( Required( IndexedValue( , requestJsonObject) ) ) ), OrderRegistrationRequestData.class ) ) .result() new new new new this new new new new new "guest" new "guest" new new new "email" new new new "name" new new new "items" new "items" new new new new "id" new new new "delivery" new "delivery" new // Here goes the condition whether this order should be delivered by courier or picked up. // It's omitted for brevity. true new new new "where" new "where" new new new "street" new new new "building" new new "when" new "when" new new new new "date" new "yyyy-MM-dd HH:mm:ss" new new new "source" What catches the eye first? There are plenty of gizmos. This class accepts exactly two arguments: original element and closure. Whether the first parameter results in true, the second closure is invoked. The typical cases are just as in an example above: FastFail Check whether the request represents a well-formed json Check whether some key is present (like ) new IndexedValue("when", deliveryJsonElement) Second, the object structure reflects the request structure. It might seem to (and actually could) be a drawback, since the request can be extremely complex. The solution is pretty simple: you can represent each semantic block as its own class, like the following: FastFail<>( WellFormedJson( Unnamed<>(Either.right( Present<>( .jsonRequestString))) ), requestJsonObject -> UnnamedBlocOfNameds<>( List.of( Guest(requestJsonObject), Items(requestJsonObject), RequiredNamedBlocOfCallback<>( , requestJsonObject, deliveryJsonElement -> SwitchTrue<>( , List.of( Specific<>( () -> , Courier(deliveryJsonElement) ) ) ) ), Source(requestJsonObject) ), OrderRegistrationRequestData.class ) ) new new new new this new new new new "delivery" new "delivery" new // pretty dumb clause true new new If you have a block structure that depends on passed type (like ), is your friend. It represents a sort of declarative switch-case expression, where the value checked against is always true (hence the name, "Switch True"). delivery.type_id SwitchTrue If everything’s successful, you get a data object reflecting the request structure, with type hinted values: Result<OrderRegistrationRequestData> result = ValidatedOrderRegistrationRequest(jsonRequest).result(); result.value().raw().guest().email(); new // get an email: In case you passed only and , you would get a following error: guest.email source assertFalse(result.isSuccessful()); assertEquals( .of( , .of( , MustBePresent().value()), , MustBePresent().value(), , MustBePresent().value(), , MustBeInteger().value() ), result.error().value() ); Map "guest" Map "email" new "items" new "delivery" new "source" new There is plenty of space left to extend the logic, just add another validating decorator. More examples Check out more usage examples . is an example of inline validation in greater detail. you can find out how to split the validation logic according to semantic request blocks. in Unit-tests Here And here